querysub 0.312.0 → 0.314.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 (70) 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 +19 -81
  16. package/src/-e-certs/certAuthority.ts +7 -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/deployManager/setupMachineMain.ts +8 -1
  35. package/src/diagnostics/NodeViewer.tsx +5 -6
  36. package/src/diagnostics/logs/FastArchiveAppendable.ts +757 -0
  37. package/src/diagnostics/logs/FastArchiveController.ts +524 -0
  38. package/src/diagnostics/logs/FastArchiveViewer.tsx +863 -0
  39. package/src/diagnostics/logs/LogViewer2.tsx +349 -0
  40. package/src/diagnostics/logs/TimeRangeSelector.tsx +94 -0
  41. package/src/diagnostics/logs/diskLogger.ts +135 -305
  42. package/src/diagnostics/logs/diskShimConsoleLogs.ts +6 -29
  43. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +577 -0
  44. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +225 -0
  45. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +207 -0
  46. package/src/diagnostics/logs/importLogsEntry.ts +38 -0
  47. package/src/diagnostics/logs/injectFileLocationToConsole.ts +7 -17
  48. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +0 -0
  49. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +153 -0
  50. package/src/diagnostics/managementPages.tsx +7 -16
  51. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +0 -1
  52. package/src/diagnostics/periodic.ts +5 -0
  53. package/src/diagnostics/watchdog.ts +2 -2
  54. package/src/functional/SocketChannel.ts +67 -0
  55. package/src/library-components/Input.tsx +1 -1
  56. package/src/library-components/InputLabel.tsx +5 -2
  57. package/src/misc.ts +111 -0
  58. package/src/src.d.ts +34 -1
  59. package/src/user-implementation/userData.ts +4 -3
  60. package/test.ts +13 -0
  61. package/testEntry2.ts +29 -0
  62. package/src/diagnostics/errorLogs/ErrorLogController.ts +0 -535
  63. package/src/diagnostics/errorLogs/ErrorLogCore.ts +0 -274
  64. package/src/diagnostics/errorLogs/LogClassifiers.tsx +0 -308
  65. package/src/diagnostics/errorLogs/LogFilterUI.tsx +0 -84
  66. package/src/diagnostics/errorLogs/LogNotify.tsx +0 -101
  67. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +0 -723
  68. package/src/diagnostics/errorLogs/LogViewer.tsx +0 -757
  69. package/src/diagnostics/errorLogs/logFiltering.tsx +0 -149
  70. package/src/diagnostics/logs/DiskLoggerPage.tsx +0 -613
