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
@@ -0,0 +1,225 @@
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { qreact } from "../../../4-dom/qreact";
3
+ import { NOT_AN_ERROR_EXPIRE_TIME, RecentErrorsController, SuppressionEntry, SuppressionListController, getSuppressEntryChecker } from "./ErrorNotificationController";
4
+ import { t } from "../../../2-proxy/schema2";
5
+ import { css } from "../../../4-dom/css";
6
+ import { InputLabel } from "../../../library-components/InputLabel";
7
+ import { Button } from "../../../library-components/Button";
8
+ import { Querysub } from "../../../4-querysub/QuerysubController";
9
+ import { nextId, sort, timeInDay } from "socket-function/src/misc";
10
+ import { formatNumber, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
11
+ import { formatDateJSX } from "../../../misc/formatJSX";
12
+ import { LogDatum } from "../diskLogger";
13
+
14
+ export class ErrorSuppressionUI extends qreact.Component<{
15
+ dataSeqNum: number;
16
+ suppressionCounts: Map<string, number>;
17
+ expiredSuppressionCounts: Map<string, number>;
18
+ datums: LogDatum[];
19
+ rerunFilters: () => void;
20
+ }> {
21
+ state = t.state({
22
+ matchedInput: t.string(""),
23
+ });
24
+
25
+ private calculatePreviewMatchCount(pattern: string): number {
26
+ if (!pattern.trim()) return 0;
27
+
28
+ try {
29
+ let entry: SuppressionEntry = {
30
+ key: "preview",
31
+ match: pattern,
32
+ comment: "",
33
+ lastUpdateTime: Date.now(),
34
+ expiresAt: Date.now() + timeInDay,
35
+ };
36
+ let checker = getSuppressEntryChecker(entry);
37
+
38
+ let matchCount = 0;
39
+ for (let datum of this.props.datums.slice(0, 1000)) {
40
+ let datumStr = JSON.stringify(datum);
41
+ let buffer = Buffer.from(datumStr);
42
+ if (checker.fnc(buffer, 0, buffer.length)) {
43
+ matchCount++;
44
+ }
45
+ }
46
+ return matchCount;
47
+ } catch (error) {
48
+ return 0;
49
+ }
50
+ }
51
+
52
+ public render() {
53
+ this.props.dataSeqNum;
54
+ const controller = SuppressionListController(SocketFunction.browserNodeId());
55
+ const entries = (controller.getSuppressionList() || []);
56
+ sort(entries, x => -x.lastUpdateTime);
57
+ sort(entries, x => -(this.props.suppressionCounts.get(x.key) ? 1 : 0));
58
+ sort(entries, x => -(this.props.expiredSuppressionCounts.get(x.key) ? 1 : 0));
59
+
60
+ const previewMatchCount = this.calculatePreviewMatchCount(this.state.matchedInput);
61
+
62
+ return <div className={css.vbox(16).pad2(16).fillWidth.bord2(0, 0, 50, 5).hsl(0, 0, 80)}>
63
+ <h2 className={css.fontSize(18).marginTop(0)}>Error Suppression List</h2>
64
+
65
+ <div className={css.hbox(8).fillWidth}>
66
+ <InputLabel
67
+ label="Add Match"
68
+ value={this.state.matchedInput}
69
+ hot
70
+ onChangeValue={(value: string) => this.state.matchedInput = value}
71
+ onKeyDown={e => {
72
+ if (e.key === "Enter") {
73
+ let value = e.currentTarget.value;
74
+ e.currentTarget.value = "";
75
+ void Querysub.onCommitFinished(async () => {
76
+ await controller.setSuppressionEntry.promise({
77
+ key: nextId(),
78
+ match: value,
79
+ comment: "",
80
+ lastUpdateTime: Date.now(),
81
+ expiresAt: Date.now() + timeInDay,
82
+ });
83
+ if (!e.shiftKey) {
84
+ Querysub.commit(() => {
85
+ this.props.rerunFilters();
86
+ });
87
+ }
88
+ });
89
+ }
90
+ }}
91
+ fillWidth
92
+ placeholder="* is supported as a wildcard (enter to submit and refresh, shift+enter to submit and do not refresh)"
93
+ />
94
+
95
+ {this.state.matchedInput.trim() && <span className={css.opacity(0.6).fontSize(12).flexShrink0}>
96
+ matches {formatNumber(previewMatchCount)} (in top 1000)
97
+ </span>}
98
+ <Button onClick={() => {
99
+ let value = this.state.matchedInput;
100
+ this.state.matchedInput = "";
101
+ void Querysub.onCommitFinished(async () => {
102
+ await controller.setSuppressionEntry.promise({
103
+ key: nextId(),
104
+ match: value,
105
+ comment: "",
106
+ lastUpdateTime: Date.now(),
107
+ expiresAt: NOT_AN_ERROR_EXPIRE_TIME,
108
+ });
109
+ Querysub.commit(() => {
110
+ this.props.rerunFilters();
111
+ });
112
+ });
113
+ }}>
114
+ Not an error
115
+ </Button>
116
+ <Button
117
+ onClick={() => {
118
+ let value = this.state.matchedInput;
119
+ this.state.matchedInput = "";
120
+ void Querysub.onCommitFinished(async () => {
121
+ await controller.setSuppressionEntry.promise({
122
+ key: nextId(),
123
+ match: value,
124
+ comment: "",
125
+ lastUpdateTime: Date.now(),
126
+ expiresAt: Date.now() + timeInDay,
127
+ });
128
+ Querysub.commit(() => {
129
+ this.props.rerunFilters();
130
+ });
131
+ });
132
+ }}
133
+ title="Fixed, or will fix within a day"
134
+ >
135
+ Fixed
136
+ </Button>
137
+ </div>
138
+
139
+ <div className={css.vbox(8).fillWidth.overflowAuto.maxHeight("30vh")}>
140
+ {entries.map((entry) => {
141
+ const updateEntry = (changes: Partial<SuppressionEntry>) => {
142
+ let newEntry = { ...entry, ...changes };
143
+ void Querysub.onCommitFinished(async () => {
144
+ await controller.setSuppressionEntry.promise(newEntry);
145
+ });
146
+ };
147
+ let count = this.props.suppressionCounts.get(entry.key) || 0;
148
+ let expiredCount = this.props.expiredSuppressionCounts.get(entry.key) || 0;
149
+ return <div
150
+ key={entry.match}
151
+ className={
152
+ css.hbox(8).pad2(12).bord2(0, 0, 10).fillWidth
153
+ //+ (entry.expiresAt < Date.now() && expiredCount > 0 && css.opacity(0.5))
154
+ + ((count === 0 && expiredCount === 0) && css.opacity(0.6))
155
+ + (
156
+ count > 0 && entry.expiresAt !== NOT_AN_ERROR_EXPIRE_TIME && css.hsla(0, 50, 50, 0.5)
157
+ || css.hsla(0, 0, 0, 0.1)
158
+ )
159
+ }
160
+ >
161
+ <span className={css.width(100).hbox(2) + (expiredCount === 0 && count === 0 && css.opacity(0.8))}>
162
+ ({formatNumber(count)})
163
+ {
164
+ expiredCount > 0 && <span className={css.hsl(0, 50, 50).pad2(4, 2).colorhsl(0, 50, 95)}>
165
+ + {formatNumber(expiredCount)} EXPIRED
166
+ </span>
167
+ }
168
+ </span>
169
+ <InputLabel
170
+ label="Match Pattern"
171
+ value={entry.match}
172
+ onChangeValue={value => updateEntry({ match: value })}
173
+ fillWidth={0.5}
174
+ />
175
+ <InputLabel
176
+ label="Comment"
177
+ value={entry.comment}
178
+ onChangeValue={value => updateEntry({ comment: value })}
179
+ fillWidth
180
+ />
181
+
182
+ <Button
183
+ onClick={() => updateEntry({ expiresAt: Date.now() + timeInDay })}
184
+ title="Fixed, or will fix within a day"
185
+ >
186
+ Fixed
187
+ </Button>
188
+ <Button onClick={() => updateEntry({ expiresAt: Date.now() + timeInDay * 7 })}
189
+ title="Ignore for a week"
190
+ >
191
+ Ignore (for a week)
192
+ </Button>
193
+ <Button onClick={() => updateEntry({ expiresAt: NOT_AN_ERROR_EXPIRE_TIME })}>
194
+ Not an error
195
+ </Button>
196
+ <Button
197
+ onClick={() => updateEntry({ expiresAt: Date.now() - timeInDay * 7 })}
198
+ title="Unignore"
199
+ >
200
+ Unignore
201
+ </Button>
202
+ {entry.expiresAt === NOT_AN_ERROR_EXPIRE_TIME && <span>
203
+ Not an error
204
+ </span>}
205
+ {entry.expiresAt < Date.now() && <span>
206
+ Expired
207
+ </span>}
208
+ {entry.expiresAt > Date.now() && entry.expiresAt !== NOT_AN_ERROR_EXPIRE_TIME && <span>
209
+ Expires in {formatDateJSX(Date.now() * 2 - entry.expiresAt)}
210
+ </span>}
211
+ <span className={css.opacity(0.5)}>
212
+ (Last Updated: {formatDateJSX(entry.lastUpdateTime)})
213
+ </span>
214
+ <Button
215
+ onClick={() => controller.removeSuppressionEntry.promise(entry.key)}
216
+ hue={120}
217
+ >
218
+ Remove
219
+ </Button>
220
+ </div>;
221
+ })}
222
+ </div>
223
+ </div>;
224
+ }
225
+ }
@@ -0,0 +1,207 @@
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { qreact } from "../../../4-dom/qreact";
3
+ import { css } from "../../../4-dom/css";
4
+ import { isCurrentUserSuperUser } from "../../../user-implementation/userData";
5
+ import { RecentErrorsController, SuppressionListController, watchRecentErrors, MAX_RECENT_ERRORS, NOT_AN_ERROR_EXPIRE_TIME, SuppressionEntry } from "./ErrorNotificationController";
6
+ import { t } from "../../../2-proxy/schema2";
7
+ import { InputLabel } from "../../../library-components/InputLabel";
8
+ import { Button } from "../../../library-components/Button";
9
+ import { ATag } from "../../../library-components/ATag";
10
+ import { managementPageURL, showingManagementURL } from "../../managementPages";
11
+ import { errorNotifyToggleURL } from "../LogViewer2";
12
+ import { Querysub } from "../../../4-querysub/QuerysubController";
13
+ import { nextId, timeInDay } from "socket-function/src/misc";
14
+ import { formatNumber } from "socket-function/src/formatting/format";
15
+ import { Icon } from "../../../library-components/icons";
16
+
17
+ export class ErrorWarning extends qreact.Component {
18
+ state = t.state({
19
+ suppressionInput: t.string(""),
20
+ reloading: t.boolean(false),
21
+ });
22
+
23
+ public render() {
24
+ if (!isCurrentUserSuperUser()) return undefined;
25
+ watchRecentErrors();
26
+
27
+ let recentController = RecentErrorsController(SocketFunction.browserNodeId());
28
+ let suppressionController = SuppressionListController(SocketFunction.browserNodeId());
29
+
30
+ let recentErrors = recentController.getRecentErrors();
31
+
32
+ const refreshButton =
33
+ <Button
34
+ onClick={() => {
35
+ this.state.reloading = true;
36
+ void Querysub.onCommitFinished(async () => {
37
+ try {
38
+ await recentController.rescanAllErrorsNow.promise();
39
+ } finally {
40
+ Querysub.commit(() => {
41
+ this.state.reloading = false;
42
+ });
43
+ }
44
+ });
45
+ }}
46
+ flavor="noui"
47
+ className={
48
+ css.button.hsla(0, 0, 50, 1).pad2(4, 1).borderRadius(2)
49
+ + (this.state.reloading && css.animation("ErrorWarning-rotate 1s linear infinite"))
50
+ }
51
+ >
52
+
53
+ <style>
54
+ {
55
+ `@keyframes ErrorWarning-rotate {
56
+ from {
57
+ transform: rotate(0deg);
58
+ }
59
+ to {
60
+ transform: rotate(360deg);
61
+ }
62
+ }
63
+ `}
64
+ </style>
65
+ </Button>;
66
+
67
+ if (!recentErrors || recentErrors.length === 0) {
68
+ return <span className={css.hbox(8)}>
69
+ <ATag target="_blank" values={[showingManagementURL.getOverride(true), managementPageURL.getOverride("LogViewer2"), errorNotifyToggleURL.getOverride(true)]}>
70
+ No Errors
71
+ </ATag>
72
+ {refreshButton}
73
+ </span>;
74
+ }
75
+
76
+ // Count unique files
77
+ let fileSet = new Set<string>();
78
+ for (let error of recentErrors) {
79
+ let file = String(error.__FILE__) || "";
80
+ if (file) {
81
+ fileSet.add(file);
82
+ }
83
+ }
84
+ let fileCount = fileSet.size;
85
+ let fileCountText = `${formatNumber(fileCount)} files matched`;
86
+ if (recentErrors.length >= MAX_RECENT_ERRORS) {
87
+ fileCountText = "> " + fileCountText;
88
+ }
89
+
90
+ const addSuppressionEntry = async (match: string, expiresAt: number) => {
91
+ await suppressionController.setSuppressionEntry.promise({
92
+ key: nextId(),
93
+ match: match,
94
+ comment: "",
95
+ lastUpdateTime: Date.now(),
96
+ expiresAt: expiresAt,
97
+ });
98
+ };
99
+
100
+ let suppressionList = suppressionController.getSuppressionList() || [];
101
+ let topExpired: SuppressionEntry | undefined = undefined;
102
+ if (recentErrors[0].__matchedOutdatedSuppressionKey) {
103
+ let key = recentErrors[0].__matchedOutdatedSuppressionKey;
104
+ topExpired = suppressionList.find(x => x.key === key);
105
+
106
+ }
107
+
108
+ return (
109
+ <div className={css.vbox(8).fillWidth}>
110
+ <div className={css.hbox(8).wrap}>
111
+ <div className={css}>
112
+ ⚠️ {fileCountText} files with errors
113
+ </div>
114
+
115
+ <ATag target="_blank" values={[showingManagementURL.getOverride(true), managementPageURL.getOverride("LogViewer2"), errorNotifyToggleURL.getOverride(true)]}>
116
+ View Logs
117
+ </ATag>
118
+ {refreshButton}
119
+ </div>
120
+
121
+ {topExpired &&
122
+ <div className={css.hbox(8)}>
123
+ <Button onClick={() => {
124
+ void Querysub.onCommitFinished(async () => {
125
+ await suppressionController.setSuppressionEntry.promise({
126
+ ...topExpired!,
127
+ expiresAt: Date.now() + timeInDay,
128
+ });
129
+ });
130
+ }}>
131
+ Ignore Again
132
+ </Button>
133
+ <div>
134
+ Match Pattern =
135
+ </div>
136
+ <div className={css.hsl(200, 40, 40).pad2(4, 2).colorhsl(0, 0, 95)}>
137
+ {topExpired.match}
138
+ </div>
139
+ </div>
140
+ }
141
+ <div className={css.hbox(8).hsl(0, 50, 50).pad2(4, 2).colorhsl(0, 50, 95)}>
142
+ {recentErrors[0].param0} ({recentErrors[0].__NAME__})
143
+ </div>
144
+
145
+ <div className={css.hbox(8).fillWidth}>
146
+ <InputLabel
147
+ value={this.state.suppressionInput}
148
+ noEnterKeyBlur
149
+ onChangeValue={(value: string) => this.state.suppressionInput = value}
150
+ onKeyDown={e => {
151
+ if (e.key === "Enter") {
152
+ let value = e.currentTarget.value;
153
+ if (value.trim()) {
154
+ e.currentTarget.value = "";
155
+ void Querysub.onCommitFinished(async () => {
156
+ await addSuppressionEntry(value, Date.now() + timeInDay);
157
+ Querysub.commit(() => {
158
+ this.state.suppressionInput = "";
159
+ });
160
+ });
161
+ }
162
+ }
163
+ }}
164
+ placeholder="Quick suppress pattern (press enter to add)"
165
+ fillWidth
166
+ />
167
+
168
+
169
+ <Button
170
+ onClick={() => {
171
+ let value = this.state.suppressionInput;
172
+ if (value.trim()) {
173
+ void Querysub.onCommitFinished(async () => {
174
+ await addSuppressionEntry(value, Date.now() + timeInDay);
175
+ Querysub.commit(() => {
176
+ this.state.suppressionInput = "";
177
+ });
178
+ });
179
+ }
180
+ }}
181
+ className={css.fontSize(12).pad2(4, 6)}
182
+ disabled={!this.state.suppressionInput.trim()}
183
+ >
184
+ Fixed
185
+ </Button>
186
+ <Button
187
+ onClick={() => {
188
+ let value = this.state.suppressionInput;
189
+ if (value.trim()) {
190
+ void Querysub.onCommitFinished(async () => {
191
+ await addSuppressionEntry(value, NOT_AN_ERROR_EXPIRE_TIME);
192
+ Querysub.commit(() => {
193
+ this.state.suppressionInput = "";
194
+ });
195
+ });
196
+ }
197
+ }}
198
+ className={css.fontSize(12).pad2(4, 6)}
199
+ disabled={!this.state.suppressionInput.trim()}
200
+ >
201
+ Not a bug
202
+ </Button>
203
+ </div>
204
+ </div>
205
+ );
206
+ }
207
+ }
@@ -0,0 +1,38 @@
1
+ import { Querysub } from "../../4-querysub/Querysub";
2
+ import fs from "fs";
3
+ import { Zip } from "../../zip";
4
+ import { getLoggers } from "./diskLogger";
5
+ import { formatNumber } from "socket-function/src/formatting/format";
6
+ import { shutdown } from "../periodic";
7
+
8
+ async function main() {
9
+ let { logLogs } = getLoggers()!;
10
+ //let root = "X:/root/machine-services/cyoa-1-dply/git/database-storage/disklogs";
11
+ /*
12
+ let root = "D:/repos/qs-cyoa/database-storage/disklogs/";
13
+ let files = await fs.promises.readdir(root);
14
+ for (let fileName of files) {
15
+ let path = root + fileName;
16
+ if (!path.endsWith(".log")) continue;
17
+ let contents = await fs.promises.readFile(path);
18
+ let lines = contents.toString("utf8").split("\n");
19
+ for (let line of lines) {
20
+ if (!line) continue;
21
+ try {
22
+ let log = JSON.parse(line);
23
+ logLogs.append(log);
24
+ } catch (e) {
25
+ require("debugbreak")(2);
26
+ debugger;
27
+ console.error(e, line);
28
+ }
29
+ }
30
+ console.log(`Flushing ${path}, line count: ${formatNumber(lines.length)}, ${formatNumber(contents.length)}B`);
31
+ let time = Number(fileName.split("-")[0]) || Date.now();
32
+ await logLogs.flushNow(time);
33
+ }
34
+ */
35
+ await logLogs.moveLogsToBackblaze();
36
+ //await shutdown();
37
+ }
38
+ main().catch(console.error).finally(() => process.exit());
@@ -1,9 +1,10 @@
1
1
  import { css, isNode } from "typesafecss";
2
2
  import { compileTransformBefore } from "typenode";
3
+
3
4
  // IMPORTANT! Don't add any imports BEFORE we add compileTransform
4
5
  if (isNode()) {
5
- // NOTE: While mapping line numbers would be nice, just adding the file is easy,
6
- // and provides a huge benefit. And... you should be able to trace down the specific
6
+ // NOTE: While mapping line numbers would be nice, just adding the file is easier,
7
+ // and still provides a huge benefit. And... you should be able to trace down the specific
7
8
  // line from the message (and if not, you should make the message more specific).
8
9
  compileTransformBefore((contents, path, module) => {
9
10
  if (![".js", ".jsx", ".ts", ".tsx"].some(x => path.endsWith(x))) {
@@ -34,21 +35,10 @@ if (isNode()) {
34
35
  return newConsole;
35
36
  }
36
37
 
37
- // Use line numbers BEFORE we inject the compile transform. The line numbers are for vs code,
38
- // so this makes them accurate for original source.
39
- contents = contents.split("\n").map((line, index) => {
40
- return line
41
- .replaceAll(`(0, diskLogger_1.diskLog)(`, `(0, diskLogger_1.diskLog)(console.__context, { __LINE__: ${index} }, `)
42
- .replaceAll(` diskLog(`, ` diskLog(console.__context, { __LINE__: ${index} },`)
43
- .replaceAll(`(0, diskLogger_1.logDisk)(`, `(0, diskLogger_1.logDisk)(console.__context, { __LINE__: ${index} }, `)
44
- .replaceAll(` logDisk(`, ` logDisk(console.__context, { __LINE__: ${index} },`)
45
- ;
46
- }).join("\n");
47
- // IMPORTANT! No newlines, so the line numbers match up with the original source.
38
+ // IMPORTANT! No newlines, so we don't break sourcemaps
48
39
  return `var console = (${shimConsole.toString().replaceAll("\n", " ")})(); ${contents}`;
49
40
  });
50
41
 
51
- // We want to call shimConsoleLogs early. Before we call it, our additional context isn't
52
- // screened out, which makes the terminal hard to read.
53
- void import("./diskShimConsoleLogs").then(x => x.shimConsoleLogs());
54
- }
42
+ let diskShimConsoleLogs = require("./diskShimConsoleLogs") as typeof import("./diskShimConsoleLogs");
43
+ diskShimConsoleLogs.shimConsoleLogs();
44
+ }
@@ -0,0 +1,153 @@
1
+ Huge amount of data:
2
+ https://127-0-0-1.querysubtest.com:7007/?hot&enableLogs&page=login&caseInsensitive&showingmanagement&endTime=1757732880000&startTime=1746997620000&managementpage=LogViewer2
3
+ Very small amount of data
4
+ https://127-0-0-1.querysubtest.com:7007/?hot&enableLogs&page=login&filter=%22431%22&showingmanagement&endTime=1755140880000&startTime=1754950020000&managementpage=LogViewer2
5
+
6
+
7
+
8
+ 4) Setup new hetnzer server
9
+
10
+ yarn setup-machine 176.9.2.136
11
+
12
+ 5) Update all services, and move them to that machine
13
+ 5) Verify the hezner server can run the site well
14
+ 6) Take down our digital ocean server
15
+ 7) Destroy our digital ocean server
16
+
17
+
18
+ 6) Update URLParam to allow linking it to other parameters, resetting when they change.
19
+ - With a function, and have standard one beside URLParam (that uses page and tab)
20
+ - ALSO managementPageURL
21
+ - Reset filter in FastArchiveViewer
22
+ - First observe the overlap with it and the BookOverview
23
+ - If we are actually on the book overview page and we close the management page then that shouldn't reset it. We just want to reset it when you change pages. Because we want you to be able to hide and show the management page quickly if you want to double check something. Generally speaking though, you won't be on a page with filter and then going back and forth. And if you are, whatever. That's just the management page. We just want to avoid the overall confusion and annoyance of having lots of pre-filled values (And mostly the confusion of having filter prefilled all the time because it's always going to be set because everyone uses it and no one resets it at the moment.
24
+
25
+
26
+
27
+
28
+ 5) IM error notifications - allow immediately knowing about production issues, for better testing
29
+ - Create a dedicated entry point which acts like a client of the HTTP server, using RecentErrorControllers.getRecentErrors
30
+ - Getting it working in a script will be interesting, but... in theory it should just work?
31
+ - Just for new errors
32
+ - Using backblaze to track when we send it, so we can heavily limit IMs and email
33
+ - IM api key tracked in secrets (like email api key)
34
+ - Once we get it working, deploy to production
35
+
36
+ 6) IM + email digests (daily / weekly?)
37
+ - a very short digest for the instant message which then links to a page on the site with a larger digest
38
+ - which has tabs, and each part in the instant message links to the correct tab
39
+ - Augments the error notifications entry point, having it also queue stuff up for digests.
40
+ - Some notifications will never be immediate and will always be only in digests.
41
+ - For now this will just be for:
42
+ - non-suppressed errors
43
+ - suppressed errors
44
+ - Eventually the goal of this is to add our tracking charts to this. There are some really useful metrics we can track.
45
+ - unique visit IPs.
46
+ - Percent bounces.
47
+ - average visit length and also median visit length top 95" bottom 5"
48
+ - average stories read average story percentages read
49
+ - percent of first page story views by subscribers percent of fifth page story view by subscribers
50
+ - Number of users visiting the fifth page.
51
+ - New subscriptions
52
+ - Subscription cancellations
53
+ - Total subscriptions
54
+
55
+
56
+
57
+ 5) Life cycle analyzer
58
+ - Uses FastArchiveViewer, but instead of showing a table, shows lifecycles (a derived concept)
59
+ - We save them in backblaze, with a bit of cache for loading them
60
+ - List of life cycles
61
+ - Life cycle
62
+ - Title
63
+ - Operation list (each supports | / &, but having multiple is even better)
64
+ - Match filter
65
+ - Group key extractions (optional, if not set it becomes a singleton)
66
+ - Just a field name
67
+ - CAN have multiple, which adds us as multiple life cycles
68
+ - With each one being namespaced using the key, so we can tell them apart
69
+ - Global value setting (optional, if not set it has no global state impact)
70
+ - A list of set values
71
+ - Each one is an expression which can use fields in the object, ex:
72
+ - `alivePathValueServers.$threadId = true`
73
+ - Show AND SHOULDN'T include match filters!
74
+ - So when we should the count matched, we can show stats for these, which will often be should have "finished", and shouldn't have "error", so we can see completed, and errors
75
+ - Similar to error notifications, but... it's nice to also have this here, as we could miss the notification, or suppress it, but when we are looking at a life cycle it's relevant skyrockets.
76
+ - ALSO for start, so we can see cutoff starts!
77
+ OH! How do we handle cut off starts?
78
+ - Maybe... we have a "read preload" duration, and... we read that, BUT, only include life cycles which are also in our main selected time. So we don't cut anything off in our main time, but don't add new values which also get cut off!
79
+ - Same time/machine/thread selector as log viewer
80
+ - Allow filtering to specific life cycles
81
+ - After download, shows matches per life cycle
82
+ - Button to reset to all
83
+ - Download logs, and scan for selected life cycles
84
+ - Immediately on load, showing progress and throttling, so it's not too bad
85
+ - Result
86
+ - List of life cycles, with count of each
87
+ - Table of individual life cycles?
88
+ - Can then filter within these life cycles by searching
89
+ - BUT, importantly, if any log is matched in a life cycle, the entire life cycle is matched
90
+ - AND, global settings for ALL life cycles are applied, not just filtered ones!
91
+ - Table of result life cycles
92
+ - Preview shows first matched line
93
+ - ALSO, shows duration of life cycle!
94
+ - And start date
95
+ - Expand to see pin that specific life cycle above
96
+ - Show list of logs in it, in another table, with searching on each of them
97
+ - Start date AND duration of each line!
98
+ - Can pin multiple life cycles (I guess might as well)
99
+ - Show list of global value expressions as well (limited, but with filtering to search them easily)
100
+ - Can expand a global value to see object (but again... limited?)
101
+ - Can select a specific global value path, to have it injected into
102
+ - a column for before and after each life cycle
103
+ - A column for after each line within a life cycle
104
+ - Can also select parent values, to show all paths under that (use ShowMore, to make them manageable...)
105
+
106
+ 6) Add life cycles for
107
+ - Node discovery life cycle (new node, check for alive, check for paths, etc)
108
+ - Discover nodes
109
+ - Check if trusted
110
+ - Sets authorities
111
+ - Sets nodes which exist
112
+ - Ready broadcast
113
+ - Resync is also part of life cycle
114
+ - Use different keys for threadId, and, triggeredNodeId, so we can track how a node discovers other nodes, vs how a node is discovered
115
+ - Mark as dead, dead count increases, remove node as dead
116
+ - MAYBE there should be 2 lifecycles, one for all, and one for just creation type stuff (no discovery)
117
+ - Trusted machine lifecycle
118
+ - Check if we need to add trust
119
+ - Add trust to archives
120
+ - Read trusted nodes from archives
121
+ => Set of trusted machines per threadId
122
+ - Functions evaluation in FunctionRunner
123
+ - Adding in http, evaluation, and maybe even forwarding the final PathValue writes to it?
124
+ - Known nodes
125
+ - Not really a life cycle, but does set global state
126
+ - Value syncing
127
+ - Crosses node boundaries, with the server having to include the client key, so we can track a proxy access => ask the server => validate permissions => send value => receive value => notify proxy
128
+ - Value creation
129
+ - Source, propagation, writing to archives
130
+ - Permissions (probably part of value syncing)
131
+ - SOCKET connections, open, stats, closed, error, etc.
132
+ - Archive management
133
+ - Queue (count)
134
+ - Archive (count + file)
135
+ - Joining, deleting, etc, etc
136
+
137
+ 6) Redeploy everything to the servers again, updating our machine apply script, all of our services, deploying our code, etc
138
+
139
+ 7) Use log viewer to find rejections / errors during startup, and then trace down the sync life cycle for the path write, and find where it is lagging out!
140
+ - Re-enable all of our services as well.
141
+ - Just logs MIGHT be able to do it, but... life cycles should make it a lot easier to correlate logs, which is something we need to do anyways to solve it...
142
+
143
+ 10) Verify old user/fast-log-cache machine folders are deleted
144
+
145
+
146
+ SPECIAL UI links for certain errors in log view
147
+ - Probably dynamically created, based on contents of log
148
+ - LINKS to filters for all these special errors on a special page
149
+ - Special errors
150
+ - Rejection errors
151
+ - Link to life cycle filtering show the life cycles involving the path (we need to hardcode the life cycle ids for these, after we create the life cycles)
152
+ - Audit failures
153
+ - Same info as rejection errors, except for the path, and the failing lock path