11/* eslint-disable @typescript-eslint/no-explicit-any */
22/* eslint-disable @typescript-eslint/ban-types */
33import type { ConsoleLevel , HandlerDataConsole } from '../types-hoist/instrument' ;
4+ import type { WrappedFunction } from '../types-hoist/wrappedfunction' ;
45import { CONSOLE_LEVELS , originalConsoleMethods } from '../utils/debug-logger' ;
5- import { fill } from '../utils/object' ;
6+ import { fill , markFunctionWrapped } from '../utils/object' ;
67import { GLOBAL_OBJ } from '../utils/worldwide' ;
78import { 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