querysub 0.343.0 → 0.345.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.
- package/package.json +3 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +1 -0
- package/src/3-path-functions/PathFunctionHelpers.ts +1 -1
- package/src/3-path-functions/PathFunctionRunner.ts +3 -0
- package/src/3-path-functions/syncSchema.ts +4 -1
- package/src/4-deploy/edgeClientWatcher.tsx +76 -82
- package/src/4-querysub/querysubPrediction.ts +1 -1
- package/src/diagnostics/logs/diskLogger.ts +11 -0
- package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +65 -6
- package/src/diagnostics/logs/errorNotifications/errorDigestEntry.tsx +9 -2
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +1 -1
- package/src/misc/cloneHelpers.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.345.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"test": "yarn typenode ./test.ts",
|
|
16
16
|
"test3": "yarn typenode ./src/test/test.tsx --local",
|
|
17
17
|
"test2": "yarn typenode ./src/4-dom/qreactTest.tsx --local",
|
|
18
|
-
"error-watch": "yarn typenode ./src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx"
|
|
18
|
+
"error-watch": "yarn typenode ./src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx",
|
|
19
|
+
"error-email": "yarn typenode ./src/diagnostics/logs/errorNotifications/errorDigestEntry.tsx"
|
|
19
20
|
},
|
|
20
21
|
"bin": {
|
|
21
22
|
"deploy": "./bin/deploy.js",
|
|
@@ -1636,6 +1636,7 @@ export class PathValueProxyWatcher {
|
|
|
1636
1636
|
// The calls have to happen after our local writes. This is because they are likely to
|
|
1637
1637
|
// influence the local writes, and we don't want our local writes to be always invalidated
|
|
1638
1638
|
call.runAtTime = getNextTime();
|
|
1639
|
+
call.fromProxy = watcher.debugName;
|
|
1639
1640
|
logErrors(runCall(call, metadata));
|
|
1640
1641
|
watcher.options.onCallCommit?.(call, metadata);
|
|
1641
1642
|
}
|
|
@@ -121,7 +121,7 @@ export function interceptCallsBase<T>(
|
|
|
121
121
|
}
|
|
122
122
|
interceptCalls.declare(interceptCallsBase);
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
/** Writes the function call allowing interceptors to capture it (vs runCall which just runs it). */
|
|
125
125
|
export function writeFunctionCall(config: {
|
|
126
126
|
domainName: string;
|
|
127
127
|
moduleId: string;
|
|
@@ -91,6 +91,9 @@ export interface CallSpec {
|
|
|
91
91
|
callerIP: string;
|
|
92
92
|
runAtTime: Time;
|
|
93
93
|
|
|
94
|
+
// Not just used for debugging, also used to add special proxy-related warnings.
|
|
95
|
+
fromProxy?: string;
|
|
96
|
+
|
|
94
97
|
filterable?: Filterable;
|
|
95
98
|
}
|
|
96
99
|
export function debugCallSpec(spec: CallSpec): string {
|
|
@@ -48,6 +48,9 @@ export type FunctionMetadata<F = unknown> = {
|
|
|
48
48
|
*/
|
|
49
49
|
delayCommit?: boolean;
|
|
50
50
|
|
|
51
|
+
/** By default, we try to finish calls in the start order when using Querysub.commitAsync, unless the caller explicitly says not to. However, if this is set, then we won't have this call finish or cause other calls to be finished in the start order. This makes locking less safe, but can be useful for long-running functions that shouldn't block other functions and which the order doesn't matter. */
|
|
52
|
+
noFinishInStartOrder?: boolean;
|
|
53
|
+
|
|
51
54
|
/** Too many locks can lag the server, and eventually cause crashes. Consider using Querysub.noLocks(() => ...) around code that is accessing too many values, assuming you don't want to lock them. However, if absolutely required, you can override max locks to allow as many locks to be created as you want until the server crashses... */
|
|
52
55
|
maxLocksOverride?: number;
|
|
53
56
|
};
|
|
@@ -363,7 +366,7 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
|
|
|
363
366
|
moduleId,
|
|
364
367
|
functionId: name,
|
|
365
368
|
args,
|
|
366
|
-
metadata
|
|
369
|
+
metadata: metadata
|
|
367
370
|
});
|
|
368
371
|
}, {
|
|
369
372
|
debug() {
|
|
@@ -18,6 +18,8 @@ import { Button } from "../library-components/Button";
|
|
|
18
18
|
import { updateRootDiscoveryLocation } from "../-f-node-discovery/NodeDiscovery";
|
|
19
19
|
|
|
20
20
|
const SWITCH_SERVER_TIMEOUT = timeInSecond * 15;
|
|
21
|
+
const MAX_DISPLAY_INTERVAL = timeInMinute * 5;
|
|
22
|
+
const FINAL_DELAY = timeInSecond * 30;
|
|
21
23
|
|
|
22
24
|
let lastHashServer = "";
|
|
23
25
|
export function startEdgeNotifier() {
|
|
@@ -61,113 +63,105 @@ const notifyClients = throttleFunction(timeInMinute, async function notifyClient
|
|
|
61
63
|
// Track current notification state
|
|
62
64
|
let currentNotification: { close: () => void } | null = null;
|
|
63
65
|
let curHash = "";
|
|
66
|
+
let lastShownTime = 0;
|
|
64
67
|
function onLiveHashChange(liveHash: string, refreshThresholdTime: number) {
|
|
65
68
|
console.log(blue(`Received client liveHash ${liveHash}, prev hash: ${curHash}`));
|
|
66
69
|
if (liveHash === curHash) return;
|
|
67
70
|
let prevHash = curHash;
|
|
68
71
|
// Don't notify the user right away. Hopefully they refresh naturally, and we never have to notify them at all!
|
|
69
72
|
// Also, refresh BEFORE the server dies, not exactly when it is about to die
|
|
70
|
-
let
|
|
73
|
+
let delays = [0.4, 0.75, 1].map(x => delay(x * refreshThresholdTime));
|
|
74
|
+
|
|
71
75
|
console.log(blue(`Client liveHash changed ${liveHash}, prev hash: ${prevHash}`));
|
|
72
|
-
// If we are replacing an already existing notification, don't show immediately
|
|
73
|
-
let skipFirst = false;
|
|
74
|
-
if (currentNotification) {
|
|
75
|
-
currentNotification.close();
|
|
76
|
-
currentNotification = null;
|
|
77
|
-
skipFirst = true;
|
|
78
|
-
}
|
|
79
|
-
curHash = liveHash;
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
curHash = liveHash;
|
|
82
78
|
|
|
83
79
|
// Start notification loop
|
|
84
80
|
void (async () => {
|
|
85
81
|
// Show notifications at intervals
|
|
86
|
-
for (let
|
|
82
|
+
for (let delayTime of delays) {
|
|
83
|
+
await delayTime;
|
|
87
84
|
// Don't show if a newer notification is active
|
|
88
85
|
if (curHash !== liveHash) return;
|
|
89
86
|
|
|
90
|
-
let waitDuration = (notifyIntervals[i + 1] - notifyIntervals[i]) * duration;
|
|
91
|
-
// If the duration is short, and it's not the last one, skip it
|
|
92
|
-
if (i < notifyIntervals.length - 2 && waitDuration <= 30 * 1000) continue;
|
|
93
|
-
|
|
94
87
|
// Update the URL override for manual refreshes
|
|
95
88
|
if (!liveHash.startsWith("forced")) {
|
|
96
89
|
Querysub.localCommit(() => {
|
|
97
90
|
liveHashOverrideURL.value = { liveHash, time: Date.now() };
|
|
98
91
|
});
|
|
99
92
|
}
|
|
100
|
-
if (!skipFirst) {
|
|
101
|
-
if (currentNotification) {
|
|
102
|
-
currentNotification.close();
|
|
103
|
-
currentNotification = null;
|
|
104
|
-
}
|
|
105
|
-
// Show notification modal
|
|
106
|
-
let nextNotification = {
|
|
107
|
-
close: showModal({
|
|
108
|
-
onClose: () => {
|
|
109
|
-
if (currentNotification !== nextNotification) return;
|
|
110
|
-
currentNotification = null;
|
|
111
|
-
},
|
|
112
|
-
content: (
|
|
113
|
-
<div
|
|
114
|
-
title={`Live Hash: ${liveHash}, prev hash: ${prevHash}`}
|
|
115
|
-
className={
|
|
116
|
-
css.vbox(10).pad(20)
|
|
117
|
-
.hsla(0, 0, 0, 0.8)
|
|
118
|
-
.position("fixed")
|
|
119
|
-
.bottom(10)
|
|
120
|
-
.right(10)
|
|
121
|
-
.zIndex(1000)
|
|
122
|
-
+ " keepModalsOpen"
|
|
123
|
-
}
|
|
124
|
-
>
|
|
125
|
-
<div className={css.vbox(10).maxWidth(250)}>
|
|
126
|
-
<h3 className={css.margin(0)}>Server Update Available</h3>
|
|
127
|
-
<div>The server has been updated. Please refresh the page to ensure you don't experience compatibility issues.</div>
|
|
128
|
-
{i !== notifyIntervals.length - 2 && <div>This notification will be shown again at {formatNiceDateTime(Date.now() + waitDuration)}</div>}
|
|
129
|
-
{i === notifyIntervals.length - 2 && <div>The page will automatically refresh at {formatNiceDateTime(Date.now() + waitDuration)}</div>}
|
|
130
|
-
</div>
|
|
131
|
-
<div className={css.hbox(10).justifyContent("flex-end")}>
|
|
132
|
-
<button
|
|
133
|
-
className={css.pad(8, 16).hsl(200, 50, 50).color("white").pointer}
|
|
134
|
-
onClick={(e) => {
|
|
135
|
-
if (!liveHash.startsWith("forced")) {
|
|
136
|
-
Querysub.localCommit(() => {
|
|
137
|
-
liveHashOverrideURL.value = { liveHash, time: Date.now() };
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
window.location.reload();
|
|
141
|
-
}}
|
|
142
|
-
>
|
|
143
|
-
Refresh Now
|
|
144
|
-
</button>
|
|
145
|
-
<button
|
|
146
|
-
className={css.pad(8, 16).pointer}
|
|
147
|
-
onClick={(e) => {
|
|
148
|
-
if (currentNotification) {
|
|
149
|
-
currentNotification.close();
|
|
150
|
-
currentNotification = null;
|
|
151
|
-
}
|
|
152
|
-
}}
|
|
153
|
-
>
|
|
154
|
-
Dismiss
|
|
155
|
-
</button>
|
|
156
|
-
</div>
|
|
157
|
-
</div>
|
|
158
|
-
)
|
|
159
|
-
}).close
|
|
160
|
-
};
|
|
161
|
-
currentNotification = nextNotification;
|
|
162
|
-
}
|
|
163
|
-
skipFirst = false;
|
|
164
93
|
|
|
165
|
-
|
|
166
|
-
|
|
94
|
+
let timeSinceLastShown = Date.now() - lastShownTime;
|
|
95
|
+
let isLastDelay = delayTime === delays.at(-1);
|
|
96
|
+
if (timeSinceLastShown < MAX_DISPLAY_INTERVAL && !isLastDelay) continue;
|
|
97
|
+
|
|
98
|
+
lastShownTime = Date.now();
|
|
99
|
+
|
|
100
|
+
if (currentNotification) {
|
|
101
|
+
currentNotification.close();
|
|
102
|
+
currentNotification = null;
|
|
103
|
+
}
|
|
104
|
+
// Show notification modal
|
|
105
|
+
let nextNotification = {
|
|
106
|
+
close: showModal({
|
|
107
|
+
onClose: () => {
|
|
108
|
+
if (currentNotification !== nextNotification) return;
|
|
109
|
+
currentNotification = null;
|
|
110
|
+
},
|
|
111
|
+
content: (
|
|
112
|
+
<div
|
|
113
|
+
title={`Live Hash: ${liveHash}, prev hash: ${prevHash}`}
|
|
114
|
+
className={
|
|
115
|
+
css.vbox(10).pad(20)
|
|
116
|
+
.hsla(0, 0, 0, 0.8)
|
|
117
|
+
.position("fixed")
|
|
118
|
+
.bottom(10)
|
|
119
|
+
.right(10)
|
|
120
|
+
.zIndex(1000)
|
|
121
|
+
+ " keepModalsOpen"
|
|
122
|
+
}
|
|
123
|
+
>
|
|
124
|
+
<div className={css.vbox(10).maxWidth(250)}>
|
|
125
|
+
<h3 className={css.margin(0)}>Server Update Available</h3>
|
|
126
|
+
<div>The server has been updated. Please refresh the page to ensure you don't experience compatibility issues.</div>
|
|
127
|
+
{isLastDelay && <div>The page will automatically refresh shortly</div>}
|
|
128
|
+
</div>
|
|
129
|
+
<div className={css.hbox(10).justifyContent("flex-end")}>
|
|
130
|
+
<button
|
|
131
|
+
className={css.pad(8, 16).hsl(200, 50, 50).color("white").pointer}
|
|
132
|
+
onClick={(e) => {
|
|
133
|
+
if (!liveHash.startsWith("forced")) {
|
|
134
|
+
Querysub.localCommit(() => {
|
|
135
|
+
liveHashOverrideURL.value = { liveHash, time: Date.now() };
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
window.location.reload();
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
Refresh Now
|
|
142
|
+
</button>
|
|
143
|
+
<button
|
|
144
|
+
className={css.pad(8, 16).pointer}
|
|
145
|
+
onClick={(e) => {
|
|
146
|
+
if (currentNotification) {
|
|
147
|
+
currentNotification.close();
|
|
148
|
+
currentNotification = null;
|
|
149
|
+
}
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
Dismiss
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
}).close
|
|
158
|
+
};
|
|
159
|
+
currentNotification = nextNotification;
|
|
167
160
|
}
|
|
168
161
|
|
|
169
162
|
// Only force refresh if this is still the current notification
|
|
170
|
-
if (curHash === liveHash) {
|
|
163
|
+
if (curHash === liveHash && currentNotification) {
|
|
164
|
+
await delay(FINAL_DELAY);
|
|
171
165
|
window.location.reload();
|
|
172
166
|
}
|
|
173
167
|
})();
|
|
@@ -418,7 +418,7 @@ export function getCallWrites(config: {
|
|
|
418
418
|
|
|
419
419
|
let finishInStartOrder: number | boolean | undefined;
|
|
420
420
|
|
|
421
|
-
if (config.useFinishReordering) {
|
|
421
|
+
if (config.useFinishReordering && !config.metadata?.noFinishInStartOrder && call.fromProxy) {
|
|
422
422
|
let triggerCaller = getCurrentCallCreationProxy();
|
|
423
423
|
if (!triggerCaller) {
|
|
424
424
|
require("debugbreak")(2);
|
|
@@ -40,10 +40,21 @@ export type LogDatum = Record<string, unknown> & {
|
|
|
40
40
|
export function getLogHash(obj: LogDatum) {
|
|
41
41
|
return getPathStr2(obj.__threadId || "", obj.time.toString());
|
|
42
42
|
}
|
|
43
|
+
const errorMessageFileRegex = /\n at .+?\(([^:]+):\d+:\d+\)/;
|
|
44
|
+
|
|
43
45
|
export function getLogFile(obj: LogDatum) {
|
|
44
46
|
let logType = obj.param0 || "";
|
|
45
47
|
if (obj.__FILE__) {
|
|
46
48
|
logType = String(obj.__FILE__);
|
|
49
|
+
// /root/machine-services/*/git/
|
|
50
|
+
if (logType.startsWith("/root/machine-services/")) {
|
|
51
|
+
logType = logType.split("/").slice(4).join("/");
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
let match = logType.match(errorMessageFileRegex);
|
|
55
|
+
if (match) {
|
|
56
|
+
logType = match[1];
|
|
57
|
+
}
|
|
47
58
|
}
|
|
48
59
|
if (obj[LOG_LINE_LIMIT_ID]) {
|
|
49
60
|
logType += "::" + String(obj[LOG_LINE_LIMIT_ID]);
|
|
@@ -23,6 +23,14 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
let errors: {
|
|
26
|
+
file: string;
|
|
27
|
+
errorsInFile: number;
|
|
28
|
+
warningsInFile: number;
|
|
29
|
+
message: string;
|
|
30
|
+
messageTime: string;
|
|
31
|
+
}[] = [];
|
|
32
|
+
let warnings: {
|
|
33
|
+
file: string;
|
|
26
34
|
errorsInFile: number;
|
|
27
35
|
warningsInFile: number;
|
|
28
36
|
message: string;
|
|
@@ -38,6 +46,8 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
38
46
|
let corruptWarning = "";
|
|
39
47
|
|
|
40
48
|
let failingFiles = 0;
|
|
49
|
+
let errorFiles = 0;
|
|
50
|
+
let warningFiles = 0;
|
|
41
51
|
|
|
42
52
|
for (let value of digestInfo.histogram.values()) {
|
|
43
53
|
errorCount += value.unsuppressedErrors;
|
|
@@ -52,25 +62,44 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
52
62
|
if (value.firstCorruptWarning && !corruptWarning) {
|
|
53
63
|
corruptWarning = value.firstCorruptWarning;
|
|
54
64
|
}
|
|
55
|
-
if (value.unsuppressedErrors > 0) {
|
|
56
|
-
failingFiles++;
|
|
57
|
-
}
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
for (let value of digestInfo.byFile
|
|
67
|
+
for (let [file, value] of digestInfo.byFile) {
|
|
61
68
|
for (let error of value.latestErrors.slice(-MAX_COUNT_PER_FILE)) {
|
|
62
69
|
errors.push({
|
|
70
|
+
file,
|
|
63
71
|
errorsInFile: value.errors,
|
|
64
72
|
warningsInFile: value.warnings,
|
|
65
73
|
message: `${error.param0} (${error.__NAME__})`,
|
|
66
74
|
messageTime: formatDateTime(error.time),
|
|
67
75
|
});
|
|
68
76
|
}
|
|
77
|
+
|
|
78
|
+
for (let warning of value.latestWarnings.slice(-MAX_COUNT_PER_FILE)) {
|
|
79
|
+
warnings.push({
|
|
80
|
+
file,
|
|
81
|
+
errorsInFile: value.errors,
|
|
82
|
+
warningsInFile: value.warnings,
|
|
83
|
+
message: `${warning.param0} (${warning.__NAME__})`,
|
|
84
|
+
messageTime: formatDateTime(warning.time),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (value.errors > 0) {
|
|
89
|
+
failingFiles++;
|
|
90
|
+
errorFiles++;
|
|
91
|
+
}
|
|
92
|
+
if (value.warnings > 0) {
|
|
93
|
+
warningFiles++;
|
|
94
|
+
}
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
sort(errors, x => -x.errorsInFile);
|
|
72
98
|
errors = errors.slice(0, MAX_COUNT);
|
|
73
99
|
|
|
100
|
+
sort(warnings, x => -x.warningsInFile);
|
|
101
|
+
warnings = warnings.slice(0, MAX_COUNT);
|
|
102
|
+
|
|
74
103
|
let link = createLink([
|
|
75
104
|
showingManagementURL.getOverride(true),
|
|
76
105
|
managementPageURL.getOverride("ErrorDigestPage"),
|
|
@@ -81,7 +110,7 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
81
110
|
await sendEmail({
|
|
82
111
|
to: notifyEmails,
|
|
83
112
|
fromPrefix: "error-digest",
|
|
84
|
-
subject: `${errorCount} err |
|
|
113
|
+
subject: `${errorCount} err | ~${formatNumber(failingFiles)} lines | ${warningCount} warn${corruptErrors + corruptWarnings > 0 ? ` | ${corruptErrors + corruptWarnings} corrupt` : ""} | ${suppressedErrors + suppressedWarnings} hid | ${formatTime(digestInfo.scanDuration)} | ${formatNumber(digestInfo.totalUncompressedBytes)} | ${formatNumber(digestInfo.totalFiles)} log files`,
|
|
85
114
|
contents: <div>
|
|
86
115
|
<h2>Error Summary</h2>
|
|
87
116
|
<ul style="list-style-type: none; padding-left: 0;">
|
|
@@ -89,12 +118,14 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
89
118
|
<strong style="color: #dc3545;">Errors:</strong>
|
|
90
119
|
<span style="background-color: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-weight: bold; margin-left: 8px;">{errorCount}</span>
|
|
91
120
|
<span style="color: #dc3545;"> unsuppressed</span>
|
|
121
|
+
<span style="color: #6c757d; margin-left: 8px;">({formatNumber(errorFiles)} files)</span>
|
|
92
122
|
{suppressedErrors > 0 && <span>, <span style="color: #6c757d;">{formatNumber(suppressedErrors)} suppressed</span></span>}
|
|
93
123
|
</li>
|
|
94
124
|
<li>
|
|
95
125
|
<strong style="color: #fd7e14;">Warnings:</strong>
|
|
96
126
|
<span style="background-color: #fd7e14; color: white; padding: 2px 6px; border-radius: 3px; font-weight: bold; margin-left: 8px;">{warningCount}</span>
|
|
97
127
|
<span style="color: #fd7e14;"> unsuppressed</span>
|
|
128
|
+
<span style="color: #6c757d; margin-left: 8px;">({formatNumber(warningFiles)} files)</span>
|
|
98
129
|
{suppressedWarnings > 0 && <span>, <span style="color: #6c757d;">{formatNumber(suppressedWarnings)} suppressed</span></span>}
|
|
99
130
|
</li>
|
|
100
131
|
</ul>
|
|
@@ -147,11 +178,12 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
147
178
|
<a href={link} style="display: block; margin-bottom: 20px; padding: 10px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; text-align: center;">View live logs</a>
|
|
148
179
|
|
|
149
180
|
{errors.length > 0 && <div>
|
|
150
|
-
<h2 style="color: #495057;">Recent Errors (<span style="color: #dc3545; font-weight: bold;">{errors.length}</span> shown)</h2>
|
|
181
|
+
<h2 style="color: #495057;">Recent Errors (<span style="color: #dc3545; font-weight: bold;">{errors.length}</span> files shown, <span style="color: #dc3545; font-weight: bold;">{errors.reduce((acc, error) => acc + error.errorsInFile, 0)}</span> errors)</h2>
|
|
151
182
|
<table style="border-collapse: collapse; width: 100%; margin-top: 10px;">
|
|
152
183
|
<thead>
|
|
153
184
|
<tr style="background-color: #495057; color: white;">
|
|
154
185
|
<th style="border: 1px solid #6c757d; padding: 8px; text-align: left;">Time</th>
|
|
186
|
+
<th style="border: 1px solid #6c757d; padding: 8px; text-align: left;">File</th>
|
|
155
187
|
<th style="border: 1px solid #6c757d; padding: 8px; text-align: left;">Message</th>
|
|
156
188
|
<th style="border: 1px solid #6c757d; padding: 8px; text-align: right; background-color: #dc3545;">Errors in File</th>
|
|
157
189
|
<th style="border: 1px solid #6c757d; padding: 8px; text-align: right; background-color: #fd7e14;">Warnings in File</th>
|
|
@@ -161,6 +193,7 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
161
193
|
{errors.map((error, index) => (
|
|
162
194
|
<tr key={index} style={`background-color: ${index % 2 === 0 ? "#f8f9fa" : "white"}`}>
|
|
163
195
|
<td style="border: 1px solid #ddd; padding: 8px;">{error.messageTime}</td>
|
|
196
|
+
<td style="border: 1px solid #ddd; padding: 8px; font-family: monospace; font-size: 12px;">{error.file}</td>
|
|
164
197
|
<td style="border: 1px solid #ddd; padding: 8px;">{error.message}</td>
|
|
165
198
|
<td style="border: 1px solid #ddd; padding: 8px; text-align: right; background-color: #f8d7da; color: #721c24; font-weight: bold;">{error.errorsInFile}</td>
|
|
166
199
|
<td style="border: 1px solid #ddd; padding: 8px; text-align: right; background-color: #fff3cd; color: #856404; font-weight: bold;">{error.warningsInFile}</td>
|
|
@@ -169,6 +202,32 @@ export async function sendErrorDigestEmail(digestInfo: ErrorDigestInfo) {
|
|
|
169
202
|
</tbody>
|
|
170
203
|
</table>
|
|
171
204
|
</div>}
|
|
205
|
+
|
|
206
|
+
{warnings.length > 0 && <div style="margin-top: 20px;">
|
|
207
|
+
<h2 style="color: #495057;">Recent Warnings (<span style="color: #fd7e14; font-weight: bold;">{warnings.length}</span> files shown, <span style="color: #fd7e14; font-weight: bold;">{warnings.reduce((acc, warning) => acc + warning.warningsInFile, 0)}</span> warnings)</h2>
|
|
208
|
+
<table style="border-collapse: collapse; width: 100%; margin-top: 10px;">
|
|
209
|
+
<thead>
|
|
210
|
+
<tr style="background-color: #495057; color: white;">
|
|
211
|
+
<th style="border: 1px solid #6c757d; padding: 8px; text-align: left;">Time</th>
|
|
212
|
+
<th style="border: 1px solid #6c757d; padding: 8px; text-align: left;">File</th>
|
|
213
|
+
<th style="border: 1px solid #6c757d; padding: 8px; text-align: left;">Message</th>
|
|
214
|
+
<th style="border: 1px solid #6c757d; padding: 8px; text-align: right; background-color: #dc3545;">Errors in File</th>
|
|
215
|
+
<th style="border: 1px solid #6c757d; padding: 8px; text-align: right; background-color: #fd7e14;">Warnings in File</th>
|
|
216
|
+
</tr>
|
|
217
|
+
</thead>
|
|
218
|
+
<tbody>
|
|
219
|
+
{warnings.map((warning, index) => (
|
|
220
|
+
<tr key={index} style={`background-color: ${index % 2 === 0 ? "#f8f9fa" : "white"}`}>
|
|
221
|
+
<td style="border: 1px solid #ddd; padding: 8px;">{warning.messageTime}</td>
|
|
222
|
+
<td style="border: 1px solid #ddd; padding: 8px; font-family: monospace; font-size: 12px;">{warning.file}</td>
|
|
223
|
+
<td style="border: 1px solid #ddd; padding: 8px;">{warning.message}</td>
|
|
224
|
+
<td style="border: 1px solid #ddd; padding: 8px; text-align: right; background-color: #f8d7da; color: #721c24; font-weight: bold;">{warning.errorsInFile}</td>
|
|
225
|
+
<td style="border: 1px solid #ddd; padding: 8px; text-align: right; background-color: #fff3cd; color: #856404; font-weight: bold;">{warning.warningsInFile}</td>
|
|
226
|
+
</tr>
|
|
227
|
+
))}
|
|
228
|
+
</tbody>
|
|
229
|
+
</table>
|
|
230
|
+
</div>}
|
|
172
231
|
</div>,
|
|
173
232
|
});
|
|
174
233
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Querysub } from "../../../4-querysub/QuerysubController";
|
|
2
|
+
import { runDigest, runDigestLoop } from "./errorDigests";
|
|
2
3
|
|
|
3
4
|
async function main() {
|
|
4
|
-
|
|
5
|
+
if (process.argv.includes("--now")) {
|
|
6
|
+
await Querysub.hostService("error-digests-now");
|
|
7
|
+
|
|
8
|
+
await runDigest();
|
|
9
|
+
} else {
|
|
10
|
+
await runDigestLoop();
|
|
11
|
+
}
|
|
5
12
|
}
|
|
6
13
|
// The digest loop should never exit, and if it does, we probably want to terminate ourselves so that the service manager will restart us, hopefully putting us back in a good state.
|
|
7
14
|
main().catch(console.error).finally(() => process.exit());
|
|
@@ -92,7 +92,7 @@ function getClosest(value: number, choices: number[]) {
|
|
|
92
92
|
return closest;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
async function runDigest() {
|
|
95
|
+
export async function runDigest() {
|
|
96
96
|
console.log("Running error digest gathering");
|
|
97
97
|
// Find the previous day
|
|
98
98
|
let endTime = getClosest(
|
package/src/misc/cloneHelpers.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import cborx from "cbor-x";
|
|
2
2
|
import { lazy } from "socket-function/src/caching";
|
|
3
|
-
const cborxInstance = lazy(() => new cborx.Encoder({
|
|
3
|
+
const cborxInstance = lazy(() => new cborx.Encoder({
|
|
4
|
+
structuredClone: true,
|
|
5
|
+
|
|
6
|
+
}));
|
|
4
7
|
export function deepCloneCborx<T>(value: T): T {
|
|
5
8
|
return decodeCborx(encodeCborx(value));
|
|
6
9
|
}
|