adonisjs-server-stats 1.16.0 → 1.17.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/README.md +14 -2
- package/dist/core/core/dashboard-api.d.ts +6 -4
- package/dist/core/index.js +9 -6
- package/dist/core/types.d.ts +24 -1
- package/dist/react/{CacheSection-DkLtukWz.js → CacheSection-C-X2Tnca.js} +48 -44
- package/dist/react/{CacheTab-DWeXnEBI.js → CacheTab-D55mfmeE.js} +1 -1
- package/dist/react/{ConfigSection-DaVbFPDg.js → ConfigSection-C-HGnZfd.js} +1 -1
- package/dist/react/{ConfigTab-D4640WJZ.js → ConfigTab-CNnsOXBh.js} +1 -1
- package/dist/react/{CustomPaneTab-DglmAVMC.js → CustomPaneTab-D-267UVd.js} +1 -1
- package/dist/react/{EmailsSection-vYx6ExTb.js → EmailsSection-Bqw4DI4r.js} +65 -57
- package/dist/react/{EmailsTab-DKDrRmRH.js → EmailsTab-CkHOhj2F.js} +1 -1
- package/dist/react/{EventsSection-CapH5xut.js → EventsSection-CMeyjzVC.js} +1 -1
- package/dist/react/{EventsTab-BNVEySAm.js → EventsTab-DgS2v1HX.js} +1 -1
- package/dist/react/{JobsSection-ZIQsJWid.js → JobsSection-oWH6pbrG.js} +1 -1
- package/dist/react/{JobsTab-DXQzXrDt.js → JobsTab-zdxX_as-.js} +1 -1
- package/dist/react/{LogEntryRow-BWkHE51-.js → LogEntryRow-Cn8Ee_X0.js} +1 -1
- package/dist/react/{LogsSection-ConXdBkL.js → LogsSection-XmiI2MMs.js} +2 -2
- package/dist/react/{LogsTab-CJM47LPn.js → LogsTab-ixusY4v7.js} +2 -2
- package/dist/react/{OverviewSection-DOMu2qvl.js → OverviewSection-CvhObTLP.js} +119 -111
- package/dist/react/{QueriesSection-DFsaOSJI.js → QueriesSection-BvledwBI.js} +1 -1
- package/dist/react/{QueriesTab-CY9CG_7L.js → QueriesTab-smQQJvJa.js} +1 -1
- package/dist/react/{RequestsSection-CC-eVHsl.js → RequestsSection-DKuYpViv.js} +2 -2
- package/dist/react/{RoutesSection-p1DMq41y.js → RoutesSection-5RNVurQ6.js} +1 -1
- package/dist/react/{RoutesTab-Cnqy8UcK.js → RoutesTab-CpXNO-Rx.js} +1 -1
- package/dist/react/{SplitPaneWrapper-XgkA0QxE.js → SplitPaneWrapper-DQvJnsuk.js} +1 -1
- package/dist/react/{TimelineTab-1YOERxe5.js → TimelineTab-LHQHHKkO.js} +2 -2
- package/dist/react/components/Dashboard/overview-data-context.d.ts +15 -0
- package/dist/react/components/Dashboard/sections/OverviewSection.d.ts +9 -0
- package/dist/react/{index-DOSlCpZ9.js → index-LlFvooUN.js} +148 -141
- package/dist/react/index.js +1 -1
- package/dist/src/collectors/app_collector.d.ts +11 -0
- package/dist/src/collectors/app_collector.js +11 -0
- package/dist/src/collectors/redis_collector.js +13 -13
- package/dist/src/dashboard/cache_handlers.d.ts +17 -3
- package/dist/src/dashboard/cache_handlers.js +14 -19
- package/dist/src/dashboard/coalesce_cache.d.ts +2 -8
- package/dist/src/dashboard/coalesce_cache.js +25 -0
- package/dist/src/dashboard/dashboard_controller.d.ts +1 -0
- package/dist/src/dashboard/dashboard_controller.js +8 -3
- package/dist/src/dashboard/dashboard_store.d.ts +1 -3
- package/dist/src/dashboard/dashboard_store.js +14 -9
- package/dist/src/dashboard/dashboard_types.d.ts +2 -0
- package/dist/src/dashboard/filter_handlers.js +26 -4
- package/dist/src/dashboard/flush_manager.d.ts +2 -6
- package/dist/src/dashboard/flush_manager.js +9 -11
- package/dist/src/dashboard/format_helpers.d.ts +0 -4
- package/dist/src/dashboard/format_helpers.js +0 -19
- package/dist/src/dashboard/integrations/config_inspector.js +15 -52
- package/dist/src/dashboard/migrator_tables.js +4 -0
- package/dist/src/dashboard/paginate_helper.js +21 -11
- package/dist/src/dashboard/sensitive_patterns.d.ts +76 -0
- package/dist/src/dashboard/sensitive_patterns.js +259 -0
- package/dist/src/dashboard/write_queue.d.ts +27 -16
- package/dist/src/dashboard/write_queue.js +50 -47
- package/dist/src/debug/event_collector.d.ts +8 -0
- package/dist/src/debug/event_collector.js +12 -0
- package/dist/src/debug/query_collector.js +6 -1
- package/dist/src/debug/trace_collector.js +26 -13
- package/dist/src/edge/client/dashboard.js +2 -2
- package/dist/src/edge/client-vue/dashboard.js +5 -5
- package/dist/src/edge/views/dashboard.edge +1 -1
- package/dist/src/edge/views/debug-panel.edge +1 -1
- package/dist/src/edge/views/stats-bar.edge +1 -1
- package/dist/src/middleware/request_tracking_middleware.js +7 -2
- package/dist/src/provider/dashboard_init.d.ts +9 -1
- package/dist/src/provider/dashboard_init.js +19 -0
- package/dist/src/provider/email_helpers.js +20 -5
- package/dist/src/provider/server_stats_provider.d.ts +9 -0
- package/dist/src/provider/server_stats_provider.js +26 -7
- package/dist/src/provider/shutdown_helpers.js +3 -1
- package/dist/src/provider/toolbar_setup.js +21 -15
- package/dist/src/types.d.ts +24 -1
- package/dist/vue/CacheSection-Bm0goqX6.js +161 -0
- package/dist/vue/{ConfigSection-BiRAiaHj.js → ConfigSection-BrYslIMN.js} +1 -1
- package/dist/vue/{EmailsSection-Dl44qyqY.js → EmailsSection-B8N5vaWA.js} +89 -85
- package/dist/vue/{EventsSection-CWjeitjU.js → EventsSection-D2Y_xt0m.js} +1 -1
- package/dist/vue/{JobsSection-DOBb4LjZ.js → JobsSection-cEKqELLC.js} +1 -1
- package/dist/vue/{LogsSection-CXx-HOWJ.js → LogsSection-C3H2c0S3.js} +1 -1
- package/dist/vue/{OverviewSection-CyfNQ8uV.js → OverviewSection-I2swuHDa.js} +1 -1
- package/dist/vue/{QueriesSection-CXBsFp-y.js → QueriesSection-CF1IE3SN.js} +1 -1
- package/dist/vue/{RequestsSection-YTIaZGZd.js → RequestsSection-CuwgXzmU.js} +1 -1
- package/dist/vue/{RoutesSection-CDKMey49.js → RoutesSection-BhJCF7Rn.js} +1 -1
- package/dist/vue/composables/useDashboardData.d.ts +1 -1
- package/dist/vue/{index-14x39RI_.js → index-DUsImuIN.js} +95 -95
- package/dist/vue/index.js +1 -1
- package/package.json +12 -3
- package/dist/src/dashboard/explain_query.d.ts +0 -8
- package/dist/src/dashboard/explain_query.js +0 -22
- package/dist/src/provider/boot_initializer.d.ts +0 -28
- package/dist/src/provider/boot_initializer.js +0 -35
- package/dist/vue/CacheSection-CxEBVVkF.js +0 -156
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* be tested in isolation.
|
|
7
7
|
*/
|
|
8
8
|
import { round } from '../utils/math_helpers.js';
|
|
9
|
+
import { yieldToEventLoop } from './migrator_tables.js';
|
|
10
|
+
import { sanitizeBindings, sanitizeRecordValues } from './sensitive_patterns.js';
|
|
9
11
|
// ---------------------------------------------------------------------------
|
|
10
12
|
// Warn-once tracking for write-path catch blocks
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
@@ -16,6 +18,14 @@ export function hasWarned(path) {
|
|
|
16
18
|
export function markWarned(path) {
|
|
17
19
|
warnedWritePaths.add(path);
|
|
18
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Forget which write paths have warned. Called when a DashboardStore stops so
|
|
23
|
+
* a re-initialized store in the same process warns afresh instead of
|
|
24
|
+
* inheriting a spent latch from its predecessor.
|
|
25
|
+
*/
|
|
26
|
+
export function resetWriteWarnings() {
|
|
27
|
+
warnedWritePaths.clear();
|
|
28
|
+
}
|
|
19
29
|
// ---------------------------------------------------------------------------
|
|
20
30
|
// SQL normalization
|
|
21
31
|
// ---------------------------------------------------------------------------
|
|
@@ -36,32 +46,11 @@ export function normalizeSql(sql) {
|
|
|
36
46
|
.trim();
|
|
37
47
|
}
|
|
38
48
|
// ---------------------------------------------------------------------------
|
|
39
|
-
// Binding hygiene
|
|
49
|
+
// Binding hygiene — implementation lives in sensitive_patterns.ts so the
|
|
50
|
+
// in-memory QueryCollector can sanitize at capture time without importing the
|
|
51
|
+
// write path. Re-exported here for existing consumers.
|
|
40
52
|
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
const MAX_BINDING_LEN = 256;
|
|
43
|
-
/**
|
|
44
|
-
* Redact/truncate SQL bindings before persistence so secret-looking values
|
|
45
|
-
* (long tokens, hashes, keys) are not stored in cleartext.
|
|
46
|
-
*
|
|
47
|
-
* Conservative: only long strings are truncated; short values (ids, flags,
|
|
48
|
-
* emails, ordinary params) pass through so normal capture is unaffected.
|
|
49
|
-
*/
|
|
50
|
-
export function sanitizeBindings(bindings) {
|
|
51
|
-
if (Array.isArray(bindings))
|
|
52
|
-
return bindings.map(sanitizeBindings);
|
|
53
|
-
if (typeof bindings === 'string' && bindings.length > MAX_BINDING_LEN) {
|
|
54
|
-
return bindings.slice(0, MAX_BINDING_LEN) + `…[truncated ${bindings.length} chars]`;
|
|
55
|
-
}
|
|
56
|
-
if (bindings && typeof bindings === 'object') {
|
|
57
|
-
const out = {};
|
|
58
|
-
for (const [k, v] of Object.entries(bindings)) {
|
|
59
|
-
out[k] = sanitizeBindings(v);
|
|
60
|
-
}
|
|
61
|
-
return out;
|
|
62
|
-
}
|
|
63
|
-
return bindings;
|
|
64
|
-
}
|
|
53
|
+
export { sanitizeBindings };
|
|
65
54
|
// ---------------------------------------------------------------------------
|
|
66
55
|
// Pure data-prep functions
|
|
67
56
|
// ---------------------------------------------------------------------------
|
|
@@ -78,13 +67,14 @@ export function prepareRequestRows(requests) {
|
|
|
78
67
|
.map((q) => ({
|
|
79
68
|
sql_text: q.sql,
|
|
80
69
|
sql_normalized: normalizeSql(q.sql),
|
|
81
|
-
bindings: q.bindings ? JSON.stringify(sanitizeBindings(q.bindings)) : null,
|
|
70
|
+
bindings: q.bindings ? JSON.stringify(sanitizeBindings(q.bindings, q.sql)) : null,
|
|
82
71
|
duration: round(q.duration),
|
|
83
72
|
method: q.method,
|
|
84
73
|
model: q.model,
|
|
85
74
|
connection: q.connection,
|
|
86
75
|
in_transaction: q.inTransaction ? 1 : 0,
|
|
87
76
|
})),
|
|
77
|
+
eventRows: buildEventRows(input.events ?? []),
|
|
88
78
|
traceRow: input.trace
|
|
89
79
|
? {
|
|
90
80
|
method: input.trace.method,
|
|
@@ -100,6 +90,13 @@ export function prepareRequestRows(requests) {
|
|
|
100
90
|
}
|
|
101
91
|
/**
|
|
102
92
|
* Transform raw log entries into SQLite-ready row objects.
|
|
93
|
+
*
|
|
94
|
+
* The full pino record lands in `data`, and log lines routinely carry bound
|
|
95
|
+
* request context — authorization headers, cookies, tokens in payloads — so
|
|
96
|
+
* the record is run through the shared redaction rules (secret-named keys and
|
|
97
|
+
* credential-shaped values) before it is stringified. The `message` column is
|
|
98
|
+
* kept verbatim: it is free text, and the shape rules are anchored to whole
|
|
99
|
+
* values, so prose stays readable.
|
|
103
100
|
*/
|
|
104
101
|
export function prepareLogRows(logs) {
|
|
105
102
|
return logs.map((entry) => {
|
|
@@ -110,7 +107,7 @@ export function prepareLogRows(logs) {
|
|
|
110
107
|
request_id: entry.request_id || entry.requestId || entry['x-request-id']
|
|
111
108
|
? String(entry.request_id || entry.requestId || entry['x-request-id'])
|
|
112
109
|
: null,
|
|
113
|
-
data: JSON.stringify(entry),
|
|
110
|
+
data: JSON.stringify(sanitizeRecordValues(entry)),
|
|
114
111
|
};
|
|
115
112
|
});
|
|
116
113
|
}
|
|
@@ -135,9 +132,14 @@ export function buildEmailRow(record) {
|
|
|
135
132
|
/**
|
|
136
133
|
* Transform EventRecords into SQLite-ready row objects.
|
|
137
134
|
*/
|
|
135
|
+
/**
|
|
136
|
+
* Build event rows. `request_id` is attached at insert time, once the owning
|
|
137
|
+
* request row has an id — leaving it null here (as this did previously) meant
|
|
138
|
+
* retention never reclaimed them, since events are only pruned via the
|
|
139
|
+
* `server_stats_requests` foreign-key cascade.
|
|
140
|
+
*/
|
|
138
141
|
export function buildEventRows(events) {
|
|
139
142
|
return events.map((e) => ({
|
|
140
|
-
request_id: null,
|
|
141
143
|
event_name: e.event,
|
|
142
144
|
data: e.data,
|
|
143
145
|
}));
|
|
@@ -147,9 +149,16 @@ export function buildEventRows(events) {
|
|
|
147
149
|
// ---------------------------------------------------------------------------
|
|
148
150
|
/**
|
|
149
151
|
* Insert rows into a table in batches of 50.
|
|
152
|
+
*
|
|
153
|
+
* Yields to the event loop between chunks: better-sqlite3 is fully
|
|
154
|
+
* synchronous, so without a real macrotask hop a large batch would execute
|
|
155
|
+
* back-to-back on microtask continuations and stall the host app — the exact
|
|
156
|
+
* failure mode `migrator_tables.ts` documents and guards against.
|
|
150
157
|
*/
|
|
151
158
|
export async function batchInsert(trx, table, rows) {
|
|
152
159
|
for (let i = 0; i < rows.length; i += 50) {
|
|
160
|
+
if (i > 0)
|
|
161
|
+
await yieldToEventLoop();
|
|
153
162
|
await trx(table).insert(rows.slice(i, i + 50));
|
|
154
163
|
}
|
|
155
164
|
}
|
|
@@ -176,7 +185,7 @@ function buildRequestRow(input) {
|
|
|
176
185
|
}
|
|
177
186
|
/** Insert a single prepared request with its queries and trace. */
|
|
178
187
|
async function insertOneRequest(trx, prepared) {
|
|
179
|
-
const { input, filteredQueries, traceRow } = prepared;
|
|
188
|
+
const { input, filteredQueries, traceRow, eventRows } = prepared;
|
|
180
189
|
const row = buildRequestRow(input);
|
|
181
190
|
const [requestId] = await trx('server_stats_requests').insert(row);
|
|
182
191
|
const hasId = requestId !== null && requestId !== undefined;
|
|
@@ -184,12 +193,24 @@ async function insertOneRequest(trx, prepared) {
|
|
|
184
193
|
const rows = filteredQueries.map((q) => ({ ...q, request_id: requestId }));
|
|
185
194
|
await batchInsert(trx, 'server_stats_queries', rows);
|
|
186
195
|
}
|
|
196
|
+
if (hasId && eventRows.length > 0) {
|
|
197
|
+
const rows = eventRows.map((e) => ({ ...e, request_id: requestId }));
|
|
198
|
+
await batchInsert(trx, 'server_stats_events', rows);
|
|
199
|
+
}
|
|
187
200
|
if (hasId && traceRow) {
|
|
188
201
|
await trx('server_stats_traces').insert({ ...traceRow, request_id: requestId });
|
|
189
202
|
}
|
|
190
203
|
}
|
|
191
204
|
export async function flushRequests(trx, preparedRequests) {
|
|
205
|
+
let sinceYield = 0;
|
|
192
206
|
for (const prepared of preparedRequests) {
|
|
207
|
+
// Up to ~4 synchronous statements per request; a full backlog is hundreds
|
|
208
|
+
// of them in one flush. Yield periodically so the host app's I/O keeps
|
|
209
|
+
// being serviced mid-flush (see batchInsert for why awaits alone don't).
|
|
210
|
+
if (++sinceYield >= 10) {
|
|
211
|
+
sinceYield = 0;
|
|
212
|
+
await yieldToEventLoop();
|
|
213
|
+
}
|
|
193
214
|
try {
|
|
194
215
|
await insertOneRequest(trx, prepared);
|
|
195
216
|
}
|
|
@@ -202,24 +223,6 @@ export async function flushRequests(trx, preparedRequests) {
|
|
|
202
223
|
}
|
|
203
224
|
}
|
|
204
225
|
}
|
|
205
|
-
/**
|
|
206
|
-
* Flush pending events into the database.
|
|
207
|
-
*/
|
|
208
|
-
export async function flushEvents(trx, events) {
|
|
209
|
-
for (const { events: evts } of events) {
|
|
210
|
-
try {
|
|
211
|
-
const rows = buildEventRows(evts);
|
|
212
|
-
await batchInsert(trx, 'server_stats_events', rows);
|
|
213
|
-
}
|
|
214
|
-
catch (err) {
|
|
215
|
-
if (!hasWarned('recordEvents')) {
|
|
216
|
-
markWarned('recordEvents');
|
|
217
|
-
const { log } = await import('../utils/logger.js');
|
|
218
|
-
log.warn(`dashboard: recordEvents failed — ${err?.message}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
226
|
/**
|
|
224
227
|
* Flush pending emails into the database.
|
|
225
228
|
*/
|
|
@@ -12,6 +12,14 @@ export declare class EventCollector {
|
|
|
12
12
|
private circulars;
|
|
13
13
|
private summarizeData;
|
|
14
14
|
getEvents(): EventRecord[];
|
|
15
|
+
/**
|
|
16
|
+
* Events recorded after `lastId`, oldest first.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors `QueryCollector.getQueriesSince` and drives the dashboard's
|
|
19
|
+
* request pipe, which persists whatever accumulated since the previous
|
|
20
|
+
* request completed.
|
|
21
|
+
*/
|
|
22
|
+
getEventsSince(lastId: number): EventRecord[];
|
|
15
23
|
getLatest(n?: number): EventRecord[];
|
|
16
24
|
getTotalCount(): number;
|
|
17
25
|
getBufferInfo(): {
|
|
@@ -135,6 +135,18 @@ export class EventCollector {
|
|
|
135
135
|
getEvents() {
|
|
136
136
|
return this.buffer.toArray().reverse();
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Events recorded after `lastId`, oldest first.
|
|
140
|
+
*
|
|
141
|
+
* Mirrors `QueryCollector.getQueriesSince` and drives the dashboard's
|
|
142
|
+
* request pipe, which persists whatever accumulated since the previous
|
|
143
|
+
* request completed.
|
|
144
|
+
*/
|
|
145
|
+
getEventsSince(lastId) {
|
|
146
|
+
if (lastId <= 0)
|
|
147
|
+
return this.buffer.toArray();
|
|
148
|
+
return this.buffer.collectFromEnd((e) => e.id > lastId);
|
|
149
|
+
}
|
|
138
150
|
getLatest(n = 100) {
|
|
139
151
|
return this.buffer.latest(n);
|
|
140
152
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeBindings } from '../dashboard/sensitive_patterns.js';
|
|
1
2
|
import { isExcludedRequest } from '../middleware/request_tracking_middleware.js';
|
|
2
3
|
import { round } from '../utils/math_helpers.js';
|
|
3
4
|
import { RingBuffer } from './ring_buffer.js';
|
|
@@ -14,7 +15,11 @@ function buildQueryRecord(data, id) {
|
|
|
14
15
|
return {
|
|
15
16
|
id,
|
|
16
17
|
sql: data.sql || '',
|
|
17
|
-
|
|
18
|
+
// Sanitized at capture time so the in-memory debug panel never shows a
|
|
19
|
+
// credential the persisted dashboard would have redacted. The write path
|
|
20
|
+
// sanitizes again (idempotently) as defense in depth for records loaded
|
|
21
|
+
// from an older on-disk dump.
|
|
22
|
+
bindings: sanitizeBindings(data.bindings || [], data.sql || '') ?? [],
|
|
18
23
|
duration: round(parseDuration(data.duration)),
|
|
19
24
|
method: data.method || 'unknown',
|
|
20
25
|
model: data.model || null,
|
|
@@ -7,11 +7,16 @@ import { RingBuffer } from './ring_buffer.js';
|
|
|
7
7
|
*/
|
|
8
8
|
const globalRef = { current: null };
|
|
9
9
|
/**
|
|
10
|
-
* Tracks
|
|
11
|
-
* second instance
|
|
12
|
-
*
|
|
10
|
+
* Tracks which TraceCollector currently owns the console.warn patch, so a
|
|
11
|
+
* second instance cannot double-wrap it and leak the patch permanently. The
|
|
12
|
+
* owner (not a boolean) matters: if a prior instance never ran stop(), its
|
|
13
|
+
* wrapper checks the *old* AsyncLocalStorage and silently stops attaching
|
|
14
|
+
* warnings to traces — a newcomer must be able to detect that and take over.
|
|
13
15
|
*/
|
|
14
|
-
const consoleWarnPatch = {
|
|
16
|
+
const consoleWarnPatch = {
|
|
17
|
+
owner: null,
|
|
18
|
+
original: null,
|
|
19
|
+
};
|
|
15
20
|
/**
|
|
16
21
|
* Wrap an async function in a traced span.
|
|
17
22
|
*
|
|
@@ -157,12 +162,18 @@ export class TraceCollector {
|
|
|
157
162
|
};
|
|
158
163
|
emitter.on('db:query', this.dbHandler);
|
|
159
164
|
}
|
|
160
|
-
// Intercept console.warn to capture warnings per-request.
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
// Intercept console.warn to capture warnings per-request. If a prior
|
|
166
|
+
// instance still owns the patch (its stop() was skipped), restore its
|
|
167
|
+
// original first and take over — deferring to the stale owner would leave
|
|
168
|
+
// a wrapper bound to a dead AsyncLocalStorage, silently detaching
|
|
169
|
+
// warnings from every trace. Never wrap the wrapper: the restore below
|
|
170
|
+
// guarantees `console.warn` is the real one before we capture it.
|
|
171
|
+
if (consoleWarnPatch.owner !== this) {
|
|
172
|
+
if (consoleWarnPatch.owner && consoleWarnPatch.original) {
|
|
173
|
+
console.warn = consoleWarnPatch.original;
|
|
174
|
+
}
|
|
175
|
+
consoleWarnPatch.owner = this;
|
|
176
|
+
consoleWarnPatch.original = console.warn;
|
|
166
177
|
this.originalConsoleWarn = console.warn;
|
|
167
178
|
console.warn = (...args) => {
|
|
168
179
|
const ctx = this.als.getStore();
|
|
@@ -178,10 +189,12 @@ export class TraceCollector {
|
|
|
178
189
|
if (this.emitter && this.dbHandler) {
|
|
179
190
|
this.emitter.off('db:query', this.dbHandler);
|
|
180
191
|
}
|
|
181
|
-
// Only restore console.warn if this instance
|
|
182
|
-
|
|
192
|
+
// Only restore console.warn if this instance still owns the patch — a
|
|
193
|
+
// newer instance may have taken over, and restoring would clobber it.
|
|
194
|
+
if (consoleWarnPatch.owner === this && this.originalConsoleWarn) {
|
|
183
195
|
console.warn = this.originalConsoleWarn;
|
|
184
|
-
consoleWarnPatch.
|
|
196
|
+
consoleWarnPatch.owner = null;
|
|
197
|
+
consoleWarnPatch.original = null;
|
|
185
198
|
}
|
|
186
199
|
this.dbHandler = null;
|
|
187
200
|
this.emitter = null;
|