querysub 0.688.0 → 0.690.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 +1 -1
- package/src/diagnostics/logs/IndexedLogs/bufferSearchFindMatcher.ts +6 -0
- package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +8 -4
- package/src/diagnostics/logs/errorNotifications2/ErrorWarning.tsx +2 -2
- package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +4 -4
- package/src/functional/UndoWatch.tsx +1 -1
package/package.json
CHANGED
|
@@ -109,6 +109,12 @@ function getSimplerStructure(structure: MatchStructure): Buffer[][] {
|
|
|
109
109
|
}
|
|
110
110
|
export const createMatchesPatternCached = cacheLimited(1000, (pattern: string) => createMatchesPattern(Buffer.from(pattern), false));
|
|
111
111
|
|
|
112
|
+
// Suppression patterns are usually copied out of a UI that renders multi-line errors on a single line, so the copied text has plain spaces where the real error has line breaks and indentation — those patterns can never match unless whitespace is collapsed on both sides. The matched subject is JSON-stringified, where whitespace appears as escape sequences rather than raw bytes, so both forms collapse to one space. This must NOT be applied to log-index searches: the on-disk index units are built from the raw bytes, so a normalized query would miss them.
|
|
113
|
+
const WHITESPACE_OR_JSON_ESCAPED_WHITESPACE_RUN = /(?:\s|\\n|\\r|\\t)+/g;
|
|
114
|
+
export function normalizeMatchWhitespace(text: string): string {
|
|
115
|
+
return text.replace(WHITESPACE_OR_JSON_ESCAPED_WHITESPACE_RUN, " ");
|
|
116
|
+
}
|
|
117
|
+
|
|
112
118
|
// Each WILD_CARD_BYTE in pattern acts as a multi-byte wildcard: the segments on either
|
|
113
119
|
// side must appear in order somewhere within buffer.
|
|
114
120
|
// Returns a function that matches buffers against the pre-processed pattern.
|
|
@@ -10,7 +10,7 @@ import { isPublic } from "../../../config";
|
|
|
10
10
|
import { MachineThreadInfo } from "../../MachineThreadInfo";
|
|
11
11
|
import { ErrorWarning } from "./ErrorWarning";
|
|
12
12
|
import { getErrorNotificationsManager } from "./errorWatcher";
|
|
13
|
-
import { createMatchesPattern } from "../IndexedLogs/bufferSearchFindMatcher";
|
|
13
|
+
import { createMatchesPattern, normalizeMatchWhitespace } from "../IndexedLogs/bufferSearchFindMatcher";
|
|
14
14
|
import { cacheLimited } from "socket-function/src/caching";
|
|
15
15
|
import { managementPageURL, showingManagementURL } from "../../managementPages";
|
|
16
16
|
import { searchTextURL, readProductionLogsURL } from "../IndexedLogs/LogViewerParams";
|
|
@@ -58,9 +58,13 @@ function TicketButton(props: {
|
|
|
58
58
|
qreact.inline(TicketButton);
|
|
59
59
|
|
|
60
60
|
export let getMatcher = cacheLimited(100, (pattern: string) =>
|
|
61
|
-
createMatchesPattern(Buffer.from(pattern), false)
|
|
61
|
+
createMatchesPattern(Buffer.from(normalizeMatchWhitespace(pattern)), false)
|
|
62
62
|
);
|
|
63
63
|
|
|
64
|
+
export function getMatchBuffer(datum: LogDatum): Buffer {
|
|
65
|
+
return Buffer.from(normalizeMatchWhitespace(JSON.stringify(datum)));
|
|
66
|
+
}
|
|
67
|
+
|
|
64
68
|
export class LogDatumRenderer extends qreact.Component<{
|
|
65
69
|
datum: LogDatum;
|
|
66
70
|
inlineMode?: boolean;
|
|
@@ -81,7 +85,7 @@ export class LogDatumRenderer extends qreact.Component<{
|
|
|
81
85
|
if (!this.manager.state.isLoading && !this.props.hideSuppression) {
|
|
82
86
|
for (let suppression of this.manager.state.suppressionEntries) {
|
|
83
87
|
let matcher = getMatcher(suppression.pattern);
|
|
84
|
-
let errorBuffer =
|
|
88
|
+
let errorBuffer = getMatchBuffer(datum);
|
|
85
89
|
if (matcher(errorBuffer)) {
|
|
86
90
|
matchingSuppressions.push(suppression);
|
|
87
91
|
}
|
|
@@ -461,7 +465,7 @@ class SuppressionItem extends qreact.Component<{
|
|
|
461
465
|
if (example) return example;
|
|
462
466
|
// No example recorded for this suppression — find any live error matching the pattern, so the ticket stores a real, full log entry instead of a synthetic stub.
|
|
463
467
|
let matcher = getMatcher(suppression.pattern);
|
|
464
|
-
let fallback = this.manager.state.unmatchedErrors.find(e => matcher(
|
|
468
|
+
let fallback = this.manager.state.unmatchedErrors.find(e => matcher(getMatchBuffer(e)));
|
|
465
469
|
return fallback || {
|
|
466
470
|
time: Date.now(),
|
|
467
471
|
__LOG_TYPE: "error",
|
|
@@ -4,7 +4,7 @@ import { css } from "typesafecss";
|
|
|
4
4
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
5
5
|
import { ATag } from "../../../library-components/ATag";
|
|
6
6
|
import { managementPageURL, showingManagementURL } from "../../managementPages";
|
|
7
|
-
import { getMatcher, LogDatumRenderer } from "./ErrorNotificationPage";
|
|
7
|
+
import { getMatcher, getMatchBuffer, LogDatumRenderer } from "./ErrorNotificationPage";
|
|
8
8
|
import { getErrorNotificationsManager } from "./errorWatcher";
|
|
9
9
|
import { LogDatum } from "../diskLogger";
|
|
10
10
|
import { TicketsController } from "../errorTickets/tickets";
|
|
@@ -58,7 +58,7 @@ export class ErrorWarning extends qreact.Component {
|
|
|
58
58
|
openTicketPatterns.push(ticket.suppressionPattern);
|
|
59
59
|
}
|
|
60
60
|
const isCoveredByOpenTicket = (datum: LogDatum) => {
|
|
61
|
-
let errorBuffer =
|
|
61
|
+
let errorBuffer = getMatchBuffer(datum);
|
|
62
62
|
return openTicketPatterns.some(pattern => getMatcher(pattern)(errorBuffer));
|
|
63
63
|
};
|
|
64
64
|
|
|
@@ -3,7 +3,7 @@ import { archiveJSONT } from "../../../-a-archives/archivesJSONT";
|
|
|
3
3
|
import { Querysub } from "../../../4-querysub/Querysub";
|
|
4
4
|
import { getDomain, isPublic } from "../../../config";
|
|
5
5
|
import { MachineInfo } from "../../../deployManager/machineSchema";
|
|
6
|
-
import { createMatchesPattern } from "../IndexedLogs/bufferSearchFindMatcher";
|
|
6
|
+
import { createMatchesPattern, normalizeMatchWhitespace } from "../IndexedLogs/bufferSearchFindMatcher";
|
|
7
7
|
import { LogDatum, getErrorLogs } from "../diskLogger";
|
|
8
8
|
import { watchAllValues } from "./logWatcher";
|
|
9
9
|
import { batchFunction, runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
@@ -55,7 +55,7 @@ export async function getSuppressionEntries(): Promise<SuppressionEntry[]> {
|
|
|
55
55
|
return await suppression.values();
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
let getMatcher = cacheLimited(10000, (text: string) => createMatchesPattern(Buffer.from(text), false));
|
|
58
|
+
let getMatcher = cacheLimited(10000, (text: string) => createMatchesPattern(Buffer.from(normalizeMatchWhitespace(text)), false));
|
|
59
59
|
|
|
60
60
|
// The watch loop checks every incoming error against the suppression list. Reading the suppression archive once per error produced thousands of storage listings per minute, so the hot path is served from a cached copy: the first pass tolerates 15 minute staleness, and only errors that pass unsuppressed force a fresher (30 second) re-read.
|
|
61
61
|
const SUPPRESSION_CACHE_TTL = timeInMinute * 15;
|
|
@@ -89,7 +89,7 @@ function invalidateSuppressionEntriesCaches() {
|
|
|
89
89
|
|
|
90
90
|
// Side-effect-free version of applySuppression's "suppressed" verdict, for deciding which entry set the watch loop should apply against.
|
|
91
91
|
function isSuppressedByAny(error: LogDatum, suppressionEntries: SuppressionEntry[]): boolean {
|
|
92
|
-
let errorBuffer = Buffer.from(JSON.stringify(error));
|
|
92
|
+
let errorBuffer = Buffer.from(normalizeMatchWhitespace(JSON.stringify(error)));
|
|
93
93
|
for (let suppressionEntry of suppressionEntries) {
|
|
94
94
|
let isMatch = getMatcher(suppressionEntry.pattern);
|
|
95
95
|
if (isMatch(errorBuffer) && error.time <= suppressionEntry.timeout) {
|
|
@@ -125,7 +125,7 @@ export function getChunkEndTime(chunk: number) {
|
|
|
125
125
|
type SuppressionStatus = "suppressed" | "matched-timeout-passed" | "no-match";
|
|
126
126
|
|
|
127
127
|
function applySuppression(error: LogDatum, suppressionEntry: SuppressionEntry): SuppressionStatus {
|
|
128
|
-
let errorBuffer = Buffer.from(JSON.stringify(error));
|
|
128
|
+
let errorBuffer = Buffer.from(normalizeMatchWhitespace(JSON.stringify(error)));
|
|
129
129
|
let isMatch = getMatcher(suppressionEntry.pattern);
|
|
130
130
|
if (!isMatch(errorBuffer)) return "no-match";
|
|
131
131
|
|
|
@@ -24,7 +24,7 @@ const MAX_UNDO_STACK = 200;
|
|
|
24
24
|
neverSet: t.stringOptional,
|
|
25
25
|
}
|
|
26
26
|
),
|
|
27
|
-
|
|
27
|
+
*/
|
|
28
28
|
export class UndoWatch extends qreact.Component<{
|
|
29
29
|
// Tracks changes to this data, and undoes it when ctrl+z is pressed
|
|
30
30
|
// - Depends on undo regions being defined in the schema, via t.undoRegion
|