querysub 0.312.0 → 0.313.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.cursorrules +1 -1
  2. package/costsBenefits.txt +4 -1
  3. package/package.json +3 -2
  4. package/spec.txt +23 -18
  5. package/src/-0-hooks/hooks.ts +1 -1
  6. package/src/-a-archives/archives.ts +16 -3
  7. package/src/-a-archives/archivesBackBlaze.ts +51 -3
  8. package/src/-a-archives/archivesLimitedCache.ts +175 -0
  9. package/src/-a-archives/archivesPrivateFileSystem.ts +299 -0
  10. package/src/-a-auth/certs.ts +58 -31
  11. package/src/-b-authorities/cdnAuthority.ts +2 -2
  12. package/src/-b-authorities/dnsAuthority.ts +3 -2
  13. package/src/-c-identity/IdentityController.ts +3 -2
  14. package/src/-d-trust/NetworkTrust2.ts +17 -19
  15. package/src/-e-certs/EdgeCertController.ts +3 -4
  16. package/src/-e-certs/certAuthority.ts +1 -2
  17. package/src/-f-node-discovery/NodeDiscovery.ts +9 -7
  18. package/src/-g-core-values/NodeCapabilities.ts +6 -1
  19. package/src/0-path-value-core/NodePathAuthorities.ts +1 -1
  20. package/src/0-path-value-core/PathValueCommitter.ts +3 -3
  21. package/src/0-path-value-core/PathValueController.ts +3 -3
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +15 -37
  23. package/src/0-path-value-core/pathValueCore.ts +4 -3
  24. package/src/3-path-functions/PathFunctionRunner.ts +2 -2
  25. package/src/4-dom/qreact.tsx +4 -3
  26. package/src/4-querysub/Querysub.ts +2 -2
  27. package/src/4-querysub/QuerysubController.ts +2 -2
  28. package/src/5-diagnostics/GenericFormat.tsx +1 -0
  29. package/src/5-diagnostics/Table.tsx +3 -0
  30. package/src/5-diagnostics/diskValueAudit.ts +2 -1
  31. package/src/5-diagnostics/nodeMetadata.ts +0 -1
  32. package/src/deployManager/components/MachineDetailPage.tsx +9 -1
  33. package/src/deployManager/components/ServiceDetailPage.tsx +10 -1
  34. package/src/diagnostics/NodeViewer.tsx +3 -4
  35. package/src/diagnostics/logs/FastArchiveAppendable.ts +748 -0
  36. package/src/diagnostics/logs/FastArchiveController.ts +524 -0
  37. package/src/diagnostics/logs/FastArchiveViewer.tsx +863 -0
  38. package/src/diagnostics/logs/LogViewer2.tsx +349 -0
  39. package/src/diagnostics/logs/TimeRangeSelector.tsx +94 -0
  40. package/src/diagnostics/logs/diskLogger.ts +135 -305
  41. package/src/diagnostics/logs/diskShimConsoleLogs.ts +6 -29
  42. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +577 -0
  43. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +225 -0
  44. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +207 -0
  45. package/src/diagnostics/logs/importLogsEntry.ts +38 -0
  46. package/src/diagnostics/logs/injectFileLocationToConsole.ts +7 -17
  47. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +0 -0
  48. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +151 -0
  49. package/src/diagnostics/managementPages.tsx +7 -16
  50. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +0 -1
  51. package/src/diagnostics/periodic.ts +5 -0
  52. package/src/diagnostics/watchdog.ts +2 -2
  53. package/src/functional/SocketChannel.ts +67 -0
  54. package/src/library-components/Input.tsx +1 -1
  55. package/src/library-components/InputLabel.tsx +5 -2
  56. package/src/misc.ts +111 -0
  57. package/src/src.d.ts +34 -1
  58. package/src/user-implementation/userData.ts +4 -3
  59. package/test.ts +13 -0
  60. package/testEntry2.ts +29 -0
  61. package/src/diagnostics/errorLogs/ErrorLogController.ts +0 -535
  62. package/src/diagnostics/errorLogs/ErrorLogCore.ts +0 -274
  63. package/src/diagnostics/errorLogs/LogClassifiers.tsx +0 -308
  64. package/src/diagnostics/errorLogs/LogFilterUI.tsx +0 -84
  65. package/src/diagnostics/errorLogs/LogNotify.tsx +0 -101
  66. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +0 -723
  67. package/src/diagnostics/errorLogs/LogViewer.tsx +0 -757
  68. package/src/diagnostics/errorLogs/logFiltering.tsx +0 -149
  69. package/src/diagnostics/logs/DiskLoggerPage.tsx +0 -613