@@ -1,535 +0,0 @@
1
- import { lazy } from "socket-function/src/caching";
2
- import { LogClass, LOG_FLUSH_INTERVAL, LogBlock, LogBlockInfo, LogRaw, LOG_ARCHIVES, LOG_CLASSES_PATH, logClassesFromString, getLogClassCategorizer, logBlockFromBuffer, logBlockToBuffer, logClassesToString, LOG_BLOCK_EXTENSION, getLogBlockInfo, getLogBlockFileName, addToLogBlock, LogType, isUngroupedClass } from "./ErrorLogCore";
3
- import { list, throttleFunction, timeInDay, timeInMinute } from "socket-function/src/misc";
4
- import { getMachineId, getOwnMachineId, getThreadKeyCert } from "../../-a-auth/certs";
5
- import { getAllNodeIds, getBrowserUrlNode, getOwnNodeId, getOwnThreadId, watchDeltaNodeIds, watchNodeIds } from "../../-f-node-discovery/NodeDiscovery";
6
- import { getArchives } from "../../-a-archives/archives";
7
- import { isNode } from "typesafecss";
8
- import { SocketFunction } from "socket-function/SocketFunction";
9
- import { requiresNetworkTrustHook } from "../../-d-trust/NetworkTrust2";
10
- import { ignoreErrors, logErrors, timeoutToError, timeoutToUndefinedSilent } from "../../errors";
11
- import debugbreak from "debugbreak";
12
- import { blue, magenta } from "socket-function/src/formatting/logColors";
13
- import { formatNumber } from "socket-function/src/formatting/format";
14
- import { registerShutdownHandler } from "../periodic";
15
- import { isNoNetwork } from "../../config";
16
- import { ExternalRenderClass, qreact } from "../../4-dom/qreact";
17
- import { Querysub } from "../../4-querysub/QuerysubController";
18
- import { atomic, doAtomicWrites } from "../../2-proxy/PathValueProxyWatcher";
19
- import { isClient } from "../../config2";
20
- import { ControllerPick, SocketRegistered } from "socket-function/SocketFunctionTypes";
21
-
22
- const NOTIFY_HISTORY = timeInDay * 7;
23
- const LOG_STORE_HISTORY = timeInDay * 14;
24
- // Only error / fatal are tracked, for now... we might also add warns?
25
- const logIssueNotifyTypes: LogType[] = ["error", "fatal"];
26
-
27
- // We don't log on internal errors, otherwise we might infinitely loop
28
- function internalError(err: unknown | Error | Promise<unknown>) {
29
- if (err && err instanceof Error) {
30
- if (!isNode()) {
31
- debugger;
32
- return;
33
- }
34
- process.stderr.write((err.stack || err.message) + "\n");
35
- } else if (err && err instanceof Promise) {
36
- err.catch(internalError);
37
- } else {
38
- if (!isNode()) {
39
- debugger;
40
- return;
41
- }
42
- process.stderr.write("Unknown error: " + String(err) + "\n");
43
- }
44
- }
45
-
46
- export function logSilent(message: string, type: LogType) {
47
- addLocalLog({ message, time: Date.now() }, type);
48
- }
49
- export function logSilentError(message: string) {
50
- logSilent(message, "error");
51
- }
52
- export function logSilentWarning(message: string) {
53
- logSilent(message, "warn");
54
- }
55
-
56
- let nextBlockSeqNum = 1;
57
- let pendingBlock: LogBlock | undefined;
58
- export function addLocalLog(log: LogRaw, typeHint: LogType) {
59
- // NOTE: Eventually... we should ignore logs for non-public machines?
60
- //if (isNoNetwork()) return;
61
- try {
62
- if (!pendingBlock) {
63
- ensureBlocksFlushing();
64
- let now = Date.now();
65
- pendingBlock = {
66
- classes: {},
67
- startTime: now,
68
- endTime: now,
69
-
70
- threadId: "",
71
- machineId: "",
72
-
73
- seqNum: nextBlockSeqNum++,
74
- count: 0,
75
- fatalCount: 0,
76
- errorCount: 0,
77
- warnCount: 0,
78
- infoCount: 0,
79
- };
80
- }
81
-
82
- if (!logClassifier) {
83
- ignoreErrors(initLogClassifier());
84
- pendingLogs.push({ log, type: typeHint });
85
- return;
86
- }
87
- let classifier = logClassifier(log, typeHint);
88
- // Add threadId and machineId, if we have them
89
- try {
90
- pendingBlock.threadId = pendingBlock.threadId || getOwnThreadId();
91
- pendingBlock.machineId = pendingBlock.machineId || getOwnMachineId();
92
- } catch { }
93
- addToLogBlock(pendingBlock, classifier, log);
94
- ErrorLogControllerBase.onLocalLog(log, classifier);
95
- } catch (e) {
96
- internalError(e);
97
- }
98
- }
99
- setTimeout(() => {
100
- void initLogClassifier();
101
- }, 0);
102
- registerShutdownHandler(flushNow);
103
- async function flushNow() {
104
- let newBlock = pendingBlock;
105
- pendingBlock = undefined;
106
- if (!newBlock) return;
107
- newBlock.endTime = Date.now();
108
- //console.log(magenta(`Flushing log block ${formatNumber(newBlock.count)} count, seq num ${newBlock.seqNum}, at ${Date.now()}`));
109
- if (newBlock.count > 0) {
110
- let fileInfo = await logBlockToBuffer(newBlock);
111
- await LOG_ARCHIVES().set(fileInfo.fileName, fileInfo.buffer);
112
- if (Math.random() < 0.05) {
113
- console.log(blue(`Looking for old log blocks`));
114
- let deleteThreshold = Date.now() - LOG_STORE_HISTORY;
115
- let files = await LOG_ARCHIVES().find("", { shallow: true, type: "files" });
116
- for (let file of files) {
117
- if (!file.endsWith(LOG_BLOCK_EXTENSION)) continue;
118
- let info = getLogBlockInfo(file);
119
- if (info.startTime < deleteThreshold) {
120
- console.log(blue(`Deleting old log block ${file}`));
121
- await LOG_ARCHIVES().del(file);
122
- }
123
- }
124
- }
125
- }
126
- }
127
-
128
- let ensureBlocksFlushing = lazy(() => {
129
- setInterval(async () => {
130
- await internalError(flushNow());
131
- }, LOG_FLUSH_INTERVAL);
132
-
133
- // Read log classifiers every hour, in case we miss an update
134
- setInterval(() => {
135
- initLogClassifier.reset();
136
- logErrors(initLogClassifier());
137
- }, timeInMinute * 60);
138
- });
139
-
140
- let pendingLogs: { log: LogRaw; type: LogType }[] = [];
141
- let logClassifier: ((log: LogRaw, typeHint: LogType) => LogClass) | undefined;
142
- let initLogClassifier = lazy(async () => {
143
- if (!isNode()) return null as never;
144
- let logClasses = await new ErrorLogControllerBase().getClasses();
145
- logClassifier = getLogClassCategorizer(logClasses);
146
- if (pendingLogs.length > 0) {
147
- let logs = pendingLogs;
148
- pendingLogs = [];
149
- for (let log of logs) {
150
- addLocalLog(log.log, log.type);
151
- }
152
- }
153
- return {
154
- classify: logClassifier,
155
- logClasses: new Map(logClasses.map(a => [a.id, a])),
156
- };
157
- });
158
- async function onLogClassesUpdated() {
159
- initLogClassifier.reset();
160
- await initLogClassifier();
161
- await ErrorLogControllerBase.recalculateNotifyState();
162
- }
163
-
164
- export class ErrorLogControllerBase {
165
- public async scanBlocks(): Promise<LogBlockInfo[]> {
166
- let files = await LOG_ARCHIVES().find("", { shallow: true, type: "files" });
167
- files = files.filter(a => a.endsWith(LOG_BLOCK_EXTENSION));
168
- let infos = files.map(getLogBlockInfo);
169
- if (pendingBlock) {
170
- infos.push({
171
- startTime: pendingBlock.startTime,
172
- endTime: pendingBlock.endTime,
173
- seqNum: pendingBlock.seqNum,
174
- count: pendingBlock.count,
175
- fatalCount: pendingBlock.fatalCount,
176
- errorCount: pendingBlock.errorCount,
177
- warnCount: pendingBlock.warnCount,
178
- infoCount: pendingBlock.infoCount,
179
- threadId: pendingBlock.threadId,
180
- machineId: pendingBlock.machineId,
181
- fileName: "",
182
- });
183
- }
184
- return infos;
185
- }
186
-
187
- public async readBlocks(config: {
188
- files: string[];
189
- }): Promise<Buffer[]> {
190
- let fileNames = config.files.filter(a =>
191
- a.endsWith(LOG_BLOCK_EXTENSION) && !(a.includes("/") || a.includes("\\") || a.includes(".."))
192
- || !a
193
- );
194
- let buffers: Buffer[] = [];
195
- async function runThread() {
196
- while (fileNames.length > 0) {
197
- let fileName = fileNames.pop()!;
198
- if (!fileName) {
199
- if (pendingBlock) {
200
- buffers.push((await logBlockToBuffer(pendingBlock)).buffer);
201
- }
202
- continue;
203
- }
204
- let buffer = await LOG_ARCHIVES().get(fileName);
205
- if (!buffer) continue;
206
- buffers.push(buffer);
207
- }
208
- }
209
- await Promise.all(list(16).map(runThread));
210
- return buffers;
211
- }
212
- public async readAllLiveBlocks(): Promise<Buffer[]> {
213
- let nodeIds = await getAllNodeIds();
214
- let blockBuffers: Buffer[] = [];
215
- await Promise.allSettled(nodeIds.map(async nodeId => {
216
- let controller = ErrorLogController.nodes[nodeId];
217
- let buffers = await timeoutToUndefinedSilent(5000, controller.readBlocks({ files: [""] }));
218
- if (!buffers) return;
219
- console.log(magenta(`Read live data from ${nodeId}`));
220
- blockBuffers.push(...buffers);
221
- }));
222
- return blockBuffers;
223
- }
224
-
225
- public async getClasses(): Promise<LogClass[]> {
226
- let logClassesBuffer = await LOG_ARCHIVES().get(LOG_CLASSES_PATH);
227
- let logClasses = logClassesFromString(logClassesBuffer?.toString() || "");
228
- return logClasses;
229
- }
230
-
231
- public async updateClass(logClass: LogClass | { id: string; action: "delete" }) {
232
- let logClasses = await this.getClasses();
233
- let existingIndex = logClasses.findIndex(a => a.id === logClass.id);
234
- if ("action" in logClass) {
235
- if (logClass.action === "delete") {
236
- if (existingIndex !== -1) {
237
- logClasses.splice(existingIndex, 1);
238
- }
239
- }
240
- } else {
241
- if (existingIndex !== -1) {
242
- logClasses[existingIndex] = logClass;
243
- } else {
244
- logClasses.push(logClass);
245
- }
246
- }
247
- let buffer = Buffer.from(logClassesToString(logClasses));
248
- await LOG_ARCHIVES().set(LOG_CLASSES_PATH, buffer);
249
- logErrors((async () => {
250
- await onLogClassesUpdated();
251
- // Notify ALL other nodes
252
- let nodeIdsToTest = await getAllNodeIds();
253
- for (let nodeId of nodeIdsToTest) {
254
- ignoreErrors(ErrorLogController.nodes[nodeId].onClassesUpdated());
255
- }
256
- })());
257
- }
258
-
259
- public async onClassesUpdated() {
260
- ignoreErrors(onLogClassesUpdated());
261
- }
262
-
263
- private static joinNotifies(notify: (LogIssueNotify | undefined)[]): LogIssueNotify {
264
- // If we don't need to merge, don't so callers can check for ===
265
- let finalNotify: LogIssueNotify | undefined = undefined;
266
- for (let next of notify) {
267
- if (!next) continue;
268
- if (!finalNotify) {
269
- finalNotify = next;
270
- continue;
271
- }
272
- if (next.oldestUnsuppressed && (!finalNotify.oldestUnsuppressed || next.oldestUnsuppressed.time < finalNotify.oldestUnsuppressed.time)) {
273
- if (!finalNotify.oldestUngrouped) {
274
- finalNotify = next;
275
- } else {
276
- finalNotify = { ...finalNotify, oldestUnsuppressed: next.oldestUnsuppressed };
277
- }
278
- }
279
- if (next.oldestUngrouped && (!finalNotify.oldestUngrouped || next.oldestUngrouped.time < finalNotify.oldestUngrouped.time)) {
280
- if (!finalNotify.oldestUnsuppressed) {
281
- finalNotify = next;
282
- } else {
283
- finalNotify = { ...finalNotify, oldestUngrouped: next.oldestUngrouped };
284
- }
285
- }
286
- }
287
- return finalNotify || undefined;
288
- }
289
-
290
- private static logIssueNotifyEquals(lhs: LogIssueNotify | undefined, rhs: LogIssueNotify | undefined) {
291
- return (
292
- lhs?.oldestUngrouped?.message === rhs?.oldestUngrouped?.message
293
- && lhs?.oldestUnsuppressed?.message === rhs?.oldestUnsuppressed?.message
294
- && lhs?.oldestUngrouped?.time === rhs?.oldestUngrouped?.time
295
- && lhs?.oldestUnsuppressed?.time === rhs?.oldestUnsuppressed?.time
296
- );
297
- }
298
-
299
- private static inMemoryNotify: LogIssueNotify | undefined;
300
- private static updateInMemoryNotify(notify: LogIssueNotify | undefined) {
301
- if (ErrorLogControllerBase.logIssueNotifyEquals(this.inMemoryNotify, notify)) {
302
- return;
303
- }
304
- this.inMemoryNotify = notify;
305
- for (let watcher of this.inMemoryNotifyWatchers) {
306
- ErrorLogController.nodes[watcher]
307
- .liveLogsIssueNotifyCallback(notify)
308
- .catch(() => {
309
- this.inMemoryNotifyWatchers.delete(watcher);
310
- });
311
- }
312
- logErrors(ErrorLogControllerBase.recalcAllNotify());
313
- }
314
- private static inMemoryNotifyWatchers = new Set<string>();
315
- public static onLocalLog(log: LogRaw, classObj: LogClass) {
316
- if (!logIssueNotifyTypes.includes(classObj.type)) return;
317
- let notify: LogIssueNotify | undefined;
318
- if (isUngroupedClass(classObj)) {
319
- notify = {
320
- oldestUnsuppressed: undefined,
321
- oldestUngrouped: log,
322
- };
323
- } else if (log.time > classObj.suppressUntil) {
324
- notify = {
325
- oldestUnsuppressed: log,
326
- oldestUngrouped: undefined,
327
- };
328
- }
329
- if (notify) {
330
- this.updateInMemoryNotify(this.joinNotifies([this.inMemoryNotify, notify]));
331
- }
332
- }
333
- public async internalWatchLiveLogs() {
334
- let watcher = SocketFunction.getCaller().nodeId;
335
- ErrorLogControllerBase.inMemoryNotifyWatchers.add(watcher);
336
- // Await, so the client gets it's own errors
337
- await ErrorLogController.nodes[watcher].liveLogsIssueNotifyCallback(ErrorLogControllerBase.inMemoryNotify);
338
- }
339
-
340
- private static liveLogsIssueNotify = new Map<string, LogIssueNotify | undefined>();
341
- public async liveLogsIssueNotifyCallback(notify: LogIssueNotify | undefined) {
342
- let caller = SocketFunction.getCaller();
343
- let prev = ErrorLogControllerBase.liveLogsIssueNotify.get(caller.nodeId);
344
- if (ErrorLogControllerBase.logIssueNotifyEquals(prev, notify)) {
345
- return;
346
- }
347
- // Don't log when we receive it, otherwise errors show up on all servers which is very confusing and annoying!
348
- //console.log(magenta(`Received log notify from ${caller.nodeId} for ${notify?.oldestUngrouped?.message}`));
349
- ErrorLogControllerBase.liveLogsIssueNotify.set(caller.nodeId, notify);
350
- logErrors(ErrorLogControllerBase.recalcAllNotify());
351
- }
352
-
353
-
354
- private static allNotify: LogIssueNotify | undefined;
355
- private static allWatchers = new Set<string>();
356
- private static diskNotify: LogIssueNotify | undefined;
357
- private static async recalcAllNotify() {
358
- if (!ErrorLogControllerBase.watchingLogs) return;
359
- let notifies = [
360
- ...Array.from(this.liveLogsIssueNotify.values()),
361
- this.inMemoryNotify,
362
- this.diskNotify
363
- ];
364
- let { classify } = await initLogClassifier();
365
- // Reclassify based on the most recent classifier
366
- // - This only removes classifications, it doesn't add them back. Each node should be watching
367
- // classifiers and rerunning on their data to add classifications back.
368
- notifies = notifies.map(x => {
369
- if (x?.oldestUngrouped && !isUngroupedClass(classify(x.oldestUngrouped, "error"))) {
370
- x = { ...x, oldestUngrouped: undefined };
371
- }
372
- if (x?.oldestUnsuppressed && x.oldestUnsuppressed.time < classify(x.oldestUnsuppressed, "error").suppressUntil) {
373
- x = { ...x, oldestUnsuppressed: undefined };
374
- }
375
- return x;
376
- });
377
- let newNotify = this.joinNotifies(notifies);
378
- if (this.logIssueNotifyEquals(newNotify, this.allNotify)) {
379
- return;
380
- }
381
- this.allNotify = newNotify;
382
- for (let watcher of this.allWatchers) {
383
- ErrorLogController.nodes[watcher].logsIssueNotifyCallback(newNotify)
384
- .catch(() => {
385
- this.allWatchers.delete(watcher);
386
- });
387
- }
388
- }
389
-
390
- private static watchForNewNodes = lazy(() => {
391
- watchDeltaNodeIds(async ({ newNodeIds }) => {
392
- for (let nodeId of newNodeIds) {
393
- if (nodeId === getOwnNodeId()) continue;
394
- if (!ErrorLogControllerBase.liveLogsIssueNotify.has(nodeId)) {
395
- ErrorLogControllerBase.liveLogsIssueNotify.set(nodeId, undefined);
396
- }
397
- ignoreErrors(ErrorLogController.nodes[nodeId].internalWatchLiveLogs());
398
- SocketFunction.onNextDisconnect(nodeId, () => {
399
- // HACK: Remove it, to prevent notifications from errors which we never written to disk.
400
- // - The only other alternative is to get this error in the UI... somehow, and onto disk,
401
- // but not if the other node DOES write it to disk, which... gets complicated.
402
- ErrorLogControllerBase.liveLogsIssueNotify.delete(nodeId);
403
- });
404
- }
405
- });
406
- });
407
-
408
- private static watchingLogs = false;
409
- public static async recalculateNotifyState() {
410
- ErrorLogControllerBase.watchForNewNodes();
411
-
412
- // Watch all nodes again, in case they disappear
413
- for (let node of await getAllNodeIds()) {
414
- if (node === getOwnNodeId()) continue;
415
- ignoreErrors(ErrorLogController.nodes[node].internalWatchLiveLogs());
416
- }
417
-
418
- let threshold = Date.now() - NOTIFY_HISTORY;
419
-
420
- let { classify, logClasses } = await initLogClassifier();
421
- function summarizeBlock(block: LogBlock | undefined) {
422
- if (!block) return undefined;
423
- let notify: LogIssueNotify | undefined = undefined;
424
- for (let classSummary of Object.values(block.classes)) {
425
- for (let logRaw of classSummary.examples) {
426
- if (logRaw.time < threshold) continue;
427
- let classObj = classify(logRaw, classSummary.logClassType || logClasses.get(classSummary.logClassId)?.type || "error");
428
- if (!logIssueNotifyTypes.includes(classObj.type)) continue;
429
- if (isUngroupedClass(classObj)) {
430
- if (!notify || !notify.oldestUngrouped || classSummary.firstTime < notify.oldestUngrouped.time) {
431
- if (!notify) notify = { oldestUnsuppressed: undefined, oldestUngrouped: undefined };
432
- if (!notify.oldestUngrouped || notify.oldestUngrouped.time > classSummary.firstTime) {
433
- notify.oldestUngrouped = classSummary.examples[0];
434
- }
435
- }
436
- } else if (logRaw.time > classObj.suppressUntil) {
437
- if (!notify || !notify.oldestUnsuppressed || classSummary.firstTime < notify.oldestUnsuppressed.time) {
438
- if (!notify) notify = { oldestUnsuppressed: undefined, oldestUngrouped: undefined };
439
- if (!notify.oldestUnsuppressed || notify.oldestUnsuppressed.time > classSummary.firstTime) {
440
- notify.oldestUnsuppressed = classSummary.examples[0];
441
- }
442
- }
443
- }
444
- }
445
- }
446
- return notify;
447
- }
448
-
449
- // Recalculate in memory state
450
- this.updateInMemoryNotify(summarizeBlock(pendingBlock));
451
-
452
- if (ErrorLogControllerBase.watchingLogs) {
453
- let blockInfos = await new ErrorLogControllerBase().scanBlocks();
454
- blockInfos = blockInfos.filter(a => a.startTime > threshold);
455
- let blocks = await new ErrorLogControllerBase().readBlocks({ files: blockInfos.map(a => a.fileName) });
456
- let blockObjs = await Promise.all(blocks.map(x => logBlockFromBuffer(x)));
457
- let notify = this.joinNotifies(blockObjs.map(summarizeBlock));
458
- this.diskNotify = notify;
459
- }
460
-
461
- await this.recalcAllNotify();
462
- }
463
-
464
- // NOTE: We never unwatch. Watching for LogIssueNotify should be fairly cheap, so this is fine
465
- public static startLogWatch = lazy(async () => {
466
- ErrorLogControllerBase.watchingLogs = true;
467
- await this.recalculateNotifyState();
468
- });
469
-
470
- public async watchLogs(callbackClass?: ControllerPick<typeof ErrorLogController, "logsIssueNotifyCallback">) {
471
- let self = ErrorLogControllerBase;
472
- self.watchingLogs = true;
473
-
474
- let caller = SocketFunction.getCaller();
475
- self.allWatchers.add(caller.nodeId);
476
-
477
- await self.startLogWatch();
478
-
479
- let client = callbackClass ? SocketFunction.rehydrateSocketCaller(callbackClass) : ErrorLogController;
480
- await client.nodes[caller.nodeId].logsIssueNotifyCallback(self.allNotify);
481
- }
482
-
483
- public static logIssueNotifyWatchers = new Set<(notify: LogIssueNotify | undefined) => void>();
484
- public async logsIssueNotifyCallback(notify: LogIssueNotify | undefined) {
485
- onLogIssueNotify(notify);
486
- }
487
- public async unwatchLogs() {
488
- let self = ErrorLogControllerBase;
489
- let caller = SocketFunction.getCaller();
490
- self.allWatchers.delete(caller.nodeId);
491
- }
492
- }
493
-
494
- function onLogIssueNotify(notify: LogIssueNotify | undefined) {
495
- for (let watcher of ErrorLogControllerBase.logIssueNotifyWatchers) {
496
- try {
497
- watcher(notify);
498
- } catch (e) {
499
- logErrors(e);
500
- }
501
- }
502
- }
503
- export function watchLogIssueNotify(callback: (notify: LogIssueNotify | undefined) => void) {
504
- ErrorLogControllerBase.logIssueNotifyWatchers.add(callback);
505
- }
506
-
507
- export type LogIssueNotify = undefined | {
508
- // NOTE: Storing the oldest requires a lot fewer notifications, and is probably what we want
509
- // anyways (it tells us when the problem started).
510
- oldestUnsuppressed: LogRaw | undefined;
511
- oldestUngrouped: LogRaw | undefined;
512
- // NOTE: Count is too hard to count accurately, due to live values being written to disk, etc.
513
- }
514
-
515
- const ErrorLogController = SocketFunction.register(
516
- "ErrorLogController-219a7c2e-2da6-4753-9e0e-1f42dc5f6970",
517
- new ErrorLogControllerBase(),
518
- () => ({
519
- scanBlocks: {},
520
- readBlocks: {},
521
- readAllLiveBlocks: {},
522
- getClasses: {},
523
- updateClass: {},
524
- onClassesUpdated: {},
525
-
526
- watchLogs: {},
527
- unwatchLogs: {},
528
- liveLogsIssueNotifyCallback: {},
529
- logsIssueNotifyCallback: {},
530
- internalWatchLiveLogs: {},
531
- }),
532
- () => ({
533
- hooks: [requiresNetworkTrustHook],
534
- }),
535
- );