@zintrust/trace 0.4.75
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 +288 -0
- package/dist/build-manifest.json +365 -0
- package/dist/cli-register.d.ts +9 -0
- package/dist/cli-register.js +32 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +38 -0
- package/dist/context.d.ts +18 -0
- package/dist/context.js +86 -0
- package/dist/dashboard/handlers.d.ts +15 -0
- package/dist/dashboard/handlers.js +179 -0
- package/dist/dashboard/routes.d.ts +19 -0
- package/dist/dashboard/routes.js +50 -0
- package/dist/dashboard/ui.d.ts +2 -0
- package/dist/dashboard/ui.js +870 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +50 -0
- package/dist/migrations/20260331000001_create_zin_debugger_entries_table.d.ts +10 -0
- package/dist/migrations/20260331000001_create_zin_debugger_entries_table.js +28 -0
- package/dist/migrations/20260331000002_create_zin_debugger_entries_tags_table.d.ts +10 -0
- package/dist/migrations/20260331000002_create_zin_debugger_entries_tags_table.js +21 -0
- package/dist/migrations/20260331000003_create_zin_debugger_monitoring_table.d.ts +10 -0
- package/dist/migrations/20260331000003_create_zin_debugger_monitoring_table.js +17 -0
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.js +4 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +3 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +140 -0
- package/dist/storage/DebuggerStorage.d.ts +13 -0
- package/dist/storage/DebuggerStorage.js +195 -0
- package/dist/storage/TraceStorage.d.ts +13 -0
- package/dist/storage/TraceStorage.js +195 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.js +1 -0
- package/dist/types.d.ts +270 -0
- package/dist/types.js +25 -0
- package/dist/ui.d.ts +8 -0
- package/dist/ui.js +7 -0
- package/dist/utils/authTag.d.ts +5 -0
- package/dist/utils/authTag.js +18 -0
- package/dist/utils/familyHash.d.ts +1 -0
- package/dist/utils/familyHash.js +8 -0
- package/dist/utils/redact.d.ts +6 -0
- package/dist/utils/redact.js +49 -0
- package/dist/utils/requestFilter.d.ts +4 -0
- package/dist/utils/requestFilter.js +26 -0
- package/dist/utils/stackFrame.d.ts +6 -0
- package/dist/utils/stackFrame.js +38 -0
- package/dist/watchers/AuthWatcher.d.ts +6 -0
- package/dist/watchers/AuthWatcher.js +49 -0
- package/dist/watchers/BatchWatcher.d.ts +6 -0
- package/dist/watchers/BatchWatcher.js +46 -0
- package/dist/watchers/CacheWatcher.d.ts +6 -0
- package/dist/watchers/CacheWatcher.js +51 -0
- package/dist/watchers/CommandWatcher.d.ts +6 -0
- package/dist/watchers/CommandWatcher.js +49 -0
- package/dist/watchers/DumpWatcher.d.ts +7 -0
- package/dist/watchers/DumpWatcher.js +41 -0
- package/dist/watchers/EventWatcher.d.ts +6 -0
- package/dist/watchers/EventWatcher.js +42 -0
- package/dist/watchers/ExceptionWatcher.d.ts +4 -0
- package/dist/watchers/ExceptionWatcher.js +103 -0
- package/dist/watchers/GateWatcher.d.ts +6 -0
- package/dist/watchers/GateWatcher.js +45 -0
- package/dist/watchers/HttpClientWatcher.d.ts +6 -0
- package/dist/watchers/HttpClientWatcher.js +50 -0
- package/dist/watchers/HttpWatcher.d.ts +2 -0
- package/dist/watchers/HttpWatcher.js +71 -0
- package/dist/watchers/JobWatcher.d.ts +10 -0
- package/dist/watchers/JobWatcher.js +108 -0
- package/dist/watchers/LogWatcher.d.ts +2 -0
- package/dist/watchers/LogWatcher.js +50 -0
- package/dist/watchers/MailWatcher.d.ts +6 -0
- package/dist/watchers/MailWatcher.js +45 -0
- package/dist/watchers/MiddlewareWatcher.d.ts +6 -0
- package/dist/watchers/MiddlewareWatcher.js +41 -0
- package/dist/watchers/ModelWatcher.d.ts +6 -0
- package/dist/watchers/ModelWatcher.js +42 -0
- package/dist/watchers/NotificationWatcher.d.ts +6 -0
- package/dist/watchers/NotificationWatcher.js +42 -0
- package/dist/watchers/QueryWatcher.d.ts +2 -0
- package/dist/watchers/QueryWatcher.js +72 -0
- package/dist/watchers/RedisWatcher.d.ts +7 -0
- package/dist/watchers/RedisWatcher.js +38 -0
- package/dist/watchers/ScheduleWatcher.d.ts +6 -0
- package/dist/watchers/ScheduleWatcher.js +46 -0
- package/dist/watchers/ViewWatcher.d.ts +6 -0
- package/dist/watchers/ViewWatcher.js +36 -0
- package/package.json +59 -0
- package/src/cli-register.ts +63 -0
- package/src/config.ts +46 -0
- package/src/context.ts +101 -0
- package/src/dashboard/handlers.ts +197 -0
- package/src/dashboard/routes.ts +101 -0
- package/src/dashboard/ui.ts +879 -0
- package/src/dashboard/zintrust-debuger.svg +30 -0
- package/src/index.ts +88 -0
- package/src/plugin.ts +9 -0
- package/src/register.ts +219 -0
- package/src/storage/TraceStorage.ts +306 -0
- package/src/storage/index.ts +2 -0
- package/src/types.ts +317 -0
- package/src/ui.ts +9 -0
- package/src/utils/authTag.ts +20 -0
- package/src/utils/familyHash.ts +8 -0
- package/src/utils/redact.ts +64 -0
- package/src/utils/requestFilter.ts +33 -0
- package/src/utils/stackFrame.ts +44 -0
- package/src/watchers/AuthWatcher.ts +50 -0
- package/src/watchers/BatchWatcher.ts +52 -0
- package/src/watchers/CacheWatcher.ts +58 -0
- package/src/watchers/CommandWatcher.ts +55 -0
- package/src/watchers/DumpWatcher.ts +42 -0
- package/src/watchers/EventWatcher.ts +43 -0
- package/src/watchers/ExceptionWatcher.ts +114 -0
- package/src/watchers/GateWatcher.ts +50 -0
- package/src/watchers/HttpClientWatcher.ts +56 -0
- package/src/watchers/HttpWatcher.ts +94 -0
- package/src/watchers/JobWatcher.ts +121 -0
- package/src/watchers/LogWatcher.ts +61 -0
- package/src/watchers/MailWatcher.ts +47 -0
- package/src/watchers/MiddlewareWatcher.ts +42 -0
- package/src/watchers/ModelWatcher.ts +48 -0
- package/src/watchers/NotificationWatcher.ts +43 -0
- package/src/watchers/QueryWatcher.ts +85 -0
- package/src/watchers/RedisWatcher.ts +39 -0
- package/src/watchers/ScheduleWatcher.ts +54 -0
- package/src/watchers/ViewWatcher.ts +37 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthWatcher — records login/logout/failed auth events.
|
|
3
|
+
* Credentials are never stored; only the outcome.
|
|
4
|
+
*/
|
|
5
|
+
import { TraceContext } from '../context';
|
|
6
|
+
import { EntryType } from '../types';
|
|
7
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
8
|
+
let _storage = null;
|
|
9
|
+
let _ignoreRoutes = [];
|
|
10
|
+
const emit = (event, userId) => {
|
|
11
|
+
if (!_storage)
|
|
12
|
+
return;
|
|
13
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
14
|
+
return;
|
|
15
|
+
const content = {
|
|
16
|
+
event,
|
|
17
|
+
userId,
|
|
18
|
+
hostname: TraceContext.getHostname(),
|
|
19
|
+
};
|
|
20
|
+
const tags = [];
|
|
21
|
+
if (userId)
|
|
22
|
+
tags.push(`Auth:${userId}`);
|
|
23
|
+
if (event === 'failed')
|
|
24
|
+
tags.push('failed');
|
|
25
|
+
_storage
|
|
26
|
+
.writeEntry({
|
|
27
|
+
uuid: crypto.randomUUID(),
|
|
28
|
+
batchId: TraceContext.getBatchId(),
|
|
29
|
+
type: EntryType.AUTH,
|
|
30
|
+
content,
|
|
31
|
+
tags,
|
|
32
|
+
isLatest: true,
|
|
33
|
+
createdAt: TraceContext.now(),
|
|
34
|
+
})
|
|
35
|
+
.catch(() => undefined);
|
|
36
|
+
};
|
|
37
|
+
export const AuthWatcher = Object.freeze({
|
|
38
|
+
emit,
|
|
39
|
+
register({ storage, config }) {
|
|
40
|
+
if (config.watchers.auth === false)
|
|
41
|
+
return () => undefined;
|
|
42
|
+
_storage = storage;
|
|
43
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
44
|
+
return () => {
|
|
45
|
+
_storage = null;
|
|
46
|
+
_ignoreRoutes = [];
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BatchContent, ITraceWatcher } from '../types';
|
|
2
|
+
declare const emit: (name: string, total: number, processed: number, failed: number, status: BatchContent["status"]) => void;
|
|
3
|
+
export declare const BatchWatcher: ITraceWatcher & {
|
|
4
|
+
emit: typeof emit;
|
|
5
|
+
};
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { TraceContext } from '../context';
|
|
2
|
+
import { EntryType } from '../types';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
4
|
+
let _storage = null;
|
|
5
|
+
let _ignoreRoutes = [];
|
|
6
|
+
const emit = (name, total, processed, failed, status) => {
|
|
7
|
+
if (!_storage)
|
|
8
|
+
return;
|
|
9
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
10
|
+
return;
|
|
11
|
+
const tags = [name];
|
|
12
|
+
if (failed > 0)
|
|
13
|
+
tags.push('failed');
|
|
14
|
+
const content = {
|
|
15
|
+
name,
|
|
16
|
+
total,
|
|
17
|
+
processed,
|
|
18
|
+
failed,
|
|
19
|
+
status,
|
|
20
|
+
hostname: TraceContext.getHostname(),
|
|
21
|
+
};
|
|
22
|
+
_storage
|
|
23
|
+
.writeEntry({
|
|
24
|
+
uuid: crypto.randomUUID(),
|
|
25
|
+
batchId: TraceContext.getBatchId(),
|
|
26
|
+
type: EntryType.BATCH,
|
|
27
|
+
content,
|
|
28
|
+
tags,
|
|
29
|
+
isLatest: true,
|
|
30
|
+
createdAt: TraceContext.now(),
|
|
31
|
+
})
|
|
32
|
+
.catch(() => undefined);
|
|
33
|
+
};
|
|
34
|
+
export const BatchWatcher = Object.freeze({
|
|
35
|
+
emit,
|
|
36
|
+
register({ storage, config }) {
|
|
37
|
+
if (config.watchers.batch === false)
|
|
38
|
+
return () => undefined;
|
|
39
|
+
_storage = storage;
|
|
40
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
41
|
+
return () => {
|
|
42
|
+
_storage = null;
|
|
43
|
+
_ignoreRoutes = [];
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CacheContent, ITraceWatcher } from '../types';
|
|
2
|
+
declare const emit: (operation: CacheContent["operation"], key: string, duration: number, hit?: boolean) => void;
|
|
3
|
+
export declare const CacheWatcher: ITraceWatcher & {
|
|
4
|
+
emit: typeof emit;
|
|
5
|
+
};
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CacheWatcher — records cache operations.
|
|
3
|
+
* Call CacheWatcher.emit() from within your cache driver instrumentation.
|
|
4
|
+
*/
|
|
5
|
+
import { TraceContext } from '../context';
|
|
6
|
+
import { EntryType } from '../types';
|
|
7
|
+
import { AuthTag } from '../utils/authTag';
|
|
8
|
+
import { redactString } from '../utils/redact';
|
|
9
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
10
|
+
let _storage = null;
|
|
11
|
+
let _redactionFields = [];
|
|
12
|
+
let _ignoreRoutes = [];
|
|
13
|
+
const emit = (operation, key, duration, hit) => {
|
|
14
|
+
if (!_storage)
|
|
15
|
+
return;
|
|
16
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
17
|
+
return;
|
|
18
|
+
const safeKey = redactString(key, _redactionFields);
|
|
19
|
+
const content = {
|
|
20
|
+
operation,
|
|
21
|
+
key: safeKey,
|
|
22
|
+
hit,
|
|
23
|
+
duration,
|
|
24
|
+
hostname: TraceContext.getHostname(),
|
|
25
|
+
};
|
|
26
|
+
_storage
|
|
27
|
+
.writeEntry({
|
|
28
|
+
uuid: crypto.randomUUID(),
|
|
29
|
+
batchId: TraceContext.getBatchId(),
|
|
30
|
+
type: EntryType.CACHE,
|
|
31
|
+
content,
|
|
32
|
+
tags: AuthTag.append([]),
|
|
33
|
+
isLatest: true,
|
|
34
|
+
createdAt: TraceContext.now(),
|
|
35
|
+
})
|
|
36
|
+
.catch(() => undefined);
|
|
37
|
+
};
|
|
38
|
+
export const CacheWatcher = Object.freeze({
|
|
39
|
+
emit,
|
|
40
|
+
register({ storage, config }) {
|
|
41
|
+
if (config.watchers.cache === false)
|
|
42
|
+
return () => undefined;
|
|
43
|
+
_storage = storage;
|
|
44
|
+
_redactionFields = config.redaction.query;
|
|
45
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
46
|
+
return () => {
|
|
47
|
+
_storage = null;
|
|
48
|
+
_ignoreRoutes = [];
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ITraceWatcher } from '../types';
|
|
2
|
+
declare const emit: (name: string, args: Record<string, unknown>, exitCode: number, duration: number, output?: string) => void;
|
|
3
|
+
export declare const CommandWatcher: ITraceWatcher & {
|
|
4
|
+
emit: typeof emit;
|
|
5
|
+
};
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TraceContext } from '../context';
|
|
2
|
+
import { EntryType } from '../types';
|
|
3
|
+
import { redactObject } from '../utils/redact';
|
|
4
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
+
let _storage = null;
|
|
6
|
+
let _redactKeys = [];
|
|
7
|
+
let _ignoreRoutes = [];
|
|
8
|
+
const emit = (name, args, exitCode, duration, output) => {
|
|
9
|
+
if (!_storage)
|
|
10
|
+
return;
|
|
11
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
12
|
+
return;
|
|
13
|
+
const tags = [name];
|
|
14
|
+
if (exitCode !== 0)
|
|
15
|
+
tags.push('failed');
|
|
16
|
+
const content = {
|
|
17
|
+
name,
|
|
18
|
+
arguments: redactObject(args, _redactKeys),
|
|
19
|
+
exitCode,
|
|
20
|
+
duration,
|
|
21
|
+
output,
|
|
22
|
+
hostname: TraceContext.getHostname(),
|
|
23
|
+
};
|
|
24
|
+
_storage
|
|
25
|
+
.writeEntry({
|
|
26
|
+
uuid: crypto.randomUUID(),
|
|
27
|
+
batchId: TraceContext.getBatchId(),
|
|
28
|
+
type: EntryType.COMMAND,
|
|
29
|
+
content,
|
|
30
|
+
tags,
|
|
31
|
+
isLatest: true,
|
|
32
|
+
createdAt: TraceContext.now(),
|
|
33
|
+
})
|
|
34
|
+
.catch(() => undefined);
|
|
35
|
+
};
|
|
36
|
+
export const CommandWatcher = Object.freeze({
|
|
37
|
+
emit,
|
|
38
|
+
register({ storage, config }) {
|
|
39
|
+
if (config.watchers.command === false)
|
|
40
|
+
return () => undefined;
|
|
41
|
+
_storage = storage;
|
|
42
|
+
_redactKeys = config.redaction?.body ?? [];
|
|
43
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
44
|
+
return () => {
|
|
45
|
+
_storage = null;
|
|
46
|
+
_ignoreRoutes = [];
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ITraceWatcher } from '../types';
|
|
2
|
+
/** Explicitly opt-in (enabled only when config.watchers.dump === true, not just non-false). */
|
|
3
|
+
declare const emit: (value: unknown, file?: string, line?: number) => void;
|
|
4
|
+
export declare const DumpWatcher: ITraceWatcher & {
|
|
5
|
+
emit: typeof emit;
|
|
6
|
+
};
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TraceContext } from '../context';
|
|
2
|
+
import { EntryType } from '../types';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
4
|
+
let _storage = null;
|
|
5
|
+
let _enabled = false;
|
|
6
|
+
let _ignoreRoutes = [];
|
|
7
|
+
/** Explicitly opt-in (enabled only when config.watchers.dump === true, not just non-false). */
|
|
8
|
+
const emit = (value, file, line) => {
|
|
9
|
+
if (!_storage || !_enabled)
|
|
10
|
+
return;
|
|
11
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
12
|
+
return;
|
|
13
|
+
const content = { value, file, line, hostname: TraceContext.getHostname() };
|
|
14
|
+
_storage
|
|
15
|
+
.writeEntry({
|
|
16
|
+
uuid: crypto.randomUUID(),
|
|
17
|
+
batchId: TraceContext.getBatchId(),
|
|
18
|
+
type: EntryType.DUMP,
|
|
19
|
+
content,
|
|
20
|
+
tags: [],
|
|
21
|
+
isLatest: true,
|
|
22
|
+
createdAt: TraceContext.now(),
|
|
23
|
+
})
|
|
24
|
+
.catch(() => undefined);
|
|
25
|
+
};
|
|
26
|
+
export const DumpWatcher = Object.freeze({
|
|
27
|
+
emit,
|
|
28
|
+
register({ storage, config }) {
|
|
29
|
+
// DumpWatcher requires explicit opt-in (=== true), not just absence of false
|
|
30
|
+
if (config.watchers.dump !== true)
|
|
31
|
+
return () => undefined;
|
|
32
|
+
_storage = storage;
|
|
33
|
+
_enabled = true;
|
|
34
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
35
|
+
return () => {
|
|
36
|
+
_storage = null;
|
|
37
|
+
_enabled = false;
|
|
38
|
+
_ignoreRoutes = [];
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TraceContext } from '../context';
|
|
2
|
+
import { EntryType } from '../types';
|
|
3
|
+
import { AuthTag } from '../utils/authTag';
|
|
4
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
+
let _storage = null;
|
|
6
|
+
let _ignoreRoutes = [];
|
|
7
|
+
const emit = (name, listenerCount, payload) => {
|
|
8
|
+
if (!_storage)
|
|
9
|
+
return;
|
|
10
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
11
|
+
return;
|
|
12
|
+
const content = {
|
|
13
|
+
name,
|
|
14
|
+
payload,
|
|
15
|
+
listenerCount,
|
|
16
|
+
hostname: TraceContext.getHostname(),
|
|
17
|
+
};
|
|
18
|
+
_storage
|
|
19
|
+
.writeEntry({
|
|
20
|
+
uuid: crypto.randomUUID(),
|
|
21
|
+
batchId: TraceContext.getBatchId(),
|
|
22
|
+
type: EntryType.EVENT,
|
|
23
|
+
content,
|
|
24
|
+
tags: AuthTag.append([name]),
|
|
25
|
+
isLatest: true,
|
|
26
|
+
createdAt: TraceContext.now(),
|
|
27
|
+
})
|
|
28
|
+
.catch(() => undefined);
|
|
29
|
+
};
|
|
30
|
+
export const EventWatcher = Object.freeze({
|
|
31
|
+
emit,
|
|
32
|
+
register({ storage, config }) {
|
|
33
|
+
if (config.watchers.event === false)
|
|
34
|
+
return () => undefined;
|
|
35
|
+
_storage = storage;
|
|
36
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
37
|
+
return () => {
|
|
38
|
+
_storage = null;
|
|
39
|
+
_ignoreRoutes = [];
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExceptionWatcher — captures unhandled exceptions by hooking into the
|
|
3
|
+
* framework error middleware. Core must call ExceptionWatcher.capture()
|
|
4
|
+
* from within its error handler, or the register() side-effect adds a
|
|
5
|
+
* process-level unhandledRejection/uncaughtException listener as fallback.
|
|
6
|
+
*/
|
|
7
|
+
import { TraceContext } from '../context';
|
|
8
|
+
import { EntryType } from '../types';
|
|
9
|
+
import { AuthTag } from '../utils/authTag';
|
|
10
|
+
import { familyHash } from '../utils/familyHash';
|
|
11
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
12
|
+
import { parseStackFrameLine } from '../utils/stackFrame';
|
|
13
|
+
const getLinePreview = (_file, _line) => {
|
|
14
|
+
return {};
|
|
15
|
+
};
|
|
16
|
+
const buildContent = (err) => {
|
|
17
|
+
const stack = err.stack ?? '';
|
|
18
|
+
const trace = stack
|
|
19
|
+
.split('\n')
|
|
20
|
+
.slice(1)
|
|
21
|
+
.map(parseStackFrameLine)
|
|
22
|
+
.filter((x) => x !== null)
|
|
23
|
+
.slice(0, 20);
|
|
24
|
+
const firstFrame = trace[0];
|
|
25
|
+
return {
|
|
26
|
+
class: err.constructor?.name ?? 'Error',
|
|
27
|
+
file: firstFrame?.file ?? 'unknown',
|
|
28
|
+
line: firstFrame?.line ?? 0,
|
|
29
|
+
message: err.message,
|
|
30
|
+
trace,
|
|
31
|
+
linePreview: firstFrame ? getLinePreview(firstFrame.file, firstFrame.line) : {},
|
|
32
|
+
occurrences: 1,
|
|
33
|
+
hostname: TraceContext.getHostname(),
|
|
34
|
+
userId: TraceContext.getUserId(),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
let _storage = null;
|
|
38
|
+
let _listenerRefCount = 0;
|
|
39
|
+
let _ignoreRoutes = [];
|
|
40
|
+
const handleUncaughtException = (error) => {
|
|
41
|
+
captureException(error);
|
|
42
|
+
};
|
|
43
|
+
const handleUnhandledRejection = (reason) => {
|
|
44
|
+
captureException(reason);
|
|
45
|
+
};
|
|
46
|
+
const registerProcessListeners = () => {
|
|
47
|
+
if (typeof process === 'undefined')
|
|
48
|
+
return;
|
|
49
|
+
process.on('uncaughtException', handleUncaughtException);
|
|
50
|
+
process.on('unhandledRejection', handleUnhandledRejection);
|
|
51
|
+
};
|
|
52
|
+
const unregisterProcessListeners = () => {
|
|
53
|
+
if (typeof process === 'undefined')
|
|
54
|
+
return;
|
|
55
|
+
process.off('uncaughtException', handleUncaughtException);
|
|
56
|
+
process.off('unhandledRejection', handleUnhandledRejection);
|
|
57
|
+
};
|
|
58
|
+
const captureException = (err) => {
|
|
59
|
+
const storage = _storage;
|
|
60
|
+
if (!storage)
|
|
61
|
+
return;
|
|
62
|
+
if (!(err instanceof Error))
|
|
63
|
+
return;
|
|
64
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
65
|
+
return;
|
|
66
|
+
const content = buildContent(err);
|
|
67
|
+
const hash = familyHash(`${content.class}:${content.file}:${content.line}`);
|
|
68
|
+
const uuid = crypto.randomUUID();
|
|
69
|
+
storage
|
|
70
|
+
.writeEntry({
|
|
71
|
+
uuid,
|
|
72
|
+
batchId: TraceContext.getBatchId(),
|
|
73
|
+
familyHash: hash,
|
|
74
|
+
type: EntryType.EXCEPTION,
|
|
75
|
+
content,
|
|
76
|
+
tags: AuthTag.append([content.class]),
|
|
77
|
+
isLatest: true,
|
|
78
|
+
createdAt: TraceContext.now(),
|
|
79
|
+
})
|
|
80
|
+
.then(() => storage.markFamilyStale(hash, uuid))
|
|
81
|
+
.catch(() => undefined);
|
|
82
|
+
};
|
|
83
|
+
export const ExceptionWatcher = Object.freeze({
|
|
84
|
+
capture: captureException,
|
|
85
|
+
register({ storage, config }) {
|
|
86
|
+
if (config.watchers.exception === false)
|
|
87
|
+
return () => undefined;
|
|
88
|
+
_storage = storage;
|
|
89
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
90
|
+
if (_listenerRefCount === 0) {
|
|
91
|
+
registerProcessListeners();
|
|
92
|
+
}
|
|
93
|
+
_listenerRefCount += 1;
|
|
94
|
+
return () => {
|
|
95
|
+
_listenerRefCount = Math.max(0, _listenerRefCount - 1);
|
|
96
|
+
if (_listenerRefCount === 0) {
|
|
97
|
+
unregisterProcessListeners();
|
|
98
|
+
}
|
|
99
|
+
_storage = null;
|
|
100
|
+
_ignoreRoutes = [];
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { TraceContext } from '../context';
|
|
2
|
+
import { EntryType } from '../types';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
4
|
+
let _storage = null;
|
|
5
|
+
let _ignoreRoutes = [];
|
|
6
|
+
const emit = (ability, result, userId, subject) => {
|
|
7
|
+
if (!_storage)
|
|
8
|
+
return;
|
|
9
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
10
|
+
return;
|
|
11
|
+
const tags = [ability, result];
|
|
12
|
+
if (userId)
|
|
13
|
+
tags.push(`Auth:${userId}`);
|
|
14
|
+
const content = {
|
|
15
|
+
ability,
|
|
16
|
+
result,
|
|
17
|
+
userId,
|
|
18
|
+
subject,
|
|
19
|
+
hostname: TraceContext.getHostname(),
|
|
20
|
+
};
|
|
21
|
+
_storage
|
|
22
|
+
.writeEntry({
|
|
23
|
+
uuid: crypto.randomUUID(),
|
|
24
|
+
batchId: TraceContext.getBatchId(),
|
|
25
|
+
type: EntryType.GATE,
|
|
26
|
+
content,
|
|
27
|
+
tags,
|
|
28
|
+
isLatest: true,
|
|
29
|
+
createdAt: TraceContext.now(),
|
|
30
|
+
})
|
|
31
|
+
.catch(() => undefined);
|
|
32
|
+
};
|
|
33
|
+
export const GateWatcher = Object.freeze({
|
|
34
|
+
emit,
|
|
35
|
+
register({ storage, config }) {
|
|
36
|
+
if (config.watchers.gate === false)
|
|
37
|
+
return () => undefined;
|
|
38
|
+
_storage = storage;
|
|
39
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
40
|
+
return () => {
|
|
41
|
+
_storage = null;
|
|
42
|
+
_ignoreRoutes = [];
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ITraceWatcher } from '../types';
|
|
2
|
+
declare const emit: (method: string, url: string, requestHeaders: Record<string, string>, responseStatus: number, duration: number) => void;
|
|
3
|
+
export declare const HttpClientWatcher: ITraceWatcher & {
|
|
4
|
+
emit: typeof emit;
|
|
5
|
+
};
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { TraceContext } from '../context';
|
|
2
|
+
import { EntryType } from '../types';
|
|
3
|
+
import { AuthTag } from '../utils/authTag';
|
|
4
|
+
import { redactHeaders } from '../utils/redact';
|
|
5
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
6
|
+
let _storage = null;
|
|
7
|
+
let _redactHeaderNames = [];
|
|
8
|
+
let _ignoreRoutes = [];
|
|
9
|
+
const emit = (method, url, requestHeaders, responseStatus, duration) => {
|
|
10
|
+
if (!_storage)
|
|
11
|
+
return;
|
|
12
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
13
|
+
return;
|
|
14
|
+
const tags = AuthTag.append([method.toUpperCase()]);
|
|
15
|
+
if (responseStatus >= 400)
|
|
16
|
+
tags.push('failed');
|
|
17
|
+
const content = {
|
|
18
|
+
method: method.toUpperCase(),
|
|
19
|
+
url,
|
|
20
|
+
requestHeaders: redactHeaders(requestHeaders, _redactHeaderNames),
|
|
21
|
+
responseStatus,
|
|
22
|
+
duration,
|
|
23
|
+
hostname: TraceContext.getHostname(),
|
|
24
|
+
};
|
|
25
|
+
_storage
|
|
26
|
+
.writeEntry({
|
|
27
|
+
uuid: crypto.randomUUID(),
|
|
28
|
+
batchId: TraceContext.getBatchId(),
|
|
29
|
+
type: EntryType.CLIENT_REQUEST,
|
|
30
|
+
content,
|
|
31
|
+
tags,
|
|
32
|
+
isLatest: true,
|
|
33
|
+
createdAt: TraceContext.now(),
|
|
34
|
+
})
|
|
35
|
+
.catch(() => undefined);
|
|
36
|
+
};
|
|
37
|
+
export const HttpClientWatcher = Object.freeze({
|
|
38
|
+
emit,
|
|
39
|
+
register({ storage, config }) {
|
|
40
|
+
if (config.watchers.clientRequest === false)
|
|
41
|
+
return () => undefined;
|
|
42
|
+
_storage = storage;
|
|
43
|
+
_redactHeaderNames = config.redaction?.headers ?? [];
|
|
44
|
+
_ignoreRoutes = config.ignoreRoutes;
|
|
45
|
+
return () => {
|
|
46
|
+
_storage = null;
|
|
47
|
+
_ignoreRoutes = [];
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { TraceContext } from '../context';
|
|
2
|
+
import { EntryType } from '../types';
|
|
3
|
+
import { AuthTag } from '../utils/authTag';
|
|
4
|
+
import { redactHeaders, redactObject } from '../utils/redact';
|
|
5
|
+
import { RequestFilter } from '../utils/requestFilter';
|
|
6
|
+
const normalizeHeaders = (headers) => {
|
|
7
|
+
if (!headers)
|
|
8
|
+
return {};
|
|
9
|
+
return Object.fromEntries(Object.entries(headers).flatMap(([key, value]) => {
|
|
10
|
+
if (typeof value === 'string')
|
|
11
|
+
return [[key, value]];
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return [[key, value.join(', ')]];
|
|
14
|
+
return [];
|
|
15
|
+
}));
|
|
16
|
+
};
|
|
17
|
+
const buildEntry = (req, res, start, config) => {
|
|
18
|
+
const headers = redactHeaders(normalizeHeaders(req.headers), config.redaction.headers);
|
|
19
|
+
const payload = req.body ? redactObject(req.body, config.redaction.body) : {};
|
|
20
|
+
return {
|
|
21
|
+
method: req.getMethod(),
|
|
22
|
+
uri: req.getPath(),
|
|
23
|
+
headers,
|
|
24
|
+
payload,
|
|
25
|
+
responseStatus: res.getStatus(),
|
|
26
|
+
responseHeaders: {},
|
|
27
|
+
duration: Date.now() - start,
|
|
28
|
+
memory: TraceContext.getMemory(),
|
|
29
|
+
middleware: [],
|
|
30
|
+
hostname: TraceContext.getHostname(),
|
|
31
|
+
userId: TraceContext.getUserId(),
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
const shouldIgnore = (req, config) => {
|
|
35
|
+
return RequestFilter.matchesIgnoredPath(req.getPath(), config.ignoreRoutes);
|
|
36
|
+
};
|
|
37
|
+
const isWatcherEnabled = (config) => config.watchers.request !== false;
|
|
38
|
+
export const HttpWatcher = Object.freeze({
|
|
39
|
+
register({ storage, config, registerMiddleware }) {
|
|
40
|
+
if (!isWatcherEnabled(config))
|
|
41
|
+
return () => undefined;
|
|
42
|
+
if (!registerMiddleware)
|
|
43
|
+
return () => undefined;
|
|
44
|
+
const middleware = async (req, res, next) => {
|
|
45
|
+
const request = req;
|
|
46
|
+
const response = res;
|
|
47
|
+
if (shouldIgnore(request, config))
|
|
48
|
+
return next();
|
|
49
|
+
const start = TraceContext.now();
|
|
50
|
+
const batchId = TraceContext.getBatchId();
|
|
51
|
+
await next();
|
|
52
|
+
const content = buildEntry(request, response, start, config);
|
|
53
|
+
const tags = AuthTag.append([]);
|
|
54
|
+
if (content.responseStatus >= 500)
|
|
55
|
+
tags.push('failed');
|
|
56
|
+
storage
|
|
57
|
+
.writeEntry({
|
|
58
|
+
uuid: crypto.randomUUID(),
|
|
59
|
+
batchId,
|
|
60
|
+
type: EntryType.REQUEST,
|
|
61
|
+
content,
|
|
62
|
+
tags,
|
|
63
|
+
isLatest: true,
|
|
64
|
+
createdAt: TraceContext.now(),
|
|
65
|
+
})
|
|
66
|
+
.catch(() => undefined); // fire-and-forget
|
|
67
|
+
};
|
|
68
|
+
registerMiddleware(middleware);
|
|
69
|
+
return () => undefined;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ITraceWatcher } from '../types';
|
|
2
|
+
declare const emitDispatch: (name: string, queue: string, connection: string, data?: unknown) => void;
|
|
3
|
+
declare const emitProcessed: (name: string) => void;
|
|
4
|
+
declare const emitFailed: (name: string, error: Error) => void;
|
|
5
|
+
export declare const JobWatcher: ITraceWatcher & {
|
|
6
|
+
onDispatch: typeof emitDispatch;
|
|
7
|
+
onProcessed: typeof emitProcessed;
|
|
8
|
+
onFailed: typeof emitFailed;
|
|
9
|
+
};
|
|
10
|
+
export {};
|