Skip to content

Commit d007407

Browse files
AbhiPrasadmydea
andauthored
feat(core): Move console integration into core and add to cloudflare/vercel-edge (#16024)
resolves #15439 resolves #4532 resolves https://linear.app/getsentry/issue/JSC-192 Supercedes #16021 Our console instrumentation was always inconsistent, but adding breadcrumbs should not really be. This PR adds a unified console instrumentation to `@sentry/core`, and makes the Node SDK use that. We also add this to the `vercel-edge` and `cloudflare` SDKs. I also left todo comments in the deno and browser SDKs for us to unify into this single integration afterwards. --------- Co-authored-by: Francesco Gringl-Novy <francesco.novy@sentry.io>
1 parent 8046e14 commit d007407

File tree

13 files changed

+178
-79
lines changed

13 files changed

+178
-79
lines changed

packages/browser/src/integrations/breadcrumbs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const _breadcrumbsIntegration = ((options: Partial<BreadcrumbsOptions> = {}) =>
7474
return {
7575
name: INTEGRATION_NAME,
7676
setup(client) {
77+
// TODO(v10): Remove this functionality and use `consoleIntegration` from @sentry/core instead.
7778
if (_options.console) {
7879
addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client));
7980
}

packages/cloudflare/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export {
7676
captureConsoleIntegration,
7777
moduleMetadataIntegration,
7878
zodErrorsIntegration,
79+
consoleIntegration,
7980
SEMANTIC_ATTRIBUTE_SENTRY_OP,
8081
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
8182
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,

packages/cloudflare/src/sdk.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
linkedErrorsIntegration,
99
requestDataIntegration,
1010
stackParserFromStackParserOptions,
11+
consoleIntegration,
1112
} from '@sentry/core';
1213
import type { CloudflareClientOptions, CloudflareOptions } from './client';
1314
import { CloudflareClient } from './client';
@@ -27,6 +28,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[
2728
linkedErrorsIntegration(),
2829
fetchIntegration(),
2930
requestDataIntegration(sendDefaultPii ? undefined : { include: { cookies: false } }),
31+
consoleIntegration(),
3032
];
3133
}
3234

packages/core/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ export { extraErrorDataIntegration } from './integrations/extraerrordata';
108108
export { rewriteFramesIntegration } from './integrations/rewriteframes';
109109
export { zodErrorsIntegration } from './integrations/zoderrors';
110110
export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter';
111+
export { consoleIntegration } from './integrations/console';
112+
111113
export { profiler } from './profiling';
112114
export { instrumentFetchRequest } from './fetch';
113115
export { trpcMiddleware } from './trpc';
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { addBreadcrumb } from '../breadcrumbs';
2+
import { getClient } from '../currentScopes';
3+
import { defineIntegration } from '../integration';
4+
import type { ConsoleLevel } from '../types-hoist';
5+
import {
6+
CONSOLE_LEVELS,
7+
GLOBAL_OBJ,
8+
addConsoleInstrumentationHandler,
9+
safeJoin,
10+
severityLevelFromString,
11+
} from '../utils-hoist';
12+
13+
interface ConsoleIntegrationOptions {
14+
levels: ConsoleLevel[];
15+
}
16+
17+
type GlobalObjectWithUtil = typeof GLOBAL_OBJ & {
18+
util: {
19+
format: (...args: unknown[]) => string;
20+
};
21+
};
22+
23+
const INTEGRATION_NAME = 'Console';
24+
25+
/**
26+
* Captures calls to the `console` API as breadcrumbs in Sentry.
27+
*
28+
* By default the integration instruments `console.debug`, `console.info`, `console.warn`, `console.error`,
29+
* `console.log`, `console.trace`, and `console.assert`. You can use the `levels` option to customize which
30+
* levels are captured.
31+
*
32+
* @example
33+
*
34+
* ```js
35+
* Sentry.init({
36+
* integrations: [Sentry.consoleIntegration({ levels: ['error', 'warn'] })],
37+
* });
38+
* ```
39+
*/
40+
export const consoleIntegration = defineIntegration((options: Partial<ConsoleIntegrationOptions> = {}) => {
41+
const levels = new Set(options.levels || CONSOLE_LEVELS);
42+
43+
return {
44+
name: INTEGRATION_NAME,
45+
setup(client) {
46+
addConsoleInstrumentationHandler(({ args, level }) => {
47+
if (getClient() !== client || !levels.has(level)) {
48+
return;
49+
}
50+
51+
addConsoleBreadcrumb(level, args);
52+
});
53+
},
54+
};
55+
});
56+
57+
/**
58+
* Capture a console breadcrumb.
59+
*
60+
* Exported just for tests.
61+
*/
62+
export function addConsoleBreadcrumb(level: ConsoleLevel, args: unknown[]): void {
63+
const breadcrumb = {
64+
category: 'console',
65+
data: {
66+
arguments: args,
67+
logger: 'console',
68+
},
69+
level: severityLevelFromString(level),
70+
message: formatConsoleArgs(args),
71+
};
72+
73+
if (level === 'assert') {
74+
if (args[0] === false) {
75+
const assertionArgs = args.slice(1);
76+
breadcrumb.message =
77+
assertionArgs.length > 0 ? `Assertion failed: ${formatConsoleArgs(assertionArgs)}` : 'Assertion failed';
78+
breadcrumb.data.arguments = assertionArgs;
79+
} else {
80+
// Don't capture a breadcrumb for passed assertions
81+
return;
82+
}
83+
}
84+
85+
addBreadcrumb(breadcrumb, {
86+
input: args,
87+
level,
88+
});
89+
}
90+
91+
function formatConsoleArgs(values: unknown[]): string {
92+
return 'util' in GLOBAL_OBJ && typeof (GLOBAL_OBJ as GlobalObjectWithUtil).util.format === 'function'
93+
? (GLOBAL_OBJ as GlobalObjectWithUtil).util.format(...values)
94+
: safeJoin(values, ' ');
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { addConsoleBreadcrumb } from '../../../src/integrations/console';
3+
import { addBreadcrumb } from '../../../src/breadcrumbs';
4+
5+
vi.mock('../../../src/breadcrumbs', () => ({
6+
addBreadcrumb: vi.fn(),
7+
}));
8+
9+
describe('addConsoleBreadcrumb', () => {
10+
beforeEach(() => {
11+
vi.clearAllMocks();
12+
});
13+
14+
it('creates a breadcrumb with correct properties for basic console log', () => {
15+
const level = 'log';
16+
const args = ['test message', 123];
17+
18+
addConsoleBreadcrumb(level, args);
19+
20+
expect(addBreadcrumb).toHaveBeenCalledWith(
21+
expect.objectContaining({
22+
category: 'console',
23+
data: {
24+
arguments: args,
25+
logger: 'console',
26+
},
27+
level: 'log',
28+
message: 'test message 123',
29+
}),
30+
{
31+
input: args,
32+
level,
33+
},
34+
);
35+
});
36+
37+
it.each(['debug', 'info', 'warn', 'error'] as const)('handles %s level correctly', level => {
38+
addConsoleBreadcrumb(level, ['test']);
39+
expect(addBreadcrumb).toHaveBeenCalledWith(
40+
expect.objectContaining({
41+
level: expect.any(String),
42+
}),
43+
expect.any(Object),
44+
);
45+
});
46+
47+
it('skips breadcrumb for passed assertions', () => {
48+
addConsoleBreadcrumb('assert', [true, 'should not be captured']);
49+
expect(addBreadcrumb).not.toHaveBeenCalled();
50+
});
51+
52+
it('creates breadcrumb for failed assertions', () => {
53+
const args = [false, 'assertion failed', 'details'];
54+
55+
addConsoleBreadcrumb('assert', args);
56+
57+
expect(addBreadcrumb).toHaveBeenCalledWith(
58+
expect.objectContaining({
59+
message: expect.stringContaining('Assertion failed'),
60+
data: {
61+
arguments: args.slice(1),
62+
logger: 'console',
63+
},
64+
}),
65+
{
66+
input: args,
67+
level: 'assert',
68+
},
69+
);
70+
});
71+
});

packages/deno/src/integrations/breadcrumbs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const _breadcrumbsIntegration = ((options: Partial<BreadcrumbsOptions> = {}) =>
4242
return {
4343
name: INTEGRATION_NAME,
4444
setup(client) {
45+
// TODO(v10): Remove this functionality and use `consoleIntegration` from @sentry/core instead.
4546
if (_options.console) {
4647
addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client));
4748
}

packages/node/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ export { httpIntegration } from './integrations/http';
22
export { nativeNodeFetchIntegration } from './integrations/node-fetch';
33
export { fsIntegration } from './integrations/fs';
44

5-
export { consoleIntegration } from './integrations/console';
65
export { nodeContextIntegration } from './integrations/context';
76
export { contextLinesIntegration } from './integrations/contextlines';
87
export { localVariablesIntegration } from './integrations/local-variables';
@@ -131,6 +130,7 @@ export {
131130
zodErrorsIntegration,
132131
profiler,
133132
consoleLoggingIntegration,
133+
consoleIntegration,
134134
} from '@sentry/core';
135135

136136
export type {

packages/node/src/integrations/console.ts

-38
This file was deleted.

packages/node/src/sdk/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
propagationContextFromHeaders,
1212
requestDataIntegration,
1313
stackParserFromStackParserOptions,
14+
consoleIntegration,
1415
} from '@sentry/core';
1516
import {
1617
enhanceDscWithOpenTelemetryRootSpanName,
@@ -20,7 +21,6 @@ import {
2021
} from '@sentry/opentelemetry';
2122
import { DEBUG_BUILD } from '../debug-build';
2223
import { childProcessIntegration } from '../integrations/childProcess';
23-
import { consoleIntegration } from '../integrations/console';
2424
import { nodeContextIntegration } from '../integrations/context';
2525
import { contextLinesIntegration } from '../integrations/contextlines';
2626
import { httpIntegration } from '../integrations/http';

packages/node/test/integration/console.test.ts

-39
This file was deleted.

packages/vercel-edge/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export {
7676
captureConsoleIntegration,
7777
moduleMetadataIntegration,
7878
zodErrorsIntegration,
79+
consoleIntegration,
7980
SEMANTIC_ATTRIBUTE_SENTRY_OP,
8081
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
8182
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,

packages/vercel-edge/src/sdk.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
nodeStackLineParser,
2323
requestDataIntegration,
2424
stackParserFromStackParserOptions,
25+
consoleIntegration,
2526
} from '@sentry/core';
2627
import {
2728
SentryPropagator,
@@ -57,6 +58,7 @@ export function getDefaultIntegrations(options: Options): Integration[] {
5758
functionToStringIntegration(),
5859
linkedErrorsIntegration(),
5960
winterCGFetchIntegration(),
61+
consoleIntegration(),
6062
...(options.sendDefaultPii ? [requestDataIntegration()] : []),
6163
];
6264
}

0 commit comments

Comments
 (0)