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
@@ -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,151 @@
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
+ yarn setup-machine 176.9.2.136
10
+ 5) Update all services, and move them to that machine
11
+ 5) Verify the hezner server can run the site well
12
+ 6) Take down our digital ocean server
13
+ 7) Destroy our digital ocean server
14
+
15
+
16
+ 6) Update URLParam to allow linking it to other parameters, resetting when they change.
17
+ - With a function, and have standard one beside URLParam (that uses page and tab)
18
+ - ALSO managementPageURL
19
+ - Reset filter in FastArchiveViewer
20
+ - First observe the overlap with it and the BookOverview
21
+ - 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.
22
+
23
+
24
+
25
+
26
+ 5) IM error notifications - allow immediately knowing about production issues, for better testing
27
+ - Create a dedicated entry point which acts like a client of the HTTP server, using RecentErrorControllers.getRecentErrors
28
+ - Getting it working in a script will be interesting, but... in theory it should just work?
29
+ - Just for new errors
30
+ - Using backblaze to track when we send it, so we can heavily limit IMs and email
31
+ - IM api key tracked in secrets (like email api key)
32
+ - Once we get it working, deploy to production
33
+
34
+ 6) IM + email digests (daily / weekly?)
35
+ - a very short digest for the instant message which then links to a page on the site with a larger digest
36
+ - which has tabs, and each part in the instant message links to the correct tab
37
+ - Augments the error notifications entry point, having it also queue stuff up for digests.
38
+ - Some notifications will never be immediate and will always be only in digests.
39
+ - For now this will just be for:
40
+ - non-suppressed errors
41
+ - suppressed errors
42
+ - Eventually the goal of this is to add our tracking charts to this. There are some really useful metrics we can track.
43
+ - unique visit IPs.
44
+ - Percent bounces.
45
+ - average visit length and also median visit length top 95" bottom 5"
46
+ - average stories read average story percentages read
47
+ - percent of first page story views by subscribers percent of fifth page story view by subscribers
48
+ - Number of users visiting the fifth page.
49
+ - New subscriptions
50
+ - Subscription cancellations
51
+ - Total subscriptions
52
+
53
+
54
+
55
+ 5) Life cycle analyzer
56
+ - Uses FastArchiveViewer, but instead of showing a table, shows lifecycles (a derived concept)
57
+ - We save them in backblaze, with a bit of cache for loading them
58
+ - List of life cycles
59
+ - Life cycle
60
+ - Title
61
+ - Operation list (each supports | / &, but having multiple is even better)
62
+ - Match filter
63
+ - Group key extractions (optional, if not set it becomes a singleton)
64
+ - Just a field name
65
+ - CAN have multiple, which adds us as multiple life cycles
66
+ - With each one being namespaced using the key, so we can tell them apart
67
+ - Global value setting (optional, if not set it has no global state impact)
68
+ - A list of set values
69
+ - Each one is an expression which can use fields in the object, ex:
70
+ - `alivePathValueServers.$threadId = true`
71
+ - Show AND SHOULDN'T include match filters!
72
+ - 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
73
+ - 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.
74
+ - ALSO for start, so we can see cutoff starts!
75
+ OH! How do we handle cut off starts?
76
+ - 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!
77
+ - Same time/machine/thread selector as log viewer
78
+ - Allow filtering to specific life cycles
79
+ - After download, shows matches per life cycle
80
+ - Button to reset to all
81
+ - Download logs, and scan for selected life cycles
82
+ - Immediately on load, showing progress and throttling, so it's not too bad
83
+ - Result
84
+ - List of life cycles, with count of each
85
+ - Table of individual life cycles?
86
+ - Can then filter within these life cycles by searching
87
+ - BUT, importantly, if any log is matched in a life cycle, the entire life cycle is matched
88
+ - AND, global settings for ALL life cycles are applied, not just filtered ones!
89
+ - Table of result life cycles
90
+ - Preview shows first matched line
91
+ - ALSO, shows duration of life cycle!
92
+ - And start date
93
+ - Expand to see pin that specific life cycle above
94
+ - Show list of logs in it, in another table, with searching on each of them
95
+ - Start date AND duration of each line!
96
+ - Can pin multiple life cycles (I guess might as well)
97
+ - Show list of global value expressions as well (limited, but with filtering to search them easily)
98
+ - Can expand a global value to see object (but again... limited?)
99
+ - Can select a specific global value path, to have it injected into
100
+ - a column for before and after each life cycle
101
+ - A column for after each line within a life cycle
102
+ - Can also select parent values, to show all paths under that (use ShowMore, to make them manageable...)
103
+
104
+ 6) Add life cycles for
105
+ - Node discovery life cycle (new node, check for alive, check for paths, etc)
106
+ - Discover nodes
107
+ - Check if trusted
108
+ - Sets authorities
109
+ - Sets nodes which exist
110
+ - Ready broadcast
111
+ - Resync is also part of life cycle
112
+ - Use different keys for threadId, and, triggeredNodeId, so we can track how a node discovers other nodes, vs how a node is discovered
113
+ - Mark as dead, dead count increases, remove node as dead
114
+ - MAYBE there should be 2 lifecycles, one for all, and one for just creation type stuff (no discovery)
115
+ - Trusted machine lifecycle
116
+ - Check if we need to add trust
117
+ - Add trust to archives
118
+ - Read trusted nodes from archives
119
+ => Set of trusted machines per threadId
120
+ - Functions evaluation in FunctionRunner
121
+ - Adding in http, evaluation, and maybe even forwarding the final PathValue writes to it?
122
+ - Known nodes
123
+ - Not really a life cycle, but does set global state
124
+ - Value syncing
125
+ - 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
126
+ - Value creation
127
+ - Source, propagation, writing to archives
128
+ - Permissions (probably part of value syncing)
129
+ - SOCKET connections, open, stats, closed, error, etc.
130
+ - Archive management
131
+ - Queue (count)
132
+ - Archive (count + file)
133
+ - Joining, deleting, etc, etc
134
+
135
+ 6) Redeploy everything to the servers again, updating our machine apply script, all of our services, deploying our code, etc
136
+
137
+ 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!
138
+ - Re-enable all of our services as well.
139
+ - 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...
140
+
141
+ 10) Verify old user/fast-log-cache machine folders are deleted
142
+
143
+
144
+ SPECIAL UI links for certain errors in log view
145
+ - Probably dynamically created, based on contents of log
146
+ - LINKS to filters for all these special errors on a special page
147
+ - Special errors
148
+ - Rejection errors
149
+ - 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)
150
+ - Audit failures
151
+ - Same info as rejection errors, except for the path, and the failing lock path