Skip to content

Commit b83beb0

Browse files
committed
feat: Dont blow up when someone puts console.log inside beforeBreadcrumb call
1 parent ad44f1c commit b83beb0

File tree

3 files changed

+89
-43
lines changed

3 files changed

+89
-43
lines changed

packages/browser/src/integrations/breadcrumbs.ts

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,50 @@ export interface SentryWrappedXMLHttpRequest extends XMLHttpRequest {
2727
};
2828
}
2929

30+
/**
31+
* Wrapper function that'll be used for every console level
32+
*/
33+
function consoleWrapper(originalConsole: ExtensibleConsole): any {
34+
return function(level: string): any {
35+
if (!(level in global.console)) {
36+
return;
37+
}
38+
39+
fill(originalConsole, level, function(originalConsoleLevel: () => any): any {
40+
return function(...args: any[]): any {
41+
const breadcrumbData = {
42+
category: 'console',
43+
data: {
44+
extra: {
45+
arguments: args.slice(1),
46+
},
47+
logger: 'console',
48+
},
49+
level: Severity.fromString(level),
50+
message: safeJoin(args, ' '),
51+
};
52+
53+
if (level === 'assert') {
54+
if (args[0] === false) {
55+
breadcrumbData.message = `Assertion failed: ${safeJoin(args.slice(1), ' ') || 'console.assert'}`;
56+
breadcrumbData.data.extra.arguments = args.slice(1);
57+
}
58+
}
59+
60+
getCurrentHub().addBreadcrumb(breadcrumbData, {
61+
input: args,
62+
level,
63+
});
64+
65+
// this fails for some browsers. :(
66+
if (originalConsoleLevel) {
67+
originalConsoleLevel.apply(originalConsole, args);
68+
}
69+
};
70+
});
71+
};
72+
}
73+
3074
/** JSDoc */
3175
function addSentryBreadcrumb(serializedData: string): void {
3276
// There's always something that can go wrong with deserialization...
@@ -130,48 +174,8 @@ export class Breadcrumbs implements Integration {
130174
if (!('console' in global)) {
131175
return;
132176
}
133-
134177
const originalConsole = global.console as ExtensibleConsole;
135-
136-
['debug', 'info', 'warn', 'error', 'log'].forEach(level => {
137-
if (!(level in console)) {
138-
return;
139-
}
140-
141-
// tslint:disable-next-line
142-
const originalConsoleLevel = originalConsole[level];
143-
144-
(global.console as ExtensibleConsole)[level] = function(...args: any[]): void {
145-
const breadcrumbData = {
146-
category: 'console',
147-
data: {
148-
extra: {
149-
arguments: args.slice(1),
150-
},
151-
logger: 'console',
152-
},
153-
level: Severity.fromString(level),
154-
message: safeJoin(args, ' '),
155-
};
156-
157-
if (level === 'assert') {
158-
if (args[0] === false) {
159-
breadcrumbData.message = `Assertion failed: ${safeJoin(args.slice(1), ' ') || 'console.assert'}`;
160-
breadcrumbData.data.extra.arguments = args.slice(1);
161-
}
162-
}
163-
164-
getCurrentHub().addBreadcrumb(breadcrumbData, {
165-
input: args,
166-
level,
167-
});
168-
169-
// this fails for some browsers. :(
170-
if (originalConsoleLevel) {
171-
Function.prototype.apply.call(originalConsoleLevel, originalConsole, args);
172-
}
173-
};
174-
});
178+
['debug', 'info', 'warn', 'error', 'log'].forEach(consoleWrapper(originalConsole));
175179
}
176180

177181
/** JSDoc */

packages/core/src/baseclient.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,50 @@ import {
88
Severity,
99
Status,
1010
} from '@sentry/types';
11-
import { uuid4 } from '@sentry/utils/misc';
11+
import { getGlobalObject, uuid4 } from '@sentry/utils/misc';
1212
import { truncate } from '@sentry/utils/string';
1313
import { BackendClass } from './basebackend';
1414
import { Dsn } from './dsn';
1515
import { Backend, Client, Options } from './interfaces';
1616

17+
/** JSDoc */
18+
interface ExtensibleConsole extends Console {
19+
[key: string]: any;
20+
}
21+
22+
/** JSDoc */
23+
async function beforeBreadcrumbConsoleLoopGuard(
24+
callback: () => Breadcrumb | Promise<Breadcrumb | null> | null,
25+
): Promise<Breadcrumb | null> {
26+
const global = getGlobalObject();
27+
const levels = ['debug', 'info', 'warn', 'error', 'log'];
28+
29+
if (!('console' in global)) {
30+
return callback();
31+
}
32+
33+
const originalConsole = global.console as ExtensibleConsole;
34+
35+
// Restore all wrapped console methods
36+
levels.forEach(level => {
37+
if (level in global.console && originalConsole[level].__sentry__) {
38+
originalConsole[level] = originalConsole[level].__sentry_original__;
39+
}
40+
});
41+
42+
// Perform callback manipulations
43+
const result = await callback();
44+
45+
// Revert restoration to wrapped state
46+
levels.forEach(level => {
47+
if (level in global.console && originalConsole[level].__sentry__) {
48+
originalConsole[level] = originalConsole[level].__sentry_wrapped__;
49+
}
50+
});
51+
52+
return result;
53+
}
54+
1755
/**
1856
* Default maximum number of breadcrumbs added to an event. Can be overwritten
1957
* with {@link Options.maxBreadcrumbs}.
@@ -182,7 +220,9 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
182220

183221
const timestamp = new Date().getTime() / 1000;
184222
const mergedBreadcrumb = { timestamp, ...breadcrumb };
185-
const finalBreadcrumb = beforeBreadcrumb ? await beforeBreadcrumb(mergedBreadcrumb, hint) : mergedBreadcrumb;
223+
const finalBreadcrumb = beforeBreadcrumb
224+
? await beforeBreadcrumbConsoleLoopGuard(() => beforeBreadcrumb(mergedBreadcrumb, hint))
225+
: mergedBreadcrumb;
186226

187227
if (finalBreadcrumb === null) {
188228
return;

packages/utils/src/object.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ export function fill(source: { [key: string]: any }, name: string, replacement:
162162
source[name].__sentry__ = true;
163163
// tslint:disable-next-line:no-unsafe-any
164164
source[name].__sentry_original__ = original;
165+
// tslint:disable-next-line:no-unsafe-any
166+
source[name].__sentry_wrapped__ = source[name];
165167
}
166168

167169
/**

0 commit comments

Comments
 (0)