Skip to content

Commit 1884cf2

Browse files
committed
feat(core): Add experimental scopeValuesAppliedToLogs option
1 parent 56aac53 commit 1884cf2

3 files changed

Lines changed: 123 additions & 1 deletion

File tree

packages/core/src/logs/exports.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function _INTERNAL_captureLog(
7373
}
7474

7575
const { _experiments, release, environment } = client.getOptions();
76-
const { enableLogs = false, beforeSendLog } = _experiments ?? {};
76+
const { enableLogs = false, beforeSendLog, scopeValuesAppliedToLogs = [] } = _experiments ?? {};
7777
if (!enableLogs) {
7878
DEBUG_BUILD && logger.warn('logging option not enabled, log will not be captured.');
7979
return;
@@ -118,6 +118,22 @@ export function _INTERNAL_captureLog(
118118
});
119119
}
120120

121+
const { tags, user } = scope.getScopeData();
122+
scopeValuesAppliedToLogs.forEach(scopeAttribute => {
123+
switch (scopeAttribute) {
124+
case 'tags':
125+
Object.entries(tags).forEach(([key, value]) => {
126+
logAttributes[`sentry.tag.${key}`] = value;
127+
});
128+
break;
129+
case 'user':
130+
logAttributes['user.id'] = user?.id;
131+
logAttributes['user.email'] = user?.email;
132+
logAttributes['user.name'] = user?.name;
133+
break;
134+
}
135+
});
136+
121137
const span = _getSpanForScope(scope);
122138
if (span) {
123139
// Add the parent span ID to the log attributes for trace context

packages/core/src/types-hoist/options.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
200200
* @returns A new log that will be sent | null.
201201
*/
202202
beforeSendLog?: (log: Log) => Log | null;
203+
204+
/**
205+
* Setting this field to an array of attribute keys will add the corresponding attributes
206+
* from the Sentry Scope to to all outgoing logs as log attributes.
207+
*
208+
* The allowed keys are `user` and `tags`. Defaults to `[]`.
209+
*/
210+
scopeValuesAppliedToLogs?: Array<'user' | 'tags'>;
203211
};
204212

205213
/**

packages/core/test/lib/logs/exports.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,102 @@ describe('_INTERNAL_captureLog', () => {
351351
expect(beforeCaptureLogSpy).toHaveBeenCalledWith('afterCaptureLog', log);
352352
beforeCaptureLogSpy.mockRestore();
353353
});
354+
355+
it('includes scope tags in log attributes when scopeValuesAppliedToLogs includes tags', () => {
356+
const options = getDefaultTestClientOptions({
357+
dsn: PUBLIC_DSN,
358+
_experiments: { enableLogs: true, scopeValuesAppliedToLogs: ['tags'] },
359+
});
360+
const client = new TestClient(options);
361+
const scope = new Scope();
362+
scope.setTag('service', 'auth-service');
363+
scope.setTag('region', 'us-east-1');
364+
scope.setTag('deployment', 'blue');
365+
366+
_INTERNAL_captureLog({ level: 'info', message: 'test log with tags' }, client, scope);
367+
368+
const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes;
369+
expect(logAttributes).toEqual(
370+
expect.arrayContaining([
371+
expect.objectContaining({ key: 'sentry.tag.service', value: { stringValue: 'auth-service' } }),
372+
expect.objectContaining({ key: 'sentry.tag.region', value: { stringValue: 'us-east-1' } }),
373+
expect.objectContaining({ key: 'sentry.tag.deployment', value: { stringValue: 'blue' } }),
374+
]),
375+
);
376+
});
377+
378+
it('includes user data in log attributes when scopeValuesAppliedToLogs includes user', () => {
379+
const options = getDefaultTestClientOptions({
380+
dsn: PUBLIC_DSN,
381+
_experiments: { enableLogs: true, scopeValuesAppliedToLogs: ['user'] },
382+
});
383+
const client = new TestClient(options);
384+
const scope = new Scope();
385+
scope.setUser({
386+
id: '123',
387+
email: 'test@example.com',
388+
name: 'Test User',
389+
});
390+
391+
_INTERNAL_captureLog({ level: 'info', message: 'test log with user data' }, client, scope);
392+
393+
const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes;
394+
expect(logAttributes).toEqual(
395+
expect.arrayContaining([
396+
expect.objectContaining({ key: 'user.id', value: { stringValue: '123' } }),
397+
expect.objectContaining({ key: 'user.email', value: { stringValue: 'test@example.com' } }),
398+
expect.objectContaining({ key: 'user.name', value: { stringValue: 'Test User' } }),
399+
]),
400+
);
401+
});
402+
403+
it('includes both tags and user data when scopeValuesAppliedToLogs includes both', () => {
404+
const options = getDefaultTestClientOptions({
405+
dsn: PUBLIC_DSN,
406+
_experiments: { enableLogs: true, scopeValuesAppliedToLogs: ['tags', 'user'] },
407+
});
408+
const client = new TestClient(options);
409+
const scope = new Scope();
410+
scope.setTag('environment', 'production');
411+
scope.setUser({
412+
id: '123',
413+
email: 'test@example.com',
414+
});
415+
416+
_INTERNAL_captureLog({ level: 'info', message: 'test log with tags and user data' }, client, scope);
417+
418+
const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes;
419+
expect(logAttributes).toEqual(
420+
expect.arrayContaining([
421+
expect.objectContaining({ key: 'sentry.tag.environment', value: { stringValue: 'production' } }),
422+
expect.objectContaining({ key: 'user.id', value: { stringValue: '123' } }),
423+
expect.objectContaining({ key: 'user.email', value: { stringValue: 'test@example.com' } }),
424+
]),
425+
);
426+
});
427+
428+
it('does not include scope values when scopeValuesAppliedToLogs is empty', () => {
429+
const options = getDefaultTestClientOptions({
430+
dsn: PUBLIC_DSN,
431+
_experiments: { enableLogs: true, scopeValuesAppliedToLogs: [] },
432+
});
433+
const client = new TestClient(options);
434+
const scope = new Scope();
435+
scope.setTag('environment', 'production');
436+
scope.setUser({
437+
id: '123',
438+
email: 'test@example.com',
439+
});
440+
441+
_INTERNAL_captureLog({ level: 'info', message: 'test log without scope values' }, client, scope);
442+
443+
const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes;
444+
expect(logAttributes).not.toEqual(
445+
expect.arrayContaining([
446+
expect.objectContaining({ key: 'sentry.tag.environment' }),
447+
expect.objectContaining({ key: 'user.id' }),
448+
expect.objectContaining({ key: 'user.email' }),
449+
]),
450+
);
451+
});
354452
});

0 commit comments

Comments
 (0)