@@ -1,613 +0,0 @@
1
- import { css, isNode } from "typesafecss";
2
-
3
- import { SocketFunction } from "socket-function/SocketFunction";
4
- import { getSourceVSCodeLink, qreact } from "../../4-dom/qreact";
5
- import { assertIsManagementUser } from "../managementPages";
6
- import { LogFile, LogObj, getLogBuffer, getLogFiles, parseLogBuffer } from "./diskLogger";
7
- import { Querysub, t } from "../../4-querysub/Querysub";
8
- import { URLParam } from "../../library-components/URLParam";
9
- import { getSyncedController } from "../../library-components/SyncedController";
10
- import { getBrowserUrlNode } from "../../-f-node-discovery/NodeDiscovery";
11
- import { formatDateTime, formatNiceDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
12
- import { Anchor } from "../../library-components/ATag";
13
- import { cache, cacheLimited, cacheShallowConfigArgEqual, lazy } from "socket-function/src/caching";
14
- import { InputLabel, InputLabelURL } from "../../library-components/InputLabel";
15
- import { DropdownSelector } from "../../library-components/DropdownSelector";
16
- import { Table, TableType } from "../../5-diagnostics/Table";
17
- import { LogType } from "../errorLogs/ErrorLogCore";
18
- import { canHaveChildren } from "socket-function/src/types";
19
- import { measureBlock, measureWrap } from "socket-function/src/profiling/measure";
20
- import { binarySearchBasic, list, sort, timeInDay, timeInHour } from "socket-function/src/misc";
21
- import { NodeViewerController, nodeViewerController } from "../NodeViewer";
22
- import { red } from "socket-function/src/formatting/logColors";
23
- import { errorMessage, formatValue, genericFormat, warnMessage } from "../../5-diagnostics/GenericFormat";
24
- import { hslToRGB } from "socket-function/src/formatting/colors";
25
- import { InputPicker } from "../../library-components/InputPicker";
26
- import { ObjectDisplay } from "./ObjectDisplay";
27
- import { parseAnsiColors, rgbToHsl } from "./ansiFormat";
28
- import { ShowMore } from "../../library-components/ShowMore";
29
- import { getNodeIdLocation } from "socket-function/src/nodeCache";
30
- import { decodeNodeId, encodeNodeId, getMachineId } from "../../-a-auth/certs";
31
- import { Button } from "../../library-components/Button";
32
- import { TimeRangeSelector } from "../../library-components/TimeRangeSelector";
33
- import { BrowserLargeFileCache, cacheCalls } from "./BrowserLargeFileCache";
34
- import { Zip } from "../../zip";
35
-
36
- // TODO: Realtime log mode, by reading from the previous length forward, to add buffers
37
- // to what we already read.
38
- // - ALSO, correctly handle cut off messages? Or not, if the writes are atomic
39
- // it might be fine...
40
-
41
- // TODO: Allow showing context for logs, showing filter logs PLUS +/- a certain number of lines
42
- // - Also grouping them, so we can see which are part of which.
43
- // - Maybe only showing +/- within a time range as well, and maybe only the same file
44
-
45
- // TODO: Parse objects in strings and display them nicely
46
- // - I don't know if we even want to add filter support, but... making them more readable
47
- // should be easy enough, and might help with reading objects in error messages?
48
-
49
- // TODO: Track line locations from diskLog
50
- // - Probably by injecting ids? Although how would they be persistent? Hmm... Maybe line numbers
51
- // is fine? I think we map the typescript (I hope we do), so they should be correct?
52
-
53
- // TODO: Use a binary format, but faster parsing.
54
- // - If we could batch it, it would probably be very fast. We ARE just dealing with KVPs though,
55
- // so... maybe it wouldn't be that much faster...
56
-
57
- module.hotreload = true;
58
- module.hotreload = true;
59
-
60
- export let selectedNodeId = new URLParam("nodeId", "");
61
-
62
- export let filterURL = new URLParam("filter", "");
63
- let sortOldestFirst = new URLParam("oldestFirst", false);
64
- let selectedFields = new URLParam("selectedFields", "");
65
-
66
- let startTimeURL = new URLParam("startTime", undefined as number | undefined);
67
- let endTimeURL = new URLParam("endTime", undefined as number | undefined);
68
-
69
- let inspectTimeURL = new URLParam("inspectTime", "");
70
-
71
-
72
- export class DiskLoggerPage extends qreact.Component {
73
- state = t.state({
74
- downloadFilesHash: t.string,
75
- });
76
- render() {
77
- let selectedNodesIds = selectedNodeId.value.split("|").filter(x => x);
78
- const nodeController = nodeViewerController(getBrowserUrlNode());
79
- let nodeIds = nodeController.getControllerNodeIdList(DiskLoggerController) || [];
80
-
81
- if (selectedNodesIds.length === 0) {
82
- // Group by machineId
83
- let grouped = new Map<string, { nodeId: string; entryPoint: string }[]>();
84
- for (let { nodeId, entryPoint } of nodeIds) {
85
- let obj = decodeNodeId(nodeId);
86
- if (!obj) continue;
87
- let { machineId } = obj;
88
- let list = grouped.get(machineId);
89
- if (!list) {
90
- grouped.set(machineId, list = []);
91
- }
92
- list.push({ nodeId, entryPoint });
93
- }
94
- return (
95
- <div class={css.pad2(10).vbox(10)}>
96
- <h1>Node Ids ({nodeIds.length})</h1>
97
- <div class={css.hbox(40).wrap}>
98
- {Array.from(grouped.entries()).map(([machineId, list]) =>
99
-
100
- <div class={css.vbox(10).wrap.maxWidth("30vw").pad2(5).hsla(0, 0, 0, 0.1)}>
101
- <div class={css.fontSize(30)}>{getMachineId(machineId)}</div>
102
- <div class={css.hbox(20, 5).wrap}>
103
- {list.map(({ nodeId, entryPoint }) =>
104
- <Anchor
105
- class={css.hbox(10)}
106
- values={[selectedNodeId.getOverride(nodeId)]}
107
- >
108
- {nodeId} ({entryPoint.replaceAll("\\", "/").split("/").pop()})
109
- </Anchor>
110
- )}
111
- </div>
112
- </div>
113
- )}
114
- </div>
115
- </div>
116
- );
117
- }
118
-
119
- let startTime = Number(startTimeURL.value) || (Date.now() - timeInDay);
120
- let endTime = Number(endTimeURL.value) || (Date.now() + timeInDay);
121
- const controller = diskLoggerController(getBrowserUrlNode());
122
- let allFiles: (LogFile & { nodeId: string })[] = [];
123
- let loadingCount = 0;
124
-
125
- for (let nodeId of selectedNodesIds) {
126
- try {
127
- let newFiles = controller.getRemoteLogFiles(nodeId);
128
- if (newFiles) {
129
- allFiles.push(...newFiles.map(x => ({ ...x, nodeId })));
130
- } else {
131
- loadingCount++;
132
- }
133
- } catch (e) {
134
- console.log("Error reading files", nodeId, e);
135
- }
136
- }
137
- //files = files.filter(file => file.startTime <= endTime && file.endTime >= startTime);
138
- // File to fix some broken files
139
- allFiles = allFiles.filter(file => file.startTime >= +new Date("1980"));
140
- let buffers: Buffer[] = [];
141
-
142
- let firstTime = Number.MAX_SAFE_INTEGER;
143
- let lastTime = Number.MIN_SAFE_INTEGER;
144
-
145
- let filesHash = JSON.stringify(allFiles.map(x => x.path));
146
- let fileSizeSum = 0;
147
- let fileCount = 0;
148
-
149
- let selectedFiles: (LogFile & { nodeId: string })[] = [];
150
-
151
- for (let file of allFiles) {
152
- try {
153
- if (file.startTime < firstTime) firstTime = file.startTime;
154
- if (file.endTime > lastTime) lastTime = file.endTime;
155
- if (file.startTime > endTime) continue;
156
- if (file.endTime < startTime) continue;
157
- selectedFiles.push(file);
158
- fileCount++;
159
- fileSizeSum += file.size;
160
- if (filesHash !== this.state.downloadFilesHash) {
161
- loadingCount++;
162
- continue;
163
- }
164
- let buffer = controller.getRemoteLogBuffer(file.nodeId, file.path);
165
- if (!buffer) {
166
- loadingCount++;
167
- continue;
168
- }
169
- buffer = unzipCached(buffer);
170
- buffers.push(buffer);
171
- } catch (e) {
172
- console.log("Error reading buffer", file, e);
173
- }
174
- }
175
- if (firstTime === Number.MAX_SAFE_INTEGER) firstTime = startTime;
176
- if (lastTime === Number.MIN_SAFE_INTEGER) lastTime = endTime;
177
-
178
- let logs: LogObj[] = loadingCount ? [] : parseLogBufferCached(JSON.stringify(allFiles), buffers);
179
-
180
- const defaultFields = ["time", "type", "actions", "param0"];
181
- let selectedValues = selectedFields.value.split("|").filter(x => x);
182
- let fullSelectedFields = defaultFields.concat(selectedValues);
183
-
184
- let { table, allFields, filteredCount, totalCount } = processLogs({
185
- logs,
186
- startTime, endTime,
187
- order: sortOldestFirst.value ? "oldestFirst" : "newestFirst",
188
- filter: filterURL.value,
189
- selectedFields: fullSelectedFields,
190
- inspectTime: inspectTimeURL.value,
191
- });
192
-
193
- function addPath(path: string[]) {
194
- let newSelected = Array.from(new Set([...selectedValues, path.join(".")])).join("|");
195
- selectedFields.value = newSelected;
196
- }
197
-
198
-
199
- for (let column of Object.values(table.columns)) {
200
- if (!column) continue;
201
- if (!column.formatter) {
202
- column.formatter = (value: unknown) => <ObjectDisplay
203
- value={value}
204
- excludedFields={fullSelectedFields}
205
- onClickValue={(path, value) => {
206
- if (!canHaveChildren(value)) {
207
- // Add to search as well
208
- if (filterURL.value.trim()) {
209
- filterURL.value += " & ";
210
- }
211
- filterURL.value += String(value);
212
- }
213
- }}
214
- />;
215
- }
216
- }
217
-
218
- table.columns["actions"] = {
219
- formatter(value, context) {
220
- let file = (context as any)?.row.__FILE__;
221
- if (!file) return undefined;
222
- let line = (context as any)?.row.__LINE__;
223
- return (
224
- <div>
225
- <button onClick={() => {
226
- let path = `vscode://file/${file}`;
227
- if (typeof line === "number") {
228
- path += `:${line + 1}`;
229
- }
230
- window.open(path);
231
- }}>Open In VS Code</button>
232
- </div>
233
- );
234
- },
235
- };
236
- table.columns["time"] = {
237
- formatter(value, context) {
238
- return (
239
- <div
240
- className={css.button + (css.background("hsla(0, 0%, 100%, 0.2)", "hover"))}
241
- onClick={() => {
242
- inspectTimeURL.value = String(value) + "|" + 10 + "|" + 10;
243
- filterURL.value = "";
244
- }}
245
- >
246
- {genericFormat(value)}
247
- </div>
248
- );
249
- },
250
- };
251
-
252
- table.columns["type"] = {
253
- formatter(value, context) {
254
- let inner = (() => {
255
- if (value === "error") return <span className={errorMessage}>{value}</span>;
256
- if (value === "warn") return <span className={warnMessage}>{value}</span>;
257
- if (value === "info" || value === "log") return <span className={css.hsl(210, 50, 50).pad2(6, 4).color("white")}>{value}</span>;
258
- return formatValue(value, undefined, context);
259
- })();
260
- return <div><div className={css.width(100).hbox0.center}>{inner}</div></div>;
261
- }
262
- };
263
- table.columns["param0"] = {
264
- title: "Message",
265
- formatter(value, context) {
266
- return <ObjectDisplay value={value} />;
267
- },
268
- };
269
-
270
- table.columns["remaining"] = {
271
- title: "Object",
272
- formatter(value, context) {
273
- return <ObjectDisplay
274
- value={context?.row}
275
- onClickKey={path => addPath(path)}
276
- onClickValue={(path, value) => {
277
- addPath(path);
278
- if (!canHaveChildren(value)) {
279
- // Add to search as well
280
- if (filterURL.value.trim()) {
281
- filterURL.value += " & ";
282
- }
283
- filterURL.value += String(value);
284
- }
285
- }}
286
- />;
287
- },
288
- };
289
-
290
- let [inspectTime, inspectBeforeCount, inspectAfterCount] = inspectTimeURL.value.split("|").map(x => Number(x));
291
- if (inspectTime) {
292
- startTime = inspectTime;
293
- endTime = inspectTime;
294
- }
295
-
296
- return (
297
- <div class={css.pad2(10).vbox(10)}>
298
- <Anchor values={[selectedNodeId.getOverride("")]}><h2>Back</h2></Anchor>
299
- <InputPicker
300
- picked={selectedNodesIds}
301
- addPicked={x => selectedNodeId.value = [...selectedNodesIds, x].join("|")}
302
- removePicked={x => selectedNodeId.value = selectedNodesIds.filter(y => y !== x).join("|")}
303
- options={nodeIds.map(({ nodeId, entryPoint }) => ({ value: nodeId, label: `${nodeId} (${entryPoint.replaceAll("\\", "/").split("/").pop()})` }))}
304
- />
305
- <div className={css.fontSize(28).boldStyle}>
306
- {selectedNodesIds.length} nodes, {fileCount}/{allFiles.length} files, filtered logs {formatNumber(filteredCount)} / {formatNumber(totalCount)}
307
- </div>
308
- <ShowMore maxHeight={40}>
309
- <div class={css.vbox(4)}>
310
- {selectedFiles.map(file =>
311
- <div class={css.hbox(4)}>
312
- <span>{formatNumber(file.size)}B</span>
313
- <span>{formatDateTime(file.startTime)} - {formatDateTime(file.endTime)}</span>
314
- <span>({file.path})</span>
315
- </div>
316
- )}
317
- </div>
318
- </ShowMore>
319
- {filesHash !== this.state.downloadFilesHash && <div>
320
- <button onClick={() => {
321
- this.state.downloadFilesHash = filesHash;
322
- }}>
323
- Download Files ({formatNumber(fileSizeSum)}B)
324
- </button>
325
- </div>
326
- || loadingCount > 0 && <h1 className={css.hsl(210, 75, 75).pad2(10, 2)}>Downloading {loadingCount} file(s)</h1>
327
- }
328
- {!inspectTime && <>
329
- <TimeRangeSelector
330
- start={startTimeURL} end={endTimeURL}
331
- defaultStart={startTime} defaultEnd={endTime}
332
- firstTime={firstTime} lastTime={lastTime}
333
- />
334
- <div class={css.hbox(20)}>
335
- <Button onClick={() => { startTimeURL.reset(); endTimeURL.reset(); }}>Reset Time Filter</Button>
336
- <div class={css.hbox(10)}>
337
- <InputLabelURL label="Start Time" url={startTimeURL} isDatetime valueDefault={startTime} />
338
- <span>({formatNiceDateTime(startTime)})</span>
339
- </div>
340
- <div class={css.hbox(10)}>
341
- <InputLabelURL label="End Time" url={endTimeURL} isDatetime valueDefault={endTime} />
342
- <span>({formatNiceDateTime(endTime)})</span>
343
- </div>
344
- </div>
345
- <div className={css.hbox(20)}>
346
- <h2>{formatTime(Date.now() - startTime)} AGO</h2> to <h2>{formatTime(Date.now() - endTime)} AGO</h2>
347
- </div>
348
- </> ||
349
- <div class={css.hbox(20)}>
350
- <h2>{formatTime(Date.now() - inspectTime)} AGO</h2>
351
- <h2>{formatNiceDateTime(inspectTime)}</h2>
352
- <InputLabel
353
- label="Inspect Time"
354
- value={inspectTime}
355
- isDatetime
356
- onChange={x => inspectTimeURL.value = x + "|" + inspectBeforeCount + "|" + inspectAfterCount}
357
- />
358
- <button onClick={() => {
359
- inspectTimeURL.value = "";
360
- }}>
361
- Reset Inspect
362
- </button>
363
- </div>
364
- }
365
-
366
- <InputLabelURL fillWidth label="Filter: ['|' to match any conditions, '&' to match all, '!' for negation]" url={filterURL} />
367
- <InputLabelURL label="Sort Oldest First" checkbox url={sortOldestFirst} />
368
- <InputPicker
369
- label="Selected Fields"
370
- picked={selectedValues}
371
- options={Array.from(allFields.keys()).map(x => ({ value: x, label: x }))}
372
- addPicked={x => selectedFields.value = [...selectedValues, x].join("|")}
373
- removePicked={x => selectedFields.value = selectedValues.filter(y => y !== x).join("|")}
374
- />
375
- {!!inspectTime && (
376
- <InputLabel
377
- label="Before Count"
378
- value={inspectBeforeCount}
379
- onChangeValue={x => inspectTimeURL.value = inspectTime + "|" + x + "|" + inspectAfterCount}
380
- number
381
- />
382
- )}
383
- <Table {...table} initialLimit={30} />
384
- {!!inspectTime && (
385
- <InputLabel
386
- label="After Count"
387
- value={inspectAfterCount}
388
- onChangeValue={x => inspectTimeURL.value = inspectTime + "|" + inspectBeforeCount + "|" + x}
389
- number
390
- />
391
- )}
392
- </div>
393
- );
394
- }
395
- }
396
-
397
- const unzipCached = cacheLimited(100, Zip.gunzipSync);
398
-
399
-
400
- let remainingCacheSize = 1024 * 1024 * 1024 * 2;
401
- let logBufferCache = new Map<string, {
402
- size: number;
403
- logs: LogObj[];
404
- lastAccess: number;
405
- }>();
406
-
407
- const parseLogBufferCached = measureWrap(function parseLogBufferCached(hash: string, buffers: Buffer[]): LogObj[] {
408
- let cached = logBufferCache.get(hash);
409
- if (cached) {
410
- cached.lastAccess = Date.now();
411
- return cached.logs;
412
- }
413
- console.log(`Parsing buffers ${buffers.length}`);
414
- let logs = buffers.map(x => parseLogBuffer(x)).flat();
415
- let size = buffers.map(x => x.length).reduce((a, b) => a + b, 0);
416
- remainingCacheSize -= size;
417
- while (remainingCacheSize < 0 && logBufferCache.size > 0) {
418
- let oldest = [...logBufferCache.entries()].sort((a, b) => a[1].lastAccess - b[1].lastAccess)[0];
419
- logBufferCache.delete(oldest[0]);
420
- remainingCacheSize += oldest[1].size;
421
- }
422
- logBufferCache.set(hash, {
423
- size,
424
- logs,
425
- lastAccess: Date.now()
426
- });
427
- console.log(`Parsed buffers ${buffers.length} into logs ${logs.length}`);
428
- return logs;
429
- });
430
-
431
-
432
-
433
- const processLogs = cacheShallowConfigArgEqual((config: {
434
- logs: LogObj[];
435
- startTime: number; endTime: number;
436
- order: "oldestFirst" | "newestFirst";
437
- filter: string;
438
- selectedFields: string[];
439
- inspectTime: string;
440
- }): {
441
- table: TableType<LogObj>;
442
- allFields: Map<string, number>;
443
- totalCount: number;
444
- filteredCount: number;
445
- } => {
446
- return measureBlock(function processLogs() {
447
- console.log(`Processing logs ${config.logs.length}`);
448
- let { logs, order, filter, startTime, endTime, inspectTime, selectedFields } = config;
449
- logs = logs.slice();
450
- let totalCount = logs.length;
451
-
452
- let logMatchesFilter: (log: LogObj) => boolean = () => true;
453
-
454
- if (filter) {
455
- filter = filter.toLowerCase();
456
- let filterParts = filter.split("|").map(x => x.trim()).map(x => x.split("&").map(x => x.trim()));
457
- // Extract any negation
458
- let negationQueries: string[] = [];
459
- filterParts = filterParts.map(filter => filter.filter(x => {
460
- if (x.startsWith("!")) {
461
- negationQueries.push(x.slice(1));
462
- return false;
463
- }
464
- return true;
465
- })).filter(x => x.length > 0);
466
- function logMatches(filter: string, log: LogObj) {
467
- return JSON.stringify(log).toLowerCase().includes(filter);
468
- }
469
- logMatchesFilter = function logMatchesFilter(log: LogObj) {
470
- return (
471
- (filterParts.length === 0 || filterParts.some(filter => filter.every(filter => logMatches(filter, log))))
472
- && !negationQueries.some(filter => logMatches(filter, log))
473
- );
474
- };
475
- }
476
-
477
- function applySort() {
478
- measureBlock(function filterSort() {
479
- if (order === "oldestFirst") {
480
- sort(logs, x => x.time);
481
- } else {
482
- sort(logs, x => -x.time);
483
- }
484
- });
485
- }
486
-
487
-
488
- if (inspectTime) {
489
- applySort();
490
- // time|before|after
491
- let [time, before, after] = inspectTime.split("|").map(x => Number(x));
492
- applySort();
493
- measureBlock(function filterLogs() {
494
- // Binary search to find the closest index
495
- let sortOrder = order === "oldestFirst" ? 1 : -1;
496
- let index = binarySearchBasic(logs, x => x.time * sortOrder, time * sortOrder);
497
- let startIndex: number;
498
- let endIndex: number;
499
-
500
- let newLogs: LogObj[] = [];
501
- if (index < 0) {
502
- index = ~index;
503
- startIndex = index - 1;
504
- endIndex = index;
505
- } else {
506
- startIndex = index - 1;
507
- endIndex = index + 1;
508
- if (logMatchesFilter(logs[index])) {
509
- newLogs.push(logs[index]);
510
- }
511
- }
512
-
513
- while (before > 0 && startIndex > 0) {
514
- startIndex--;
515
- let logBefore = logs[startIndex];
516
- if (logMatchesFilter(logBefore)) {
517
- newLogs.unshift(logBefore);
518
- before--;
519
- }
520
- }
521
-
522
- while (after > 0 && endIndex < logs.length) {
523
- let logAfter = logs[endIndex];
524
- if (logMatchesFilter(logAfter)) {
525
- newLogs.push(logAfter);
526
- after--;
527
- }
528
- endIndex++;
529
- }
530
-
531
- logs = newLogs;
532
- });
533
- } else {
534
- measureBlock(function filterLogs() {
535
- logs = logs.filter(log =>
536
- startTime <= log.time && log.time <= endTime &&
537
- logMatchesFilter(log)
538
- );
539
- });
540
- applySort();
541
- }
542
-
543
- let filteredCount = logs.length;
544
-
545
- let fields = new Map<string, number>();
546
- for (let log of logs) {
547
- for (let key in log) {
548
- fields.set(key, (fields.get(key) || 0) + 1);
549
- }
550
- }
551
-
552
- let table: TableType<LogObj> = {
553
- rows: logs,
554
- columns: {},
555
- };
556
-
557
- for (let key of selectedFields) {
558
- table.columns[key] = {};
559
- }
560
-
561
- console.log(`Processed logs ${logs.length}`);
562
- return { table, allFields: fields, totalCount, filteredCount };
563
- });
564
- }, 10);
565
-
566
-
567
-
568
-
569
- class DiskLoggerControllerBase {
570
- // Have to forward it, as backend nodes don't have real domains, so HTTPS won't
571
- // work from the browser for them.
572
- public async getRemoteLogFiles(nodeId: string): Promise<LogFile[]> {
573
- return await DiskLoggerController.nodes[nodeId].getLogFiles();
574
- }
575
- public async getRemoteLogBuffer(nodeId: string, path: string): Promise<Buffer | undefined> {
576
- return await DiskLoggerController.nodes[nodeId].getLogBuffer(path);
577
- }
578
-
579
- public async getLogFiles(): Promise<LogFile[]> {
580
- return await getLogFiles();
581
- }
582
- public async getLogBuffer(path: string): Promise<Buffer | undefined> {
583
- // Always compress it. If it is in progress it can't be compressed on disk, but
584
- // the compression ratio is so high it's worth it to compress it dynamically now,
585
- // to speed up the network transfer.
586
- let buffer = await getLogBuffer(path);
587
- if (buffer && path.endsWith(".log")) {
588
- buffer = await Zip.gzip(buffer);
589
- }
590
- return buffer;
591
- }
592
- }
593
-
594
- export const DiskLoggerController = SocketFunction.register(
595
- "DiskLoggerController-f76a6fdf-3bd5-4bd4-a183-55a8be0a5a32",
596
- new DiskLoggerControllerBase(),
597
- () => ({
598
- getRemoteLogFiles: {},
599
- getRemoteLogBuffer: {
600
- clientHooks: [cacheCalls]
601
- },
602
-
603
- getLogFiles: {},
604
- getLogBuffer: {},
605
- }),
606
- () => ({
607
- hooks: [assertIsManagementUser],
608
- }),
609
- {
610
- }
611
- );
612
-
613
- let diskLoggerController = getSyncedController(DiskLoggerController);