-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathpatch-console.ts
More file actions
128 lines (112 loc) · 4.38 KB
/
Copy pathpatch-console.ts
File metadata and controls
128 lines (112 loc) · 4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import * as shimmer from "shimmer";
type Console = typeof console;
type wrappedConsole = Console & { [K in LogMethod]: { __wrapped?: boolean } };
import { getLogLevel, LogLevel, setLogLevel } from "../utils/log";
import { TraceContextService } from "./trace-context-service";
type LogMethod = "log" | "info" | "debug" | "error" | "warn" | "trace";
/**
* Checks if a value is a JSON-style structured log (plain object).
* When true, trace context will be injected as a `dd` property to preserve JSON format.
* When false, trace context will be prepended as a string prefix.
*/
function isJsonStyleLog(value: unknown): value is Record<string, unknown> {
if (value === null || typeof value !== "object") {
return false;
}
if (Array.isArray(value)) {
return false;
}
const proto = Object.getPrototypeOf(value);
return proto === null || proto === Object.prototype;
}
/**
* Checks if a value is a plain object (not null, not an array).
*/
function isPlainObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
/**
* Extracts the existing `dd` property from a log object if it's a plain object.
* Returns an empty object if `dd` is missing or not a plain object.
*/
function getExistingDdContext(logObject: Record<string, unknown>): Record<string, unknown> {
return isPlainObject(logObject.dd) ? logObject.dd : {};
}
/**
* Patches console output to include DataDog's trace context.
* @param contextService Provides up to date tracing context.
*/
export function patchConsole(cnsle: wrappedConsole, contextService: TraceContextService) {
patchMethod(cnsle, "log", contextService);
patchMethod(cnsle, "info", contextService);
patchMethod(cnsle, "debug", contextService);
patchMethod(cnsle, "error", contextService);
patchMethod(cnsle, "warn", contextService);
patchMethod(cnsle, "trace", contextService);
}
/**
* Removes log patching to add DataDog's trace context.
*/
export function unpatchConsole(cnsle: Console) {
unpatchMethod(cnsle, "log");
unpatchMethod(cnsle, "info");
unpatchMethod(cnsle, "debug");
unpatchMethod(cnsle, "error");
unpatchMethod(cnsle, "warn");
unpatchMethod(cnsle, "trace");
}
function patchMethod(mod: wrappedConsole, method: LogMethod, contextService: TraceContextService) {
if (mod[method].__wrapped !== undefined) {
return; // Only patch once
}
shimmer.wrap(mod, method, (original) => {
let isLogging = false;
return function emitWithContext(this: any, message?: any, ...optionalParams: any[]) {
// Disable internal logging during this call, so we don't generate an infinite loop.
// Re-entrance check, incase any of the code below tries to call a log method
if (isLogging) {
return original.apply(this as any, arguments as any);
}
isLogging = true;
const oldLogLevel = getLogLevel();
setLogLevel(LogLevel.NONE);
try {
const context = contextService.currentTraceContext;
if (context !== null) {
const traceId = context.toTraceId();
const spanId = context.toSpanId();
if (arguments.length === 0) {
// No arguments: emit just the trace context prefix
arguments.length = 1;
arguments[0] = `[dd.trace_id=${traceId} dd.span_id=${spanId}]`;
} else if (arguments.length === 1 && isJsonStyleLog(arguments[0])) {
// Single plain object: inject dd property to preserve JSON format
arguments[0] = {
...arguments[0],
dd: {
...getExistingDdContext(arguments[0]),
// Overwrite trace_id and span_id to ensure we have the latest values
trace_id: traceId,
span_id: spanId,
},
};
} else {
// String or multiple arguments: use string prefix
const prefix = `[dd.trace_id=${traceId} dd.span_id=${spanId}]`;
arguments[0] = `${prefix} ${arguments[0]}`;
}
}
} catch (error) {
// Swallow the error, because logging inside log shouldn't be supported
}
setLogLevel(oldLogLevel);
isLogging = false;
return original.apply(this as any, arguments as any);
};
});
}
function unpatchMethod(mod: wrappedConsole, method: LogMethod) {
if (mod[method].__wrapped !== undefined) {
shimmer.unwrap(mod, method);
}
}