querysub 0.326.0 → 0.328.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 (42) hide show
  1. package/package.json +3 -4
  2. package/src/-a-archives/archivesBackBlaze.ts +20 -0
  3. package/src/-a-archives/archivesDisk.ts +5 -5
  4. package/src/-a-archives/archivesLimitedCache.ts +118 -7
  5. package/src/-a-archives/archivesPrivateFileSystem.ts +3 -0
  6. package/src/-g-core-values/NodeCapabilities.ts +26 -11
  7. package/src/0-path-value-core/auditLogs.ts +4 -2
  8. package/src/2-proxy/PathValueProxyWatcher.ts +3 -0
  9. package/src/3-path-functions/PathFunctionRunner.ts +2 -2
  10. package/src/4-querysub/Querysub.ts +1 -1
  11. package/src/5-diagnostics/GenericFormat.tsx +2 -2
  12. package/src/deployManager/machineApplyMainCode.ts +10 -8
  13. package/src/deployManager/machineSchema.ts +4 -3
  14. package/src/deployManager/setupMachineMain.ts +3 -2
  15. package/src/diagnostics/logs/FastArchiveAppendable.ts +85 -59
  16. package/src/diagnostics/logs/FastArchiveController.ts +5 -2
  17. package/src/diagnostics/logs/FastArchiveViewer.tsx +222 -51
  18. package/src/diagnostics/logs/LogViewer2.tsx +83 -35
  19. package/src/diagnostics/logs/TimeRangeSelector.tsx +8 -0
  20. package/src/diagnostics/logs/diskLogGlobalContext.ts +3 -3
  21. package/src/diagnostics/logs/diskLogger.ts +70 -23
  22. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +111 -82
  23. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +37 -3
  24. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +52 -22
  25. package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +8 -0
  26. package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +198 -52
  27. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +3 -2
  28. package/src/diagnostics/managementPages.tsx +5 -0
  29. package/src/email_ims_notifications/discord.tsx +203 -0
  30. package/src/fs.ts +9 -0
  31. package/src/functional/SocketChannel.ts +9 -0
  32. package/src/functional/throttleRender.ts +134 -0
  33. package/src/library-components/ATag.tsx +2 -2
  34. package/src/library-components/SyncedController.ts +5 -3
  35. package/src/misc.ts +13 -0
  36. package/src/misc2.ts +54 -0
  37. package/src/user-implementation/SecurityPage.tsx +11 -5
  38. package/src/user-implementation/userData.ts +31 -16
  39. package/testEntry2.ts +14 -5
  40. package/src/user-implementation/setEmailKey.ts +0 -25
  41. /package/src/{email → email_ims_notifications}/postmark.tsx +0 -0
  42. /package/src/{email → email_ims_notifications}/sendgrid.tsx +0 -0
@@ -4,7 +4,7 @@ import { css } from "../../4-dom/css";
4
4
  import { URLParam } from "../../library-components/URLParam";
5
5
  import { InputLabelURL } from "../../library-components/InputLabel";
6
6
  import { Button } from "../../library-components/Button";
7
- import { formatDateTime, formatNiceDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
7
+ import { formatDateTime, formatDateTimeDetailed, formatNiceDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
8
8
  import { TimeRangeSelector, endTimeParam, getTimeRange, startTimeParam } from "./TimeRangeSelector";
9
9
  import { FastArchiveAppendable } from "./FastArchiveAppendable";
10
10
  import { t } from "../../2-proxy/schema2";
@@ -13,8 +13,8 @@ import { logErrors } from "../../errors";
13
13
  import { batchFunction, runInSerial } from "socket-function/src/batching";
14
14
  import { Querysub } from "../../4-querysub/QuerysubController";
15
15
  import { sort, timeInDay, timeInHour } from "socket-function/src/misc";
16
- import { FastArchiveViewer, filterParam } from "./FastArchiveViewer";
17
- import { LogDatum, getLoggers, LOG_LIMIT_FLAG } from "./diskLogger";
16
+ import { FastArchiveViewer, cacheBustParam, filterParam } from "./FastArchiveViewer";
17
+ import { LogDatum, getLoggers, LOG_LIMIT_FLAG, LOG_LINE_LIMIT_FLAG } from "./diskLogger";
18
18
  import { ColumnType, Table, TableType } from "../../5-diagnostics/Table";
19
19
  import { formatDateJSX } from "../../misc/formatJSX";
20
20
  import { InputPicker } from "../../library-components/InputPicker";
@@ -24,8 +24,10 @@ import { ObjectDisplay } from "./ObjectDisplay";
24
24
  import { endTime } from "../misc-pages/archiveViewerShared";
25
25
  import { ErrorSuppressionUI } from "./errorNotifications/ErrorSuppressionUI";
26
26
  import { FileMetadata } from "./FastArchiveController";
27
- import { SuppressionListController, getSuppressEntryChecker, getSuppressionFull } from "./errorNotifications/ErrorNotificationController";
27
+ import { RecentErrorsController, SuppressionListController, getSuppressEntryChecker, getSuppressionFull } from "./errorNotifications/ErrorNotificationController";
28
28
  import { SocketFunction } from "socket-function/SocketFunction";
29
+ import { throttleRender } from "../../functional/throttleRender";
30
+ import { isNode } from "typesafecss";
29
31
 
30
32
  const RENDER_INTERVAL = 1000;
31
33
 
@@ -36,6 +38,7 @@ const enableInfosURL = new URLParam("enableInfos", true);
36
38
  const enableWarningsURL = new URLParam("enableWarnings", true);
37
39
  const enableErrorsURL = new URLParam("enableErrors", true);
38
40
  const selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
41
+ const useRelativeTimeURL = new URLParam("useRelativeTime", true);
39
42
 
40
43
  export const errorNotifyToggleURL = new URLParam("errorNotifyToggle", false);
41
44
 
@@ -70,10 +73,12 @@ export class LogViewer2 extends qreact.Component {
70
73
  private fastArchiveViewer: FastArchiveViewer<LogDatum> | undefined = undefined;
71
74
 
72
75
  rerun() {
76
+ cacheBustParam.value = Date.now();
73
77
  void this.fastArchiveViewer?.handleDownload();
74
78
  }
75
79
 
76
80
  render() {
81
+ if (throttleRender({ key: "LogViewer2", frameDelay: 30 })) return undefined;
77
82
 
78
83
  this.state.datumsSeqNum;
79
84
 
@@ -119,11 +124,17 @@ export class LogViewer2 extends qreact.Component {
119
124
  let now = Date.now();
120
125
  startTimeParam.value = now - timeInDay * 7;
121
126
  endTimeParam.value = now + timeInHour * 2;
122
- filterParam.value = "";
123
127
  }
124
128
  this.rerun();
125
129
  }}
126
130
  />
131
+ {errorNotifyToggleURL.value && <div className={css.hbox(10)}>
132
+ <Button onClick={() => {
133
+ void RecentErrorsController(SocketFunction.browserNodeId()).raiseTestError.promise("Test error notification");
134
+ }}>
135
+ Test Error Notification (won't rerun search, but it should show up in the title)
136
+ </Button>
137
+ </div>}
127
138
  </div>
128
139
 
129
140
  {!errorNotifyToggleURL.value && <div className={css.hbox(10)}>
@@ -144,7 +155,8 @@ export class LogViewer2 extends qreact.Component {
144
155
  <FastArchiveViewer
145
156
  ref2={x => this.fastArchiveViewer = x}
146
157
  fastArchives={logs}
147
- onStart={() => {
158
+ runOnLoad={errorNotifyToggleURL.value}
159
+ onStart={async () => {
148
160
  this.datumCount = 0;
149
161
  this.notMatchedCount = 0;
150
162
  this.errors = 0;
@@ -167,6 +179,9 @@ export class LogViewer2 extends qreact.Component {
167
179
  });
168
180
  }
169
181
  })();
182
+ // ALWAYS update it, as the synchronous one might be out of date, and if we use an outdated one extra errors show up.
183
+ suppressionList = await suppressionController.getSuppressionList.promise();
184
+
170
185
  }}
171
186
  getWantData={async (file) => {
172
187
  if (!hasErrorNotifyToggle) return undefined;
@@ -238,6 +253,13 @@ export class LogViewer2 extends qreact.Component {
238
253
  anyLimited = true;
239
254
  }
240
255
  }
256
+ let lineLimited = false;
257
+ for (let i = 0; i < Math.min(1000, this.datums.length); i++) {
258
+ let datum = this.datums[i];
259
+ if (datum[LOG_LINE_LIMIT_FLAG]) {
260
+ lineLimited = true;
261
+ }
262
+ }
241
263
  let selectedFields: string[] = [];
242
264
  for (let field of Object.keys(defaultSelectedFields)) {
243
265
  if (atomic(selectedFieldsURL.value[field]) === undefined) {
@@ -261,15 +283,25 @@ export class LogViewer2 extends qreact.Component {
261
283
  if (anyLimited) {
262
284
  columns[LOG_LIMIT_FLAG] = {
263
285
  title: "Limited",
264
- formatter: x => x && <div className={css.hsl(0, 50, 50).colorhsl(0, 50, 95).boldStyle.pad2(10).ellipsis}>
265
- Log Line Throttled
286
+ formatter: x => x && <div className={css.hsl(0, 50, 50).colorhsl(0, 50, 95).boldStyle.pad2(10).ellipsis}
287
+ title="File throttled (other line may be lost lost!)">
288
+ FILE throttled
289
+ </div> || undefined
290
+ };
291
+ }
292
+ if (lineLimited) {
293
+ columns[LOG_LINE_LIMIT_FLAG] = {
294
+ title: "Limited",
295
+ formatter: x => x && <div className={css.hsl(60, 50, 50).colorhsl(60, 50, 95).boldStyle.pad2(10).ellipsis}
296
+ title="Line throttled (no lines lost, just count for this line is lower than the actual count)">
297
+ Line throttled
266
298
  </div> || undefined
267
299
  };
268
300
  }
269
301
  for (let field of selectedFields) {
270
302
  let column: ColumnType<unknown, LogDatum> = {};
271
303
  if (field === "time") {
272
- column.formatter = (x: unknown) => formatDateTime(Number(x));
304
+ column.formatter = (x: unknown) => useRelativeTimeURL.value ? formatDateJSX(Number(x)) : <span title={formatDateTimeDetailed(Number(x))}>{formatDateTime(Number(x))}</span>;
273
305
  }
274
306
  if (!column.formatter) {
275
307
  column.formatter = (x: unknown) => <ObjectDisplay value={x} />;
@@ -298,38 +330,54 @@ export class LogViewer2 extends qreact.Component {
298
330
  }
299
331
  };
300
332
  return <>
301
- <InputPicker
302
- label={<div className={css.hbox(10)}>
303
- <div className={css.flexShrink0}>
304
- Selected Fields
305
- </div>
306
- <Button onClick={() => {
333
+ <div className={css.hbox(10)}>
334
+ <InputPicker
335
+ label={<div className={css.hbox(10)}>
336
+ <div className={css.flexShrink0}>
337
+ Selected Fields
338
+ </div>
339
+ <Button onClick={() => {
340
+ let newValues = { ...selectedFieldsURL.value };
341
+ for (let key of Object.keys(newValues)) {
342
+ newValues[key] = key in defaultSelectedFields;
343
+ }
344
+ selectedFieldsURL.value = newValues;
345
+ }}>
346
+ Reset
347
+ </Button>
348
+ </div>}
349
+ picked={selectedFields}
350
+ options={Array.from(fieldNames).map(x => ({ value: x, label: x }))}
351
+ addPicked={x => {
307
352
  let newValues = { ...selectedFieldsURL.value };
308
- for (let key of Object.keys(newValues)) {
309
- newValues[key] = key in defaultSelectedFields;
310
- }
353
+ newValues[x] = true;
311
354
  selectedFieldsURL.value = newValues;
312
- }}>
313
- Reset
314
- </Button>
315
- </div>}
316
- picked={selectedFields}
317
- options={Array.from(fieldNames).map(x => ({ value: x, label: x }))}
318
- addPicked={x => {
319
- let newValues = { ...selectedFieldsURL.value };
320
- newValues[x] = true;
321
- selectedFieldsURL.value = newValues;
322
- }}
323
- removePicked={x => {
324
- let newValues = { ...selectedFieldsURL.value };
325
- newValues[x] = false;
326
- selectedFieldsURL.value = newValues;
327
- }}
328
- />
355
+ }}
356
+ removePicked={x => {
357
+ let newValues = { ...selectedFieldsURL.value };
358
+ newValues[x] = false;
359
+ selectedFieldsURL.value = newValues;
360
+ }}
361
+ />
362
+ <InputLabelURL
363
+ label="Use Relative Time"
364
+ checkbox
365
+ url={useRelativeTimeURL}
366
+ onChangeValue={() => {
367
+ Querysub.commit(() => {
368
+ this.state.datumsSeqNum++;
369
+ });
370
+ }}
371
+ />
372
+ </div>
373
+ {this.datums.length === 0 && <div className={css.hsl(40, 50, 50).colorhsl(60, 50, 100).boldStyle.pad2(10).ellipsis}>
374
+ No logs matched, either increase the time range or decrease the filter specificity.
375
+ </div>}
329
376
  <Table
330
377
  rows={this.datums}
331
378
  columns={columns}
332
379
  lineLimit={4}
380
+ initialLimit={10}
333
381
  characterLimit={400}
334
382
  getRowAttributes={row => {
335
383
  let hue = -1;
@@ -82,6 +82,14 @@ export class TimeRangeSelector extends qreact.Component {
82
82
  >
83
83
  Set to future data
84
84
  </Button>
85
+ <Button
86
+ hue={110} onClick={() => {
87
+ startTimeParam.value = now - timeInHour;
88
+ endTimeParam.value = now + timeInHour * 2;
89
+ }}
90
+ >
91
+ Set to last hour
92
+ </Button>
85
93
  <Button
86
94
  hue={110} onClick={() => {
87
95
  startTimeParam.value = now - timeInDay;
@@ -1,7 +1,7 @@
1
1
  import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { lazy } from "socket-function/src/caching";
3
3
  import { getExternalIP } from "socket-function/src/networking";
4
- import { decodeNodeId } from "../../-a-auth/certs";
4
+ import { decodeNodeId, getOwnMachineId, getOwnThreadId } from "../../-a-auth/certs";
5
5
  import { getOwnNodeId } from "../../-f-node-discovery/NodeDiscovery";
6
6
  import { logErrors } from "../../errors";
7
7
  import { addGlobalContext } from "./diskLogger";
@@ -12,9 +12,9 @@ export function addBuiltInContext() {
12
12
  let nodeId = getOwnNodeId();
13
13
  let nodeParts = decodeNodeId(nodeId);
14
14
  return {
15
- __machineId: nodeParts?.machineId,
15
+ __machineId: getOwnMachineId(),
16
16
  __mountId: SocketFunction.mountedNodeId,
17
- __threadId: nodeParts?.threadId,
17
+ __threadId: getOwnThreadId(),
18
18
  __port: nodeParts?.port,
19
19
  __nodeId: nodeId,
20
20
  __entry: process.argv[1],
@@ -6,6 +6,8 @@ import { timeInMinute } from "socket-function/src/misc";
6
6
  import { formatTime } from "socket-function/src/formatting/format";
7
7
  import { addEpsilons } from "../../bits";
8
8
  import { FileMetadata } from "./FastArchiveController";
9
+ import { getPathStr2 } from "../../path";
10
+ import { isPublic } from "../../config";
9
11
  // IMPORTANT! We can't have any real imports here, because we are depended on so early in startup!
10
12
 
11
13
  if (isNode()) {
@@ -35,7 +37,25 @@ export type LogDatum = Record<string, unknown> & {
35
37
  /** Dynamically set when matching recent errors only. */
36
38
  __matchedOutdatedSuppressionKey?: string;
37
39
  };
40
+ export function getLogHash(obj: LogDatum) {
41
+ return getPathStr2(obj.__threadId || "", obj.time.toString());
42
+ }
43
+ export function getLogFile(obj: LogDatum) {
44
+ let logType = obj.param0 || "";
45
+ if (obj.__FILE__) {
46
+ logType = String(obj.__FILE__);
47
+ }
48
+ if (obj[LOG_LINE_LIMIT_ID]) {
49
+ logType += "::" + String(obj[LOG_LINE_LIMIT_ID]);
50
+ }
51
+ return logType;
52
+
53
+ }
38
54
  export const LOG_LIMIT_FLAG = String.fromCharCode(44533) + "LOGS_LIMITED_FLAG-9277640b-d709-4591-ab08-2bb29bbb94f4";
55
+ export const LOG_LINE_LIMIT_FLAG = String.fromCharCode(44534) + "LOGS_LINE_LIMIT_FLAG-dd50ab1f-3021-45e3-82fc-d2702c7a64c8";
56
+
57
+ /** If this key exists in the logged object, as in a key in one of the objects logged, then we will use the value of it as the limit ID. This is useful as it allows us to either override a limit or limit something independently from other logs in the file. */
58
+ export const LOG_LINE_LIMIT_ID = "LIMIT_LINE_ID";
39
59
 
40
60
  export const getLoggers = lazy(function () {
41
61
  const { FastArchiveAppendable } = require("./FastArchiveAppendable") as typeof import("./FastArchiveAppendable");
@@ -52,6 +72,10 @@ export const getLoggers = lazy(function () {
52
72
  errorLogs: new FastArchiveAppendable<LogDatum>("logs-error/"),
53
73
  };
54
74
  });
75
+ setImmediate(() => {
76
+ // If we don't import it at all, then it doesn't work client-side.
77
+ require("./FastArchiveAppendable") as typeof import("./FastArchiveAppendable");
78
+ });
55
79
  const getNotifyErrors = lazy(function () {
56
80
  const { notifyWatchersOfError: notifyErrors } = require("./errorNotifications/ErrorNotificationController") as typeof import("./errorNotifications/ErrorNotificationController");
57
81
  if (typeof notifyErrors !== "function") {
@@ -88,6 +112,8 @@ let logLimitLookup: {
88
112
 
89
113
  const LIMIT_PERIOD = timeInMinute * 15;
90
114
  const LIMIT_THRESHOLD = 1000;
115
+ const WARN_LIMIT = 100;
116
+ const ERROR_LIMIT = 100;
91
117
 
92
118
  const logDiskDontShim = logDisk;
93
119
  /** NOTE: Calling this directly means we lose __FILE__ tracking. But... that's probably fine... */
@@ -104,6 +130,11 @@ export function logDisk(type: "log" | "warn" | "info" | "error", ...args: unknow
104
130
  if (logObj.__FILE__) {
105
131
  logType = String(logObj.__FILE__);
106
132
  }
133
+ let hasLineLimit = false;
134
+ if (logObj[LOG_LINE_LIMIT_ID]) {
135
+ logType += "::" + String(logObj[LOG_LINE_LIMIT_ID]);
136
+ hasLineLimit = true;
137
+ }
107
138
 
108
139
  if (logLimitLookup) {
109
140
  if (logObj.time > logLimitLookup.resetTime) {
@@ -120,32 +151,47 @@ export function logDisk(type: "log" | "warn" | "info" | "error", ...args: unknow
120
151
  let count = logLimitLookup.counts.get(logType) || 0;
121
152
  count++;
122
153
  logLimitLookup.counts.set(logType, count);
123
- if (count > LIMIT_THRESHOLD) {
154
+ let limit = LIMIT_THRESHOLD;
155
+ if (type === "warn") {
156
+ limit = WARN_LIMIT;
157
+ } else if (type === "error") {
158
+ limit = ERROR_LIMIT;
159
+ }
160
+ if (count > limit) {
124
161
  let timeUntilReset = logLimitLookup.resetTime - logObj.time;
125
- process.stdout.write(`Log type hit limit, not writing log type to disk for ~${formatTime(timeUntilReset)}: ${logType}\n`);
162
+ if (hasLineLimit) {
163
+ process.stdout.write(`Log type hit limit, not writing log type to disk for ~${formatTime(timeUntilReset)}: ${logType}\n`);
164
+ }
126
165
  return;
127
166
  }
128
- if (count === LIMIT_THRESHOLD) {
129
- logObj[LOG_LIMIT_FLAG] = true;
167
+ if (count >= limit) {
168
+ if (hasLineLimit) {
169
+ logObj[LOG_LINE_LIMIT_FLAG] = true;
170
+ } else {
171
+ logObj[LOG_LIMIT_FLAG] = true;
172
+ }
130
173
  }
131
174
 
132
- let loggers = startupDone ? getLoggers() : undefined;
133
- if (!loggers) {
134
- getLoggers.reset();
135
- setImmediate(() => {
136
- logDiskDontShim(type, ...args);
137
- });
138
- return;
139
- }
140
- const { logLogs, warnLogs, infoLogs, errorLogs } = loggers;
141
- if (type === "log") {
142
- logLogs.append(logObj);
143
- } else if (type === "warn") {
144
- warnLogs.append(logObj);
145
- } else if (type === "info") {
146
- infoLogs.append(logObj);
147
- } else {
148
- errorLogs.append(logObj);
175
+ // We don't want developer errors clogging up the error logs. However, they can still notify errors, Because this will only notify nodes that are able to access us (It uses a reverse connection scheme, so instead of talking to nodes that we can access, we only talk to nodes that can access us), Which will mean it will only notify for local services, so the developer still gets error notifications, But our errors won't be spread to all developers. BUT, we will still watch global errors, because we can contact the global server, so developers will still get errors about production issues, even while developing!
176
+ if (isPublic()) {
177
+ let loggers = startupDone ? getLoggers() : undefined;
178
+ if (!loggers) {
179
+ getLoggers.reset();
180
+ setImmediate(() => {
181
+ logDiskDontShim(type, ...args);
182
+ });
183
+ return;
184
+ }
185
+ const { logLogs, warnLogs, infoLogs, errorLogs } = loggers;
186
+ if (type === "log") {
187
+ logLogs.append(logObj);
188
+ } else if (type === "warn") {
189
+ warnLogs.append(logObj);
190
+ } else if (type === "info") {
191
+ infoLogs.append(logObj);
192
+ } else {
193
+ errorLogs.append(logObj);
194
+ }
149
195
  }
150
196
 
151
197
  if (type === "warn" || type === "error") {
@@ -162,12 +208,12 @@ let lastLogTime = 0;
162
208
 
163
209
  function packageLogObj(type: string, args: unknown[]): LogDatum {
164
210
  let now = Date.now();
165
- if (now < lastLogTime) {
211
+ if (now <= lastLogTime) {
166
212
  now = addEpsilons(lastLogTime, 1);
167
213
  }
168
214
  lastLogTime = now;
169
215
  let logObj: LogDatum = {
170
- time: now,
216
+ time: 0,
171
217
  __LOG_TYPE: type,
172
218
  };
173
219
  for (let part of globalContextParts) {
@@ -184,5 +230,6 @@ function packageLogObj(type: string, args: unknown[]): LogDatum {
184
230
  stringCount++;
185
231
  }
186
232
  }
233
+ logObj.time = now;
187
234
  return logObj;
188
235
  }