Skip to content

Commit a49c946

Browse files
authored
fix(logs): Ensure logs can be flushed correctly (#16216)
resolves #16110 Stores the Client to LogBuffer WeakMap onto the global object so logs can be retrieved correctly during flushing. Previously, the WeakMap reference would be different at flush time, causing no logs to be found for any given client. Adds an express e2e tests to ensure logs are flushed correctly.
1 parent 3a81041 commit a49c946

File tree

4 files changed

+40
-6
lines changed

4 files changed

+40
-6
lines changed

dev-packages/e2e-tests/test-applications/node-express/src/app.ts

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Sentry.init({
1313
debug: !!process.env.DEBUG,
1414
tunnel: `http://localhost:3031/`, // proxy server
1515
tracesSampleRate: 1,
16+
_experiments: {
17+
enableLogs: true,
18+
},
1619
});
1720

1821
import { TRPCError, initTRPC } from '@trpc/server';
@@ -30,6 +33,11 @@ app.get('/test-success', function (req, res) {
3033
res.send({ version: 'v1' });
3134
});
3235

36+
app.get('/test-log', function (req, res) {
37+
Sentry.logger.debug('Accessed /test-log route');
38+
res.send({ message: 'Log sent' });
39+
});
40+
3341
app.get('/test-param/:param', function (req, res) {
3442
res.send({ paramWas: req.params.param });
3543
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForEnvelopeItem } from '@sentry-internal/test-utils';
3+
import type { SerializedLog, SerializedLogContainer } from '@sentry/core';
4+
5+
test('should send logs', async ({ baseURL }) => {
6+
const logEnvelopePromise = waitForEnvelopeItem('node-express', envelope => {
7+
return envelope[0].type === 'log' && (envelope[1] as SerializedLogContainer).items[0]?.level === 'debug';
8+
});
9+
10+
await fetch(`${baseURL}/test-log`);
11+
12+
const logEnvelope = await logEnvelopePromise;
13+
const log = (logEnvelope[1] as SerializedLogContainer).items[0];
14+
expect(log?.level).toBe('debug');
15+
expect(log?.body).toBe('Accessed /test-log route');
16+
});

packages/core/src/logs/exports.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import { _getSpanForScope } from '../utils/spanOnScope';
77
import { isParameterizedString } from '../utils-hoist/is';
88
import { logger } from '../utils-hoist/logger';
99
import { timestampInSeconds } from '../utils-hoist/time';
10+
import { GLOBAL_OBJ } from '../utils-hoist/worldwide';
1011
import { SEVERITY_TEXT_TO_SEVERITY_NUMBER } from './constants';
1112
import { createLogEnvelope } from './envelope';
1213

1314
const MAX_LOG_BUFFER_SIZE = 100;
1415

15-
const CLIENT_TO_LOG_BUFFER_MAP = new WeakMap<Client, Array<SerializedLog>>();
16+
// The reference to the Client <> LogBuffer map is stored to ensure it's always the same
17+
GLOBAL_OBJ._sentryClientToLogBufferMap = new WeakMap<Client, Array<SerializedLog>>();
1618

1719
/**
1820
* Converts a log attribute to a serialized log attribute.
@@ -149,11 +151,11 @@ export function _INTERNAL_captureLog(
149151
),
150152
};
151153

152-
const logBuffer = CLIENT_TO_LOG_BUFFER_MAP.get(client);
154+
const logBuffer = _INTERNAL_getLogBuffer(client);
153155
if (logBuffer === undefined) {
154-
CLIENT_TO_LOG_BUFFER_MAP.set(client, [serializedLog]);
156+
GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [serializedLog]);
155157
} else {
156-
CLIENT_TO_LOG_BUFFER_MAP.set(client, [...logBuffer, serializedLog]);
158+
GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [...logBuffer, serializedLog]);
157159
if (logBuffer.length >= MAX_LOG_BUFFER_SIZE) {
158160
_INTERNAL_flushLogsBuffer(client, logBuffer);
159161
}
@@ -181,7 +183,7 @@ export function _INTERNAL_flushLogsBuffer(client: Client, maybeLogBuffer?: Array
181183
const envelope = createLogEnvelope(logBuffer, clientOptions._metadata, clientOptions.tunnel, client.getDsn());
182184

183185
// Clear the log buffer after envelopes have been constructed.
184-
CLIENT_TO_LOG_BUFFER_MAP.set(client, []);
186+
GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, []);
185187

186188
client.emit('flushLogs');
187189

@@ -199,5 +201,5 @@ export function _INTERNAL_flushLogsBuffer(client: Client, maybeLogBuffer?: Array
199201
* @returns The log buffer for the given client.
200202
*/
201203
export function _INTERNAL_getLogBuffer(client: Client): Array<SerializedLog> | undefined {
202-
return CLIENT_TO_LOG_BUFFER_MAP.get(client);
204+
return GLOBAL_OBJ._sentryClientToLogBufferMap?.get(client);
203205
}

packages/core/src/utils-hoist/worldwide.ts

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
/* eslint-disable @typescript-eslint/no-explicit-any */
1414

1515
import type { Carrier } from '../carrier';
16+
import type { Client } from '../client';
17+
import type { SerializedLog } from '../types-hoist/log';
1618
import type { SdkSource } from './env';
1719

1820
/** Internal global with common properties and Sentry extensions */
@@ -35,6 +37,12 @@ export type InternalGlobal = {
3537
id?: string;
3638
};
3739
SENTRY_SDK_SOURCE?: SdkSource;
40+
/**
41+
* A map of Sentry clients to their log buffers.
42+
*
43+
* This is used to store logs that are sent to Sentry.
44+
*/
45+
_sentryClientToLogBufferMap?: WeakMap<Client, Array<SerializedLog>>;
3846
/**
3947
* Debug IDs are indirectly injected by Sentry CLI or bundler plugins to directly reference a particular source map
4048
* for resolving of a source file. The injected code will place an entry into the record for each loaded bundle/JS

0 commit comments

Comments
 (0)