Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as Sentry from '@sentry/node-core';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

const client = new Sentry.NodeClient({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
transport: loggingTransport,
stackParser: Sentry.defaultStackParser,
integrations: [],
enableLogs: true,
sendDefaultPii: true,
});

const customScope = new Sentry.Scope();
customScope.setClient(client);
customScope.update({ user: { username: 'h4cktor' } });
client.init();

async function run(): Promise<void> {
Sentry.logger.info('test info', { foo: 'bar1' }, { scope: customScope });
Sentry.logger.info('test info with %d', [1], { foo: 'bar2' }, { scope: customScope });
Sentry.logger.info(Sentry.logger.fmt`test info with fmt ${1}`, { foo: 'bar3' }, { scope: customScope });

await Sentry.flush();
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
void run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { afterAll, describe, expect, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

describe('logger public API', () => {
afterAll(() => {
cleanupChildProcesses();
});

test('captures logs with custom scopes and parameters in different forms', async () => {
const runner = createRunner(__dirname, 'subject.ts')
.expect({
log: {
items: [
{
attributes: {
foo: {
type: 'string',
value: 'bar1',
},
'sentry.sdk.name': {
type: 'string',
value: 'sentry.javascript.node',
},
'sentry.sdk.version': {
type: 'string',
value: expect.any(String),
},
'server.address': {
type: 'string',
value: 'M6QX4Q5HKV.local',
},
'user.name': {
type: 'string',
value: 'h4cktor',
},
},
body: 'test info',
level: 'info',
severity_number: 9,
timestamp: expect.any(Number),
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
},
{
attributes: {
foo: {
type: 'string',
value: 'bar2',
},
'sentry.message.parameter.0': {
type: 'integer',
value: 1,
},
'sentry.message.template': {
type: 'string',
value: 'test info with %d',
},
'sentry.sdk.name': {
type: 'string',
value: 'sentry.javascript.node',
},
'sentry.sdk.version': {
type: 'string',
value: expect.any(String),
},
'server.address': {
type: 'string',
value: 'M6QX4Q5HKV.local',
},
'user.name': {
type: 'string',
value: 'h4cktor',
},
},
body: 'test info with 1',
level: 'info',
severity_number: 9,
timestamp: expect.any(Number),
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
},
{
attributes: {
foo: {
type: 'string',
value: 'bar3',
},
'sentry.message.parameter.0': {
type: 'integer',
value: 1,
},
'sentry.message.template': {
type: 'string',
value: 'test info with fmt %s',
},
'sentry.sdk.name': {
type: 'string',
value: 'sentry.javascript.node',
},
'sentry.sdk.version': {
type: 'string',
value: expect.any(String),
},
'server.address': {
type: 'string',
value: 'M6QX4Q5HKV.local',
},
'user.name': {
type: 'string',
value: 'h4cktor',
},
},
body: 'test info with fmt 1',
level: 'info',
severity_number: 9,
timestamp: expect.any(Number),
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
},
],
},
})
.start();

await runner.completed();
});
});
6 changes: 4 additions & 2 deletions packages/node-core/src/logs/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export type CaptureLogArgs = CaptureLogArgWithTemplate | CaptureLogArgWithoutTem
export function captureLog(level: LogSeverityLevel, ...args: CaptureLogArgs): void {
const [messageOrMessageTemplate, paramsOrAttributes, maybeAttributesOrMetadata, maybeMetadata] = args;
if (Array.isArray(paramsOrAttributes)) {
const attributes = { ...(maybeAttributesOrMetadata as Log['attributes']) };
// type-casting here because from the type definitions we know that `maybeAttributesOrMetadata` is an attributes object (or undefined)
const attributes = { ...(maybeAttributesOrMetadata as Log['attributes'] | undefined) };
attributes['sentry.message.template'] = messageOrMessageTemplate;
paramsOrAttributes.forEach((param, index) => {
attributes[`sentry.message.parameter.${index}`] = param;
Expand All @@ -44,7 +45,8 @@ export function captureLog(level: LogSeverityLevel, ...args: CaptureLogArgs): vo
} else {
_INTERNAL_captureLog(
{ level, message: messageOrMessageTemplate, attributes: paramsOrAttributes },
maybeMetadata?.scope,
// type-casting here because from the type definitions we know that `maybeAttributesOrMetadata` is a metadata object (or undefined)
(maybeAttributesOrMetadata as CaptureLogMetadata | undefined)?.scope ?? maybeMetadata?.scope,
);
}
}
47 changes: 47 additions & 0 deletions packages/node-core/test/logs/exports.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as sentryCore from '@sentry/core';
import { Scope } from '@sentry/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import * as nodeLogger from '../../src/logs/exports';

Expand Down Expand Up @@ -172,4 +173,50 @@ describe('Node Logger', () => {
);
});
});

describe('scustom cope', () => {
it('calls _INTERNAL_captureLog with custom scope for basic log message', () => {
const customScope = new Scope();
nodeLogger.debug('User logged in', undefined, { scope: customScope });
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'debug',
message: 'User logged in',
},
customScope,
);
});

it('calls _INTERNAL_captureLog with custom scope for parametrized log message', () => {
const customScope = new Scope();
nodeLogger.debug('User %s logged in from %s', ['Alice', 'mobile'], undefined, { scope: customScope });
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',
},
},
customScope,
);
});

it('calls _INTERNAL_captureLog with custom scope for fmt log message', () => {
const customScope = new Scope();
nodeLogger.debug(nodeLogger.fmt`User ${'Alice'} logged in from ${'mobile'}`, undefined, { scope: customScope });
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'debug',
message: expect.objectContaining({
__sentry_template_string__: 'User %s logged in from %s',
__sentry_template_values__: ['Alice', 'mobile'],
}),
},
customScope,
);
});
});
});