diff --git a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts index 6b320e26eb8a..086e7ebc292c 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts @@ -13,6 +13,9 @@ Sentry.init({ debug: !!process.env.DEBUG, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, + _experiments: { + enableLogs: true, + }, }); import { TRPCError, initTRPC } from '@trpc/server'; @@ -30,6 +33,11 @@ app.get('/test-success', function (req, res) { res.send({ version: 'v1' }); }); +app.get('/test-log', function (req, res) { + Sentry.logger.debug('Accessed /test-log route'); + res.send({ message: 'Log sent' }); +}); + app.get('/test-param/:param', function (req, res) { res.send({ paramWas: req.params.param }); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/logs.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/logs.test.ts new file mode 100644 index 000000000000..f2e125696af6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/logs.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test'; +import { waitForEnvelopeItem } from '@sentry-internal/test-utils'; +import type { SerializedLog, SerializedLogContainer } from '@sentry/core'; + +test('should send logs', async ({ baseURL }) => { + const logEnvelopePromise = waitForEnvelopeItem('node-express', envelope => { + return envelope[0].type === 'log' && (envelope[1] as SerializedLogContainer).items[0]?.level === 'debug'; + }); + + await fetch(`${baseURL}/test-log`); + + const logEnvelope = await logEnvelopePromise; + const log = (logEnvelope[1] as SerializedLogContainer).items[0]; + expect(log?.level).toBe('debug'); + expect(log?.body).toBe('Accessed /test-log route'); +}); diff --git a/packages/core/src/logs/exports.ts b/packages/core/src/logs/exports.ts index 8496f7fba595..a2451c6cc6c0 100644 --- a/packages/core/src/logs/exports.ts +++ b/packages/core/src/logs/exports.ts @@ -7,12 +7,14 @@ import { _getSpanForScope } from '../utils/spanOnScope'; import { isParameterizedString } from '../utils-hoist/is'; import { logger } from '../utils-hoist/logger'; import { timestampInSeconds } from '../utils-hoist/time'; +import { GLOBAL_OBJ } from '../utils-hoist/worldwide'; import { SEVERITY_TEXT_TO_SEVERITY_NUMBER } from './constants'; import { createLogEnvelope } from './envelope'; const MAX_LOG_BUFFER_SIZE = 100; -const CLIENT_TO_LOG_BUFFER_MAP = new WeakMap>(); +// The reference to the Client <> LogBuffer map is stored to ensure it's always the same +GLOBAL_OBJ._sentryClientToLogBufferMap = new WeakMap>(); /** * Converts a log attribute to a serialized log attribute. @@ -149,11 +151,11 @@ export function _INTERNAL_captureLog( ), }; - const logBuffer = CLIENT_TO_LOG_BUFFER_MAP.get(client); + const logBuffer = _INTERNAL_getLogBuffer(client); if (logBuffer === undefined) { - CLIENT_TO_LOG_BUFFER_MAP.set(client, [serializedLog]); + GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [serializedLog]); } else { - CLIENT_TO_LOG_BUFFER_MAP.set(client, [...logBuffer, serializedLog]); + GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [...logBuffer, serializedLog]); if (logBuffer.length >= MAX_LOG_BUFFER_SIZE) { _INTERNAL_flushLogsBuffer(client, logBuffer); } @@ -181,7 +183,7 @@ export function _INTERNAL_flushLogsBuffer(client: Client, maybeLogBuffer?: Array const envelope = createLogEnvelope(logBuffer, clientOptions._metadata, clientOptions.tunnel, client.getDsn()); // Clear the log buffer after envelopes have been constructed. - CLIENT_TO_LOG_BUFFER_MAP.set(client, []); + GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, []); client.emit('flushLogs'); @@ -199,5 +201,5 @@ export function _INTERNAL_flushLogsBuffer(client: Client, maybeLogBuffer?: Array * @returns The log buffer for the given client. */ export function _INTERNAL_getLogBuffer(client: Client): Array | undefined { - return CLIENT_TO_LOG_BUFFER_MAP.get(client); + return GLOBAL_OBJ._sentryClientToLogBufferMap?.get(client); } diff --git a/packages/core/src/utils-hoist/worldwide.ts b/packages/core/src/utils-hoist/worldwide.ts index 426831038f13..3a396d96f809 100644 --- a/packages/core/src/utils-hoist/worldwide.ts +++ b/packages/core/src/utils-hoist/worldwide.ts @@ -13,6 +13,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Carrier } from '../carrier'; +import type { Client } from '../client'; +import type { SerializedLog } from '../types-hoist/log'; import type { SdkSource } from './env'; /** Internal global with common properties and Sentry extensions */ @@ -35,6 +37,12 @@ export type InternalGlobal = { id?: string; }; SENTRY_SDK_SOURCE?: SdkSource; + /** + * A map of Sentry clients to their log buffers. + * + * This is used to store logs that are sent to Sentry. + */ + _sentryClientToLogBufferMap?: WeakMap>; /** * Debug IDs are indirectly injected by Sentry CLI or bundler plugins to directly reference a particular source map * for resolving of a source file. The injected code will place an entry into the record for each loaded bundle/JS