Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/browser/src/integrations/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const _breadcrumbsIntegration = ((options: Partial<BreadcrumbsOptions> = {}) =>
return {
name: INTEGRATION_NAME,
setup(client) {
// TODO(v10): Remove this functionality and use `consoleIntegration` from @sentry/core instead.
if (_options.console) {
addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client));
}
Expand Down
1 change: 1 addition & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export {
captureConsoleIntegration,
moduleMetadataIntegration,
zodErrorsIntegration,
consoleIntegration,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
Expand Down
2 changes: 2 additions & 0 deletions packages/cloudflare/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
linkedErrorsIntegration,
requestDataIntegration,
stackParserFromStackParserOptions,
consoleIntegration,
} from '@sentry/core';
import type { CloudflareClientOptions, CloudflareOptions } from './client';
import { CloudflareClient } from './client';
Expand All @@ -27,6 +28,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[
linkedErrorsIntegration(),
fetchIntegration(),
requestDataIntegration(sendDefaultPii ? undefined : { include: { cookies: false } }),
consoleIntegration(),
];
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export { extraErrorDataIntegration } from './integrations/extraerrordata';
export { rewriteFramesIntegration } from './integrations/rewriteframes';
export { zodErrorsIntegration } from './integrations/zoderrors';
export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter';
export { consoleIntegration } from './integrations/console';

export { profiler } from './profiling';
export { instrumentFetchRequest } from './fetch';
export { trpcMiddleware } from './trpc';
Expand Down
95 changes: 95 additions & 0 deletions packages/core/src/integrations/console.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { addBreadcrumb } from '../breadcrumbs';
import { getClient } from '../currentScopes';
import { defineIntegration } from '../integration';
import type { ConsoleLevel } from '../types-hoist';
import {
CONSOLE_LEVELS,
GLOBAL_OBJ,
addConsoleInstrumentationHandler,
safeJoin,
severityLevelFromString,
} from '../utils-hoist';

interface ConsoleIntegrationOptions {
levels: ConsoleLevel[];
}

type GlobalObjectWithUtil = typeof GLOBAL_OBJ & {
util: {
format: (...args: unknown[]) => string;
};
};

const INTEGRATION_NAME = 'Console';

/**
* Captures calls to the `console` API as logs in Sentry.
*
* By default the integration instruments `console.debug`, `console.info`, `console.warn`, `console.error`,
* `console.log`, `console.trace`, and `console.assert`. You can use the `levels` option to customize which
* levels are captured.
*
* @example
*
* ```js
* Sentry.init({
* integrations: [Sentry.consoleIntegration({ levels: ['error', 'warn'] })],
* });
* ```
*/
export const consoleIntegration = defineIntegration((options: Partial<ConsoleIntegrationOptions> = {}) => {
const levels = new Set(options.levels || CONSOLE_LEVELS);

return {
name: INTEGRATION_NAME,
setup(client) {
addConsoleInstrumentationHandler(({ args, level }) => {
if (getClient() !== client || !levels.has(level)) {
return;
}

captureConsoleBreadcrumb(level, args);
});
},
};
});

/**
* Capture a console breadcrumb.
*
* Exported just for tests.
*/
export function captureConsoleBreadcrumb(level: ConsoleLevel, args: unknown[]): void {
const breadcrumb = {
category: 'console',
data: {
arguments: args,
logger: 'console',
},
level: severityLevelFromString(level),
message: formatConsoleArgs(args),
};

if (level === 'assert') {
if (args[0] === false) {
const assertionArgs = args.slice(1);
breadcrumb.message =
assertionArgs.length > 0 ? `Assertion failed: ${formatConsoleArgs(assertionArgs)}` : 'Assertion failed';
breadcrumb.data.arguments = assertionArgs;
} else {
// Don't capture a breadcrumb for passed assertions
return;
}
}

addBreadcrumb(breadcrumb, {
input: args,
level,
});
}

function formatConsoleArgs(values: unknown[]): string {
return 'util' in GLOBAL_OBJ && typeof (GLOBAL_OBJ as GlobalObjectWithUtil).util.format === 'function'
? (GLOBAL_OBJ as GlobalObjectWithUtil).util.format(...values)
: safeJoin(values, ' ');
}
75 changes: 75 additions & 0 deletions packages/core/test/lib/integrations/console.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { captureConsoleBreadcrumb } from '../../../src/integrations/console';
import { addBreadcrumb } from '../../../src/breadcrumbs';

vi.mock('../../../src/breadcrumbs', () => ({
addBreadcrumb: vi.fn(),
}));

describe('captureConsoleBreadcrumb', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('creates a breadcrumb with correct properties for basic console log', () => {
const level = 'log';
const args = ['test message', 123];

captureConsoleBreadcrumb(level, args);

expect(addBreadcrumb).toHaveBeenCalledWith(
expect.objectContaining({
category: 'console',
data: {
arguments: args,
logger: 'console',
},
level: 'log',
message: 'test message 123',
}),
{
input: args,
level,
},
);
});

it('handles different console levels correctly', () => {
const levels = ['debug', 'info', 'warn', 'error'] as const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l/nit: I'd rather use it.each so that we'd know for which specific level the test failed.

Also q: I noticed this pattern in quite a few tests (not yours, more generally in the repo) and was wondering: Am I maybe missing something why this is preferrable over it.each? 😅

Again, not a blocker, feel free to ignore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also q: I noticed this pattern in quite a few tests (not yours, more generally in the repo) and was wondering: Am I maybe missing something why this is preferrable over it.each? 😅

I've been leaning less toward using it.each because it's easier for me to it.only test one at a time, but I agree the table test is the better pattern here. Will change.


levels.forEach(level => {
captureConsoleBreadcrumb(level, ['test']);
expect(addBreadcrumb).toHaveBeenCalledWith(
expect.objectContaining({
level: expect.any(String),
}),
expect.any(Object),
);
});
});

it('skips breadcrumb for passed assertions', () => {
captureConsoleBreadcrumb('assert', [true, 'should not be captured']);
expect(addBreadcrumb).not.toHaveBeenCalled();
});

it('creates breadcrumb for failed assertions', () => {
const args = [false, 'assertion failed', 'details'];

captureConsoleBreadcrumb('assert', args);

expect(addBreadcrumb).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Assertion failed'),
data: {
arguments: args.slice(1),
logger: 'console',
},
}),
{
input: args,
level: 'assert',
},
);
});
});
1 change: 1 addition & 0 deletions packages/deno/src/integrations/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const _breadcrumbsIntegration = ((options: Partial<BreadcrumbsOptions> = {}) =>
return {
name: INTEGRATION_NAME,
setup(client) {
// TODO(v10): Remove this functionality and use `consoleIntegration` from @sentry/core instead.
if (_options.console) {
addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client));
}
Expand Down
2 changes: 1 addition & 1 deletion packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export { httpIntegration } from './integrations/http';
export { nativeNodeFetchIntegration } from './integrations/node-fetch';
export { fsIntegration } from './integrations/fs';

export { consoleIntegration } from './integrations/console';
export { nodeContextIntegration } from './integrations/context';
export { contextLinesIntegration } from './integrations/contextlines';
export { localVariablesIntegration } from './integrations/local-variables';
Expand Down Expand Up @@ -131,6 +130,7 @@ export {
zodErrorsIntegration,
profiler,
consoleLoggingIntegration,
consoleIntegration,
} from '@sentry/core';

export type {
Expand Down
38 changes: 0 additions & 38 deletions packages/node/src/integrations/console.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/node/src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
propagationContextFromHeaders,
requestDataIntegration,
stackParserFromStackParserOptions,
consoleIntegration,
} from '@sentry/core';
import {
enhanceDscWithOpenTelemetryRootSpanName,
Expand All @@ -20,7 +21,6 @@ import {
} from '@sentry/opentelemetry';
import { DEBUG_BUILD } from '../debug-build';
import { childProcessIntegration } from '../integrations/childProcess';
import { consoleIntegration } from '../integrations/console';
import { nodeContextIntegration } from '../integrations/context';
import { contextLinesIntegration } from '../integrations/contextlines';
import { httpIntegration } from '../integrations/http';
Expand Down
39 changes: 0 additions & 39 deletions packages/node/test/integration/console.test.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/vercel-edge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export {
captureConsoleIntegration,
moduleMetadataIntegration,
zodErrorsIntegration,
consoleIntegration,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
Expand Down
2 changes: 2 additions & 0 deletions packages/vercel-edge/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
nodeStackLineParser,
requestDataIntegration,
stackParserFromStackParserOptions,
consoleIntegration,
} from '@sentry/core';
import {
SentryPropagator,
Expand Down Expand Up @@ -57,6 +58,7 @@ export function getDefaultIntegrations(options: Options): Integration[] {
functionToStringIntegration(),
linkedErrorsIntegration(),
winterCGFetchIntegration(),
consoleIntegration(),
...(options.sendDefaultPii ? [requestDataIntegration()] : []),
];
}
Expand Down
Loading