diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 35de77f611d0..ac6ea866c9d9 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -1,11 +1,8 @@ import { feedbackAsyncIntegration } from './feedbackAsync'; import { feedbackSyncIntegration } from './feedbackSync'; -import * as logger from './log'; export * from './exports'; -export { logger }; - export { reportingObserverIntegration } from './integrations/reportingobserver'; export { httpClientIntegration } from './integrations/httpclient'; export { contextLinesIntegration } from './integrations/contextlines'; @@ -63,6 +60,7 @@ export { zodErrorsIntegration, thirdPartyErrorFilterIntegration, featureFlagsIntegration, + logger, } from '@sentry/core'; export type { Span, FeatureFlagsIntegration } from '@sentry/core'; export { makeBrowserOfflineTransport } from './transports/offline'; diff --git a/packages/browser/test/client.test.ts b/packages/browser/test/client.test.ts index c6cbc735c8a1..c1fcac17444b 100644 --- a/packages/browser/test/client.test.ts +++ b/packages/browser/test/client.test.ts @@ -3,6 +3,7 @@ */ import * as sentryCore from '@sentry/core'; +import { Scope } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { applyDefaultOptions, BrowserClient } from '../src/client'; import { WINDOW } from '../src/helpers'; @@ -30,10 +31,12 @@ describe('BrowserClient', () => { sendClientReports: true, }), ); + const scope = new Scope(); + scope.setClient(client); // Add some logs - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, client); - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, client); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, scope); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, scope); // Simulate visibility change to hidden if (WINDOW.document) { @@ -58,9 +61,12 @@ describe('BrowserClient', () => { it('flushes logs when page visibility changes to hidden', () => { const flushOutcomesSpy = vi.spyOn(client as any, '_flushOutcomes'); + const scope = new Scope(); + scope.setClient(client); + // Add some logs - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, client); - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, client); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, scope); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, scope); // Simulate visibility change to hidden if (WINDOW.document) { @@ -73,9 +79,12 @@ describe('BrowserClient', () => { }); it('flushes logs on flush event', () => { + const scope = new Scope(); + scope.setClient(client); + // Add some logs - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, client); - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, client); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, scope); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, scope); // Trigger flush event client.emit('flush'); @@ -84,8 +93,11 @@ describe('BrowserClient', () => { }); it('flushes logs after idle timeout', () => { + const scope = new Scope(); + scope.setClient(client); + // Add a log which will trigger afterCaptureLog event - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log' }, client); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log' }, scope); // Fast forward the idle timeout vi.advanceTimersByTime(DEFAULT_FLUSH_INTERVAL); @@ -94,14 +106,17 @@ describe('BrowserClient', () => { }); it('resets idle timeout when new logs are captured', () => { + const scope = new Scope(); + scope.setClient(client); + // Add initial log - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, client); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 1' }, scope); // Fast forward part of the idle timeout vi.advanceTimersByTime(DEFAULT_FLUSH_INTERVAL / 2); // Add another log which should reset the timeout - sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, client); + sentryCore._INTERNAL_captureLog({ level: 'info', message: 'test log 2' }, scope); // Fast forward the remaining time vi.advanceTimersByTime(DEFAULT_FLUSH_INTERVAL / 2); diff --git a/packages/browser/test/log.test.ts b/packages/browser/test/log.test.ts deleted file mode 100644 index 0967d38531dd..000000000000 --- a/packages/browser/test/log.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @vitest-environment jsdom - */ - -import * as sentryCore from '@sentry/core'; -import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { init, logger } from '../src'; -import { makeSimpleTransport } from './mocks/simpletransport'; - -const dsn = 'https://53039209a22b4ec1bcc296a3c9fdecd6@sentry.io/4291'; - -// Mock the core functions -vi.mock('@sentry/core', async requireActual => { - return { - ...((await requireActual()) as any), - _INTERNAL_captureLog: vi.fn(), - _INTERNAL_flushLogsBuffer: vi.fn(), - }; -}); - -describe('Logger', () => { - // Use the mocked functions - const mockCaptureLog = vi.mocked(sentryCore._INTERNAL_captureLog); - const mockFlushLogsBuffer = vi.mocked(sentryCore._INTERNAL_flushLogsBuffer); - - beforeEach(() => { - // Reset mocks - mockCaptureLog.mockClear(); - mockFlushLogsBuffer.mockClear(); - - // Reset the global scope, isolation scope, and current scope - getGlobalScope().clear(); - getIsolationScope().clear(); - getCurrentScope().clear(); - getCurrentScope().setClient(undefined); - - // Mock setTimeout and clearTimeout - vi.useFakeTimers(); - - // Initialize with logs enabled - init({ - dsn, - transport: makeSimpleTransport, - enableLogs: true, - }); - }); - - afterEach(() => { - vi.clearAllTimers(); - vi.useRealTimers(); - }); - - describe('Logger methods', () => { - it('should export all log methods', () => { - expect(logger).toBeDefined(); - expect(logger.trace).toBeTypeOf('function'); - expect(logger.debug).toBeTypeOf('function'); - expect(logger.info).toBeTypeOf('function'); - expect(logger.warn).toBeTypeOf('function'); - expect(logger.error).toBeTypeOf('function'); - expect(logger.fatal).toBeTypeOf('function'); - }); - - it('should call _INTERNAL_captureLog with trace level', () => { - logger.trace('Test trace message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'trace', - message: 'Test trace message', - attributes: { key: 'value' }, - severityNumber: undefined, - }); - }); - - it('should call _INTERNAL_captureLog with debug level', () => { - logger.debug('Test debug message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'debug', - message: 'Test debug message', - attributes: { key: 'value' }, - severityNumber: undefined, - }); - }); - - it('should call _INTERNAL_captureLog with info level', () => { - logger.info('Test info message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Test info message', - attributes: { key: 'value' }, - severityNumber: undefined, - }); - }); - - it('should call _INTERNAL_captureLog with warn level', () => { - logger.warn('Test warn message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'warn', - message: 'Test warn message', - attributes: { key: 'value' }, - severityNumber: undefined, - }); - }); - - it('should call _INTERNAL_captureLog with error level', () => { - logger.error('Test error message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'error', - message: 'Test error message', - attributes: { key: 'value' }, - severityNumber: undefined, - }); - }); - - it('should call _INTERNAL_captureLog with fatal level', () => { - logger.fatal('Test fatal message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'fatal', - message: 'Test fatal message', - attributes: { key: 'value' }, - severityNumber: undefined, - }); - }); - }); - - it('should handle parameterized strings with parameters', () => { - logger.info(logger.fmt`Hello ${'John'}, your balance is ${100}`, { userId: 123 }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: expect.objectContaining({ - __sentry_template_string__: 'Hello %s, your balance is %s', - __sentry_template_values__: ['John', 100], - }), - attributes: { - userId: 123, - }, - }); - }); - - it('should handle parameterized strings without additional attributes', () => { - logger.debug(logger.fmt`User ${'Alice'} logged in from ${'mobile'}`); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'debug', - message: expect.objectContaining({ - __sentry_template_string__: 'User %s logged in from %s', - __sentry_template_values__: ['Alice', 'mobile'], - }), - }); - }); -}); diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index db0e0aab98a7..5a35a994b641 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -96,10 +96,9 @@ export { consoleLoggingIntegration, createConsolaReporter, featureFlagsIntegration, + logger, } from '@sentry/core'; -export * as logger from './logs/exports'; - export { withSentry } from './handler'; export { instrumentDurableObjectWithSentry } from './durableobject'; export { sentryPagesPlugin } from './pages-plugin'; diff --git a/packages/cloudflare/src/logs/exports.ts b/packages/cloudflare/src/logs/exports.ts deleted file mode 100644 index c21477e378b3..000000000000 --- a/packages/cloudflare/src/logs/exports.ts +++ /dev/null @@ -1,205 +0,0 @@ -import type { Log, LogSeverityLevel, ParameterizedString } from '@sentry/core'; -import { _INTERNAL_captureLog } from '@sentry/core'; - -/** - * Capture a log with the given level. - * - * @param level - The level of the log. - * @param message - The message to log. - * @param attributes - Arbitrary structured data that stores information about the log - e.g., userId: 100. - * @param severityNumber - The severity number of the log. - */ -function captureLog( - level: LogSeverityLevel, - message: ParameterizedString, - attributes?: Log['attributes'], - severityNumber?: Log['severityNumber'], -): void { - _INTERNAL_captureLog({ level, message, attributes, severityNumber }); -} - -/** - * @summary Capture a log with the `trace` level. Requires the `enableLogs` option to be enabled. - * - * @param message - The message to log. - * @param attributes - Arbitrary structured data that stores information about the log - e.g., { userId: 100, route: '/dashboard' }. - * - * @example - * - * ``` - * Sentry.logger.trace('User clicked submit button', { - * buttonId: 'submit-form', - * formId: 'user-profile', - * timestamp: Date.now() - * }); - * ``` - * - * @example With template strings - * - * ``` - * Sentry.logger.trace(Sentry.logger.fmt`User ${user} navigated to ${page}`, { - * userId: '123', - * sessionId: 'abc-xyz' - * }); - * ``` - */ -export function trace(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('trace', message, attributes); -} - -/** - * @summary Capture a log with the `debug` level. Requires the `enableLogs` option to be enabled. - * - * @param message - The message to log. - * @param attributes - Arbitrary structured data that stores information about the log - e.g., { component: 'Header', state: 'loading' }. - * - * @example - * - * ``` - * Sentry.logger.debug('Component mounted', { - * component: 'UserProfile', - * props: { userId: 123 }, - * renderTime: 150 - * }); - * ``` - * - * @example With template strings - * - * ``` - * Sentry.logger.debug(Sentry.logger.fmt`API request to ${endpoint} failed`, { - * statusCode: 404, - * requestId: 'req-123', - * duration: 250 - * }); - * ``` - */ -export function debug(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('debug', message, attributes); -} - -/** - * @summary Capture a log with the `info` level. Requires the `enableLogs` option to be enabled. - * - * @param message - The message to log. - * @param attributes - Arbitrary structured data that stores information about the log - e.g., { feature: 'checkout', status: 'completed' }. - * - * @example - * - * ``` - * Sentry.logger.info('User completed checkout', { - * orderId: 'order-123', - * amount: 99.99, - * paymentMethod: 'credit_card' - * }); - * ``` - * - * @example With template strings - * - * ``` - * Sentry.logger.info(Sentry.logger.fmt`User ${user} updated profile picture`, { - * userId: 'user-123', - * imageSize: '2.5MB', - * timestamp: Date.now() - * }); - * ``` - */ -export function info(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('info', message, attributes); -} - -/** - * @summary Capture a log with the `warn` level. Requires the `enableLogs` option to be enabled. - * - * @param message - The message to log. - * @param attributes - Arbitrary structured data that stores information about the log - e.g., { browser: 'Chrome', version: '91.0' }. - * - * @example - * - * ``` - * Sentry.logger.warn('Browser compatibility issue detected', { - * browser: 'Safari', - * version: '14.0', - * feature: 'WebRTC', - * fallback: 'enabled' - * }); - * ``` - * - * @example With template strings - * - * ``` - * Sentry.logger.warn(Sentry.logger.fmt`API endpoint ${endpoint} is deprecated`, { - * recommendedEndpoint: '/api/v2/users', - * sunsetDate: '2024-12-31', - * clientVersion: '1.2.3' - * }); - * ``` - */ -export function warn(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('warn', message, attributes); -} - -/** - * @summary Capture a log with the `error` level. Requires the `enableLogs` option to be enabled. - * - * @param message - The message to log. - * @param attributes - Arbitrary structured data that stores information about the log - e.g., { error: 'NetworkError', url: '/api/data' }. - * - * @example - * - * ``` - * Sentry.logger.error('Failed to load user data', { - * error: 'NetworkError', - * url: '/api/users/123', - * statusCode: 500, - * retryCount: 3 - * }); - * ``` - * - * @example With template strings - * - * ``` - * Sentry.logger.error(Sentry.logger.fmt`Payment processing failed for order ${orderId}`, { - * error: 'InsufficientFunds', - * amount: 100.00, - * currency: 'USD', - * userId: 'user-456' - * }); - * ``` - */ -export function error(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('error', message, attributes); -} - -/** - * @summary Capture a log with the `fatal` level. Requires the `enableLogs` option to be enabled. - * - * @param message - The message to log. - * @param attributes - Arbitrary structured data that stores information about the log - e.g., { appState: 'corrupted', sessionId: 'abc-123' }. - * - * @example - * - * ``` - * Sentry.logger.fatal('Application state corrupted', { - * lastKnownState: 'authenticated', - * sessionId: 'session-123', - * timestamp: Date.now(), - * recoveryAttempted: true - * }); - * ``` - * - * @example With template strings - * - * ``` - * Sentry.logger.fatal(Sentry.logger.fmt`Critical system failure in ${service}`, { - * service: 'payment-processor', - * errorCode: 'CRITICAL_FAILURE', - * affectedUsers: 150, - * timestamp: Date.now() - * }); - * ``` - */ -export function fatal(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('fatal', message, attributes); -} - -export { fmt } from '@sentry/core'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b971aa8b43a3..80c8ee3fbbb0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -122,7 +122,8 @@ export { trpcMiddleware } from './trpc'; export { wrapMcpServerWithSentry } from './integrations/mcp-server'; export { captureFeedback } from './feedback'; export type { ReportDialogOptions } from './report-dialog'; -export { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_captureSerializedLog } from './logs/exports'; +export { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_captureSerializedLog } from './logs/internal'; +export * as logger from './logs/public-api'; export { consoleLoggingIntegration } from './logs/console-integration'; export { createConsolaReporter } from './integrations/consola'; export { addVercelAiProcessors } from './utils/vercel-ai'; diff --git a/packages/core/src/integrations/consola.ts b/packages/core/src/integrations/consola.ts index cdae9efa17dc..1caa7d2f212f 100644 --- a/packages/core/src/integrations/consola.ts +++ b/packages/core/src/integrations/consola.ts @@ -1,6 +1,6 @@ import type { Client } from '../client'; import { getClient } from '../currentScopes'; -import { _INTERNAL_captureLog } from '../logs/exports'; +import { _INTERNAL_captureLog } from '../logs/internal'; import { formatConsoleArgs } from '../logs/utils'; import type { LogSeverityLevel } from '../types-hoist/log'; diff --git a/packages/core/src/logs/console-integration.ts b/packages/core/src/logs/console-integration.ts index 6c967499800f..a79da511373f 100644 --- a/packages/core/src/logs/console-integration.ts +++ b/packages/core/src/logs/console-integration.ts @@ -6,7 +6,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; import type { ConsoleLevel } from '../types-hoist/instrument'; import type { IntegrationFn } from '../types-hoist/integration'; import { CONSOLE_LEVELS, debug } from '../utils/debug-logger'; -import { _INTERNAL_captureLog } from './exports'; +import { _INTERNAL_captureLog } from './internal'; import { formatConsoleArgs } from './utils'; interface CaptureConsoleOptions { diff --git a/packages/core/src/logs/exports.ts b/packages/core/src/logs/internal.ts similarity index 99% rename from packages/core/src/logs/exports.ts rename to packages/core/src/logs/internal.ts index 702e8605adf1..adcbf0dfb737 100644 --- a/packages/core/src/logs/exports.ts +++ b/packages/core/src/logs/internal.ts @@ -116,10 +116,10 @@ export function _INTERNAL_captureSerializedLog(client: Client, serializedLog: Se */ export function _INTERNAL_captureLog( beforeLog: Log, - client: Client | undefined = getClient(), currentScope = getCurrentScope(), captureSerializedLog: (client: Client, log: SerializedLog) => void = _INTERNAL_captureSerializedLog, ): void { + const client = currentScope?.getClient() ?? getClient(); if (!client) { DEBUG_BUILD && debug.warn('No client available to capture log.'); return; diff --git a/packages/browser/src/log.ts b/packages/core/src/logs/public-api.ts similarity index 70% rename from packages/browser/src/log.ts rename to packages/core/src/logs/public-api.ts index c21477e378b3..27507ab3dfe7 100644 --- a/packages/browser/src/log.ts +++ b/packages/core/src/logs/public-api.ts @@ -1,5 +1,7 @@ -import type { Log, LogSeverityLevel, ParameterizedString } from '@sentry/core'; -import { _INTERNAL_captureLog } from '@sentry/core'; +import type { Scope } from '../scope'; +import type { Log, LogSeverityLevel } from '../types-hoist/log'; +import type { ParameterizedString } from '../types-hoist/parameterize'; +import { _INTERNAL_captureLog } from './internal'; /** * Capture a log with the given level. @@ -7,15 +9,24 @@ import { _INTERNAL_captureLog } from '@sentry/core'; * @param level - The level of the log. * @param message - The message to log. * @param attributes - Arbitrary structured data that stores information about the log - e.g., userId: 100. + * @param scope - The scope to capture the log with. * @param severityNumber - The severity number of the log. */ function captureLog( level: LogSeverityLevel, message: ParameterizedString, attributes?: Log['attributes'], + scope?: Scope, severityNumber?: Log['severityNumber'], ): void { - _INTERNAL_captureLog({ level, message, attributes, severityNumber }); + _INTERNAL_captureLog({ level, message, attributes, severityNumber }, scope); +} + +/** + * Additional metadata to capture the log with. + */ +interface CaptureLogMetadata { + scope?: Scope; } /** @@ -23,6 +34,7 @@ function captureLog( * * @param message - The message to log. * @param attributes - Arbitrary structured data that stores information about the log - e.g., { userId: 100, route: '/dashboard' }. + * @param metadata - additional metadata to capture the log with. * * @example * @@ -43,8 +55,12 @@ function captureLog( * }); * ``` */ -export function trace(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('trace', message, attributes); +export function trace( + message: ParameterizedString, + attributes?: Log['attributes'], + { scope }: CaptureLogMetadata = {}, +): void { + captureLog('trace', message, attributes, scope); } /** @@ -52,6 +68,7 @@ export function trace(message: ParameterizedString, attributes?: Log['attributes * * @param message - The message to log. * @param attributes - Arbitrary structured data that stores information about the log - e.g., { component: 'Header', state: 'loading' }. + * @param metadata - additional metadata to capture the log with. * * @example * @@ -73,8 +90,12 @@ export function trace(message: ParameterizedString, attributes?: Log['attributes * }); * ``` */ -export function debug(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('debug', message, attributes); +export function debug( + message: ParameterizedString, + attributes?: Log['attributes'], + { scope }: CaptureLogMetadata = {}, +): void { + captureLog('debug', message, attributes, scope); } /** @@ -82,6 +103,7 @@ export function debug(message: ParameterizedString, attributes?: Log['attributes * * @param message - The message to log. * @param attributes - Arbitrary structured data that stores information about the log - e.g., { feature: 'checkout', status: 'completed' }. + * @param metadata - additional metadata to capture the log with. * * @example * @@ -103,8 +125,12 @@ export function debug(message: ParameterizedString, attributes?: Log['attributes * }); * ``` */ -export function info(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('info', message, attributes); +export function info( + message: ParameterizedString, + attributes?: Log['attributes'], + { scope }: CaptureLogMetadata = {}, +): void { + captureLog('info', message, attributes, scope); } /** @@ -112,6 +138,7 @@ export function info(message: ParameterizedString, attributes?: Log['attributes' * * @param message - The message to log. * @param attributes - Arbitrary structured data that stores information about the log - e.g., { browser: 'Chrome', version: '91.0' }. + * @param metadata - additional metadata to capture the log with. * * @example * @@ -134,8 +161,12 @@ export function info(message: ParameterizedString, attributes?: Log['attributes' * }); * ``` */ -export function warn(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('warn', message, attributes); +export function warn( + message: ParameterizedString, + attributes?: Log['attributes'], + { scope }: CaptureLogMetadata = {}, +): void { + captureLog('warn', message, attributes, scope); } /** @@ -143,6 +174,7 @@ export function warn(message: ParameterizedString, attributes?: Log['attributes' * * @param message - The message to log. * @param attributes - Arbitrary structured data that stores information about the log - e.g., { error: 'NetworkError', url: '/api/data' }. + * @param metadata - additional metadata to capture the log with. * * @example * @@ -166,8 +198,12 @@ export function warn(message: ParameterizedString, attributes?: Log['attributes' * }); * ``` */ -export function error(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('error', message, attributes); +export function error( + message: ParameterizedString, + attributes?: Log['attributes'], + { scope }: CaptureLogMetadata = {}, +): void { + captureLog('error', message, attributes, scope); } /** @@ -175,6 +211,7 @@ export function error(message: ParameterizedString, attributes?: Log['attributes * * @param message - The message to log. * @param attributes - Arbitrary structured data that stores information about the log - e.g., { appState: 'corrupted', sessionId: 'abc-123' }. + * @param metadata - additional metadata to capture the log with. * * @example * @@ -198,8 +235,12 @@ export function error(message: ParameterizedString, attributes?: Log['attributes * }); * ``` */ -export function fatal(message: ParameterizedString, attributes?: Log['attributes']): void { - captureLog('fatal', message, attributes); +export function fatal( + message: ParameterizedString, + attributes?: Log['attributes'], + { scope }: CaptureLogMetadata = {}, +): void { + captureLog('fatal', message, attributes, scope); } -export { fmt } from '@sentry/core'; +export { fmt } from '../utils/parameterize'; diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index d9c44ed7149d..44e608925535 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -2,7 +2,7 @@ import { createCheckInEnvelope } from './checkin'; import { _getTraceInfoFromScope, Client } from './client'; import { getIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; -import { _INTERNAL_flushLogsBuffer } from './logs/exports'; +import { _INTERNAL_flushLogsBuffer } from './logs/internal'; import type { Scope } from './scope'; import { registerSpanErrorInstrumentation } from './tracing'; import type { CheckIn, MonitorConfig, SerializedCheckIn } from './types-hoist/checkin'; diff --git a/packages/core/test/lib/integrations/consola.test.ts b/packages/core/test/lib/integrations/consola.test.ts index 66830fb75e06..186e5fdc295e 100644 --- a/packages/core/test/lib/integrations/consola.test.ts +++ b/packages/core/test/lib/integrations/consola.test.ts @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getClient } from '../../../src/currentScopes'; +import { getClient, getCurrentScope } from '../../../src/currentScopes'; import { createConsolaReporter } from '../../../src/integrations/consola'; -import { _INTERNAL_captureLog } from '../../../src/logs/exports'; +import { _INTERNAL_captureLog } from '../../../src/logs/internal'; import { formatConsoleArgs } from '../../../src/logs/utils'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; // Mock dependencies -vi.mock('../../../src/logs/exports', () => ({ +vi.mock('../../../src/logs/internal', () => ({ _INTERNAL_captureLog: vi.fn(), })); @@ -16,6 +16,7 @@ vi.mock('../../../src/logs/utils', async actual => ({ vi.mock('../../../src/currentScopes', () => ({ getClient: vi.fn(), + getCurrentScope: vi.fn(), })); describe('createConsolaReporter', () => { @@ -32,7 +33,12 @@ describe('createConsolaReporter', () => { normalizeMaxBreadth: 1000, }); + const mockScope = { + getClient: vi.fn().mockReturnValue(mockClient), + }; + vi.mocked(getClient).mockReturnValue(mockClient); + vi.mocked(getCurrentScope).mockReturnValue(mockScope as any); }); afterEach(() => { diff --git a/packages/core/test/lib/logs/exports.test.ts b/packages/core/test/lib/logs/internal.test.ts similarity index 93% rename from packages/core/test/lib/logs/exports.test.ts rename to packages/core/test/lib/logs/internal.test.ts index c3369784c34a..49339e72b6b1 100644 --- a/packages/core/test/lib/logs/exports.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -5,7 +5,7 @@ import { _INTERNAL_flushLogsBuffer, _INTERNAL_getLogBuffer, logAttributeToSerializedLogAttribute, -} from '../../../src/logs/exports'; +} from '../../../src/logs/internal'; import type { Log } from '../../../src/types-hoist/log'; import * as loggerModule from '../../../src/utils/debug-logger'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; @@ -84,8 +84,10 @@ describe('_INTERNAL_captureLog', () => { it('captures and sends logs', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); - _INTERNAL_captureLog({ level: 'info', message: 'test log message' }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: 'test log message' }, scope); expect(_INTERNAL_getLogBuffer(client)).toHaveLength(1); expect(_INTERNAL_getLogBuffer(client)?.[0]).toEqual( expect.objectContaining({ @@ -103,8 +105,10 @@ describe('_INTERNAL_captureLog', () => { const logWarnSpy = vi.spyOn(loggerModule.debug, 'warn').mockImplementation(() => undefined); const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); - _INTERNAL_captureLog({ level: 'info', message: 'test log message' }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: 'test log message' }, scope); expect(logWarnSpy).toHaveBeenCalledWith('logging option not enabled, log will not be captured.'); expect(_INTERNAL_getLogBuffer(client)).toBeUndefined(); @@ -116,12 +120,13 @@ describe('_INTERNAL_captureLog', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setPropagationContext({ traceId: '3d9355f71e9c444b81161599adac6e29', sampleRand: 1, }); - _INTERNAL_captureLog({ level: 'error', message: 'test log with trace' }, client, scope); + _INTERNAL_captureLog({ level: 'error', message: 'test log with trace' }, scope); expect(_INTERNAL_getLogBuffer(client)?.[0]).toEqual( expect.objectContaining({ @@ -139,8 +144,10 @@ describe('_INTERNAL_captureLog', () => { environment: 'test', }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); - _INTERNAL_captureLog({ level: 'info', message: 'test log with metadata' }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: 'test log with metadata' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ @@ -161,6 +168,8 @@ describe('_INTERNAL_captureLog', () => { enableLogs: true, }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); // Mock getSdkMetadata to return SDK info vi.spyOn(client, 'getSdkMetadata').mockReturnValue({ sdk: { @@ -169,7 +178,7 @@ describe('_INTERNAL_captureLog', () => { }, }); - _INTERNAL_captureLog({ level: 'info', message: 'test log with SDK metadata' }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: 'test log with SDK metadata' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ @@ -190,10 +199,12 @@ describe('_INTERNAL_captureLog', () => { enableLogs: true, }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); // Mock getSdkMetadata to return no SDK info vi.spyOn(client, 'getSdkMetadata').mockReturnValue({}); - _INTERNAL_captureLog({ level: 'info', message: 'test log without SDK metadata' }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: 'test log without SDK metadata' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).not.toEqual( @@ -207,6 +218,8 @@ describe('_INTERNAL_captureLog', () => { it('includes custom attributes in log', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); _INTERNAL_captureLog( { @@ -214,8 +227,7 @@ describe('_INTERNAL_captureLog', () => { message: 'test log with custom attributes', attributes: { userId: '123', component: 'auth' }, }, - client, - undefined, + scope, ); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; @@ -234,16 +246,18 @@ describe('_INTERNAL_captureLog', () => { it('flushes logs buffer when it reaches max size', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); // Fill the buffer to max size (100 is the MAX_LOG_BUFFER_SIZE constant in client.ts) for (let i = 0; i < 100; i++) { - _INTERNAL_captureLog({ level: 'info', message: `log message ${i}` }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: `log message ${i}` }, scope); } expect(_INTERNAL_getLogBuffer(client)).toHaveLength(100); // Add one more to trigger flush - _INTERNAL_captureLog({ level: 'info', message: 'trigger flush' }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: 'trigger flush' }, scope); expect(_INTERNAL_getLogBuffer(client)).toEqual([]); }); @@ -251,6 +265,7 @@ describe('_INTERNAL_captureLog', () => { it('does not flush logs buffer when it is empty', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); + const mockSendEnvelope = vi.spyOn(client as any, 'sendEnvelope').mockImplementation(() => {}); _INTERNAL_flushLogsBuffer(client); expect(mockSendEnvelope).not.toHaveBeenCalled(); @@ -259,10 +274,12 @@ describe('_INTERNAL_captureLog', () => { it('handles parameterized strings correctly', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); const parameterizedMessage = fmt`Hello ${'John'}, welcome to ${'Sentry'}`; - _INTERNAL_captureLog({ level: 'info', message: parameterizedMessage }, client, undefined); + _INTERNAL_captureLog({ level: 'info', message: parameterizedMessage }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ @@ -284,8 +301,10 @@ describe('_INTERNAL_captureLog', () => { it('does not set the template attribute if there are no parameters', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); - _INTERNAL_captureLog({ level: 'debug', message: fmt`User logged in` }, client, undefined); + _INTERNAL_captureLog({ level: 'debug', message: fmt`User logged in` }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({}); @@ -304,6 +323,8 @@ describe('_INTERNAL_captureLog', () => { beforeSendLog, }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); _INTERNAL_captureLog( { @@ -311,8 +332,7 @@ describe('_INTERNAL_captureLog', () => { message: 'original message', attributes: { original: true }, }, - client, - undefined, + scope, ); expect(beforeSendLog).toHaveBeenCalledWith({ @@ -351,14 +371,15 @@ describe('_INTERNAL_captureLog', () => { beforeSendLog, }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); _INTERNAL_captureLog( { level: 'info', message: 'test message', }, - client, - undefined, + scope, ); expect(beforeSendLog).toHaveBeenCalled(); @@ -374,6 +395,8 @@ describe('_INTERNAL_captureLog', () => { const beforeCaptureLogSpy = vi.spyOn(TestClient.prototype, 'emit'); const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); const log: Log = { level: 'info', @@ -381,7 +404,7 @@ describe('_INTERNAL_captureLog', () => { attributes: {}, }; - _INTERNAL_captureLog(log, client, undefined); + _INTERNAL_captureLog(log, scope); expect(beforeCaptureLogSpy).toHaveBeenCalledWith('beforeCaptureLog', log); expect(beforeCaptureLogSpy).toHaveBeenCalledWith('afterCaptureLog', log); @@ -396,13 +419,14 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({ id: '123', email: 'user@example.com', username: 'testuser', }); - _INTERNAL_captureLog({ level: 'info', message: 'test log with user' }, client, scope); + _INTERNAL_captureLog({ level: 'info', message: 'test log with user' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ @@ -429,12 +453,13 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({ id: '123', // email and username are missing }); - _INTERNAL_captureLog({ level: 'info', message: 'test log with partial user' }, client, scope); + _INTERNAL_captureLog({ level: 'info', message: 'test log with partial user' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ @@ -453,13 +478,14 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({ email: 'user@example.com', username: 'testuser', // id is missing }); - _INTERNAL_captureLog({ level: 'info', message: 'test log with email and username' }, client, scope); + _INTERNAL_captureLog({ level: 'info', message: 'test log with email and username' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ @@ -482,9 +508,10 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({}); - _INTERNAL_captureLog({ level: 'info', message: 'test log with empty user' }, client, scope); + _INTERNAL_captureLog({ level: 'info', message: 'test log with empty user' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({}); @@ -500,6 +527,7 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({ id: '123', email: 'user@example.com', @@ -511,7 +539,6 @@ describe('_INTERNAL_captureLog', () => { message: 'test log with user and other attributes', attributes: { component: 'auth', action: 'login' }, }, - client, scope, ); @@ -552,13 +579,14 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({ id: 123, // number instead of string email: 'user@example.com', username: undefined, // undefined value }); - _INTERNAL_captureLog({ level: 'info', message: 'test log with non-string user values' }, client, scope); + _INTERNAL_captureLog({ level: 'info', message: 'test log with non-string user values' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ @@ -581,6 +609,7 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({ id: '123', email: 'user@example.com', @@ -595,7 +624,6 @@ describe('_INTERNAL_captureLog', () => { 'user.custom': 'custom-value', // This should be preserved }, }, - client, scope, ); @@ -624,6 +652,7 @@ describe('_INTERNAL_captureLog', () => { }); const client = new TestClient(options); const scope = new Scope(); + scope.setClient(client); scope.setUser({ id: 'scope-id', email: 'scope@example.com', @@ -639,7 +668,6 @@ describe('_INTERNAL_captureLog', () => { 'other.attr': 'value', }, }, - client, scope, ); @@ -673,7 +701,6 @@ describe('_INTERNAL_captureLog', () => { environment: 'sdk-environment', }); const client = new TestClient(options); - // Mock getSdkMetadata to return SDK info vi.spyOn(client, 'getSdkMetadata').mockReturnValue({ sdk: { @@ -683,6 +710,7 @@ describe('_INTERNAL_captureLog', () => { }); const scope = new Scope(); + scope.setClient(client); _INTERNAL_captureLog( { @@ -696,7 +724,6 @@ describe('_INTERNAL_captureLog', () => { 'user.custom': 'preserved-value', }, }, - client, scope, ); diff --git a/packages/core/test/lib/server-runtime-client.test.ts b/packages/core/test/lib/server-runtime-client.test.ts index 708ac8716070..9fcb431af864 100644 --- a/packages/core/test/lib/server-runtime-client.test.ts +++ b/packages/core/test/lib/server-runtime-client.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, test, vi } from 'vitest'; import { createTransport, Scope } from '../../src'; -import { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer } from '../../src/logs/exports'; +import { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer } from '../../src/logs/internal'; import type { ServerRuntimeClientOptions } from '../../src/server-runtime-client'; import { ServerRuntimeClient } from '../../src/server-runtime-client'; import type { Event, EventHint } from '../../src/types-hoist/event'; @@ -214,12 +214,14 @@ describe('ServerRuntimeClient', () => { enableLogs: true, }); client = new ServerRuntimeClient(options); + const scope = new Scope(); + scope.setClient(client); const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); // Create a large log message that will exceed the 800KB threshold const largeMessage = 'x'.repeat(400_000); // 400KB string - _INTERNAL_captureLog({ message: largeMessage, level: 'info' }, client); + _INTERNAL_captureLog({ message: largeMessage, level: 'info' }, scope); expect(sendEnvelopeSpy).toHaveBeenCalledTimes(1); expect(client['_logWeight']).toBe(0); // Weight should be reset after flush @@ -231,12 +233,14 @@ describe('ServerRuntimeClient', () => { enableLogs: true, }); client = new ServerRuntimeClient(options); + const scope = new Scope(); + scope.setClient(client); const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); // Create a log message that won't exceed the threshold const message = 'x'.repeat(100_000); // 100KB string - _INTERNAL_captureLog({ message, level: 'info' }, client); + _INTERNAL_captureLog({ message, level: 'info' }, scope); expect(sendEnvelopeSpy).not.toHaveBeenCalled(); expect(client['_logWeight']).toBeGreaterThan(0); @@ -248,12 +252,14 @@ describe('ServerRuntimeClient', () => { enableLogs: true, }); client = new ServerRuntimeClient(options); + const scope = new Scope(); + scope.setClient(client); const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); // Add some logs - _INTERNAL_captureLog({ message: 'test1', level: 'info' }, client); - _INTERNAL_captureLog({ message: 'test2', level: 'info' }, client); + _INTERNAL_captureLog({ message: 'test1', level: 'info' }, scope); + _INTERNAL_captureLog({ message: 'test2', level: 'info' }, scope); // Trigger flush directly _INTERNAL_flushLogsBuffer(client); @@ -267,12 +273,14 @@ describe('ServerRuntimeClient', () => { dsn: PUBLIC_DSN, }); client = new ServerRuntimeClient(options); + const scope = new Scope(); + scope.setClient(client); const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); // Create a large log message const largeMessage = 'x'.repeat(400_000); - _INTERNAL_captureLog({ message: largeMessage, level: 'info' }, client); + _INTERNAL_captureLog({ message: largeMessage, level: 'info' }, scope); expect(sendEnvelopeSpy).not.toHaveBeenCalled(); expect(client['_logWeight']).toBe(0); @@ -284,12 +292,14 @@ describe('ServerRuntimeClient', () => { enableLogs: true, }); client = new ServerRuntimeClient(options); + const scope = new Scope(); + scope.setClient(client); const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); // Add some logs - _INTERNAL_captureLog({ message: 'test1', level: 'info' }, client); - _INTERNAL_captureLog({ message: 'test2', level: 'info' }, client); + _INTERNAL_captureLog({ message: 'test1', level: 'info' }, scope); + _INTERNAL_captureLog({ message: 'test2', level: 'info' }, scope); // Trigger flush event client.emit('flush'); diff --git a/packages/node-core/src/logs/capture.ts b/packages/node-core/src/logs/capture.ts index 17f94399f9bf..4c2fdc73a34c 100644 --- a/packages/node-core/src/logs/capture.ts +++ b/packages/node-core/src/logs/capture.ts @@ -1,10 +1,28 @@ import { format } from 'node:util'; -import type { Log, LogSeverityLevel, ParameterizedString } from '@sentry/core'; +import type { Log, LogSeverityLevel, ParameterizedString, Scope } from '@sentry/core'; import { _INTERNAL_captureLog } from '@sentry/core'; -export type CaptureLogArgs = - | [message: ParameterizedString, attributes?: Log['attributes']] - | [messageTemplate: string, messageParams: Array, attributes?: Log['attributes']]; +/** + * Additional metadata to capture the log with. + */ +interface CaptureLogMetadata { + scope?: Scope; +} + +type CaptureLogArgWithTemplate = [ + messageTemplate: string, + messageParams: Array, + attributes?: Log['attributes'], + metadata?: CaptureLogMetadata, +]; + +type CaptureLogArgWithoutTemplate = [ + message: ParameterizedString, + attributes?: Log['attributes'], + metadata?: CaptureLogMetadata, +]; + +export type CaptureLogArgs = CaptureLogArgWithTemplate | CaptureLogArgWithoutTemplate; /** * Capture a log with the given level. @@ -14,16 +32,19 @@ export type CaptureLogArgs = * @param attributes - Arbitrary structured data that stores information about the log - e.g., userId: 100. */ export function captureLog(level: LogSeverityLevel, ...args: CaptureLogArgs): void { - const [messageOrMessageTemplate, paramsOrAttributes, maybeAttributes] = args; + const [messageOrMessageTemplate, paramsOrAttributes, maybeAttributesOrMetadata, maybeMetadata] = args; if (Array.isArray(paramsOrAttributes)) { - const attributes = { ...maybeAttributes }; + const attributes = { ...(maybeAttributesOrMetadata as Log['attributes']) }; attributes['sentry.message.template'] = messageOrMessageTemplate; paramsOrAttributes.forEach((param, index) => { attributes[`sentry.message.parameter.${index}`] = param; }); const message = format(messageOrMessageTemplate, ...paramsOrAttributes); - _INTERNAL_captureLog({ level, message, attributes }); + _INTERNAL_captureLog({ level, message, attributes }, maybeMetadata?.scope); } else { - _INTERNAL_captureLog({ level, message: messageOrMessageTemplate, attributes: paramsOrAttributes }); + _INTERNAL_captureLog( + { level, message: messageOrMessageTemplate, attributes: paramsOrAttributes }, + maybeMetadata?.scope, + ); } } diff --git a/packages/node-core/test/logs/exports.test.ts b/packages/node-core/test/logs/exports.test.ts index 9e1cc4900e29..45da1722abc8 100644 --- a/packages/node-core/test/logs/exports.test.ts +++ b/packages/node-core/test/logs/exports.test.ts @@ -36,110 +36,140 @@ describe('Node Logger', () => { it('should call _INTERNAL_captureLog with trace level', () => { nodeLogger.trace('Test trace message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'trace', - message: 'Test trace message', - attributes: { key: 'value' }, - }); + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'trace', + message: 'Test trace message', + attributes: { key: 'value' }, + }, + undefined, + ); }); it('should call _INTERNAL_captureLog with debug level', () => { nodeLogger.debug('Test debug message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'debug', - message: 'Test debug message', - attributes: { key: 'value' }, - }); + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'debug', + message: 'Test debug message', + attributes: { key: 'value' }, + }, + undefined, + ); }); it('should call _INTERNAL_captureLog with info level', () => { nodeLogger.info('Test info message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Test info message', - attributes: { key: 'value' }, - }); + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'info', + message: 'Test info message', + attributes: { key: 'value' }, + }, + undefined, + ); }); it('should call _INTERNAL_captureLog with warn level', () => { nodeLogger.warn('Test warn message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'warn', - message: 'Test warn message', - attributes: { key: 'value' }, - }); + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'warn', + message: 'Test warn message', + attributes: { key: 'value' }, + }, + undefined, + ); }); it('should call _INTERNAL_captureLog with error level', () => { nodeLogger.error('Test error message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'error', - message: 'Test error message', - attributes: { key: 'value' }, - }); + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'error', + message: 'Test error message', + attributes: { key: 'value' }, + }, + undefined, + ); }); it('should call _INTERNAL_captureLog with fatal level', () => { nodeLogger.fatal('Test fatal message', { key: 'value' }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'fatal', - message: 'Test fatal message', - attributes: { key: 'value' }, - }); + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'fatal', + message: 'Test fatal message', + attributes: { key: 'value' }, + }, + undefined, + ); }); }); describe('Template string logging', () => { it('should handle template strings with parameters', () => { nodeLogger.info('Hello %s, your balance is %d', ['John', 100], { userId: 123 }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Hello John, your balance is 100', - attributes: { - userId: 123, - 'sentry.message.template': 'Hello %s, your balance is %d', - 'sentry.message.parameter.0': 'John', - 'sentry.message.parameter.1': 100, + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'info', + message: 'Hello John, your balance is 100', + attributes: { + userId: 123, + 'sentry.message.template': 'Hello %s, your balance is %d', + 'sentry.message.parameter.0': 'John', + 'sentry.message.parameter.1': 100, + }, }, - }); + undefined, + ); }); it('should handle template strings without additional attributes', () => { nodeLogger.debug('User %s logged in from %s', ['Alice', 'mobile']); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'debug', - message: 'User Alice logged in from mobile', - attributes: { - 'sentry.message.template': 'User %s logged in from %s', - 'sentry.message.parameter.0': 'Alice', - 'sentry.message.parameter.1': 'mobile', + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'debug', + message: 'User Alice logged in from mobile', + attributes: { + 'sentry.message.template': 'User %s logged in from %s', + 'sentry.message.parameter.0': 'Alice', + 'sentry.message.parameter.1': 'mobile', + }, }, - }); + undefined, + ); }); it('should handle parameterized strings with parameters', () => { nodeLogger.info(nodeLogger.fmt`Hello ${'John'}, your balance is ${100}`, { userId: 123 }); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: expect.objectContaining({ - __sentry_template_string__: 'Hello %s, your balance is %s', - __sentry_template_values__: ['John', 100], - }), - attributes: { - userId: 123, + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'info', + message: expect.objectContaining({ + __sentry_template_string__: 'Hello %s, your balance is %s', + __sentry_template_values__: ['John', 100], + }), + attributes: { + userId: 123, + }, }, - }); + undefined, + ); }); it('should handle parameterized strings without additional attributes', () => { nodeLogger.debug(nodeLogger.fmt`User ${'Alice'} logged in from ${'mobile'}`); - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'debug', - message: expect.objectContaining({ - __sentry_template_string__: 'User %s logged in from %s', - __sentry_template_values__: ['Alice', 'mobile'], - }), - }); + expect(mockCaptureLog).toHaveBeenCalledWith( + { + level: 'debug', + message: expect.objectContaining({ + __sentry_template_string__: 'User %s logged in from %s', + __sentry_template_values__: ['Alice', 'mobile'], + }), + }, + undefined, + ); }); }); }); diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 3e432c06c1fd..032e39f1b203 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -95,6 +95,7 @@ export { consoleLoggingIntegration, createConsolaReporter, featureFlagsIntegration, + logger, } from '@sentry/core'; export { VercelEdgeClient } from './client'; @@ -102,5 +103,3 @@ export { getDefaultIntegrations, init } from './sdk'; export { winterCGFetchIntegration } from './integrations/wintercg-fetch'; export { vercelAIIntegration } from './integrations/tracing/vercelai'; - -export * as logger from './logs/exports';