-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(core): Move console integration into core and add to cloudflare/vercel-edge #16024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, ' '); | ||
} |
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; | ||
|
||
|
||
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', | ||
}, | ||
); | ||
}); | ||
}); |
This file was deleted.
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.