Skip to content

Commit d1cfd60

Browse files
committed
fix(console): Re-patch console in AWS Lambda runtimes
1 parent 88c6f55 commit d1cfd60

File tree

1 file changed

+65
-10
lines changed

1 file changed

+65
-10
lines changed

packages/core/src/instrument/console.ts

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
/* eslint-disable @typescript-eslint/ban-types */
33
import type { ConsoleLevel, HandlerDataConsole } from '../types-hoist/instrument';
4+
import type { WrappedFunction } from '../types-hoist/wrappedfunction';
45
import { CONSOLE_LEVELS, originalConsoleMethods } from '../utils/debug-logger';
5-
import { fill } from '../utils/object';
6+
import { fill, markFunctionWrapped } from '../utils/object';
67
import { GLOBAL_OBJ } from '../utils/worldwide';
78
import { addHandler, maybeInstrument, triggerHandlers } from './handlers';
89

@@ -28,16 +29,70 @@ function instrumentConsole(): void {
2829
return;
2930
}
3031

31-
fill(GLOBAL_OBJ.console, level, function (originalConsoleMethod: () => any): Function {
32-
originalConsoleMethods[level] = originalConsoleMethod;
32+
if (typeof process !== 'undefined' && !!process.env.LAMBDA_TASK_ROOT) {
33+
// The AWS Lambda runtime replaces console methods AFTER our patch, which overwrites them.
34+
patchWithDefineProperty(level);
35+
} else {
36+
patchWithFill(level);
37+
}
38+
});
39+
}
3340

34-
return function (...args: any[]): void {
35-
const handlerData: HandlerDataConsole = { args, level };
36-
triggerHandlers('console', handlerData);
41+
function patchWithFill(level: ConsoleLevel): void {
42+
fill(GLOBAL_OBJ.console, level, function (originalConsoleMethod: () => any): Function {
43+
originalConsoleMethods[level] = originalConsoleMethod;
3744

38-
const log = originalConsoleMethods[level];
39-
log?.apply(GLOBAL_OBJ.console, args);
40-
};
41-
});
45+
return function (...args: any[]): void {
46+
triggerHandlers('console', { args, level } as HandlerDataConsole);
47+
48+
const log = originalConsoleMethods[level];
49+
log?.apply(GLOBAL_OBJ.console, args);
50+
};
4251
});
4352
}
53+
54+
function patchWithDefineProperty(level: ConsoleLevel): void {
55+
const originalMethod = GLOBAL_OBJ.console[level] as (...args: unknown[]) => void;
56+
originalConsoleMethods[level] = originalMethod;
57+
58+
let underlying: Function = originalMethod;
59+
60+
const wrapper = function (...args: any[]): void {
61+
triggerHandlers('console', { args, level });
62+
underlying.apply(GLOBAL_OBJ.console, args);
63+
};
64+
markFunctionWrapped(wrapper as unknown as WrappedFunction, originalMethod as unknown as WrappedFunction);
65+
66+
try {
67+
let current: any = wrapper;
68+
69+
Object.defineProperty(GLOBAL_OBJ.console, level, {
70+
configurable: true,
71+
enumerable: true,
72+
get() {
73+
return current;
74+
},
75+
// When `console[level]` is set to a new value, we want to check if it's something not done by us but by e.g. the Lambda runtime.
76+
set(newValue) {
77+
if (
78+
typeof newValue === 'function' &&
79+
// Ignore if it's set to the wrapper (e.g. by our own patch or consoleSandbox), which would cause an infinite loop.
80+
newValue !== wrapper &&
81+
// Function is not one of our wrappers (which have __sentry_original__) and not the original (stored in originalConsoleMethods)
82+
newValue !== originalConsoleMethods[level] &&
83+
!(newValue as WrappedFunction).__sentry_original__
84+
) {
85+
underlying = newValue;
86+
originalConsoleMethods[level] = newValue;
87+
current = wrapper;
88+
} else {
89+
// Accept as-is: consoleSandbox restores, other Sentry wrappers, or non-functions
90+
current = newValue;
91+
}
92+
},
93+
});
94+
} catch {
95+
// In case defineProperty fails (e.g. in older browsers), fall back to fill-style patching
96+
patchWithFill(level);
97+
}
98+
}

0 commit comments

Comments
 (0)