Skip to content

feat(logs): Ensure client & scope can be passed to log functions #16874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ module.exports = [
gzip: true,
limit: '95 KB',
},
{
name: '@sentry/browser (incl. Logs)',
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'logger'),
gzip: true,
limit: '26 KB',
},
{
name: '@sentry/browser (incl. Feedback)',
path: 'packages/browser/build/npm/esm/index.js',
Expand Down
62 changes: 46 additions & 16 deletions packages/browser/src/log.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Log, LogSeverityLevel, ParameterizedString } from '@sentry/core';
import type { Client, Log, LogSeverityLevel, ParameterizedString, Scope } from '@sentry/core';
import { _INTERNAL_captureLog } from '@sentry/core';

/**
Expand All @@ -7,15 +7,15 @@ 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 severityNumber - The severity number of the log.
*/
function captureLog(
level: LogSeverityLevel,
message: ParameterizedString,
attributes?: Log['attributes'],
severityNumber?: Log['severityNumber'],
client?: Client,
scope?: Scope,
): void {
_INTERNAL_captureLog({ level, message, attributes, severityNumber });
_INTERNAL_captureLog({ level, message, attributes }, client, scope);
}

/**
Expand Down Expand Up @@ -43,8 +43,13 @@ function captureLog(
* });
* ```
*/
export function trace(message: ParameterizedString, attributes?: Log['attributes']): void {
captureLog('trace', message, attributes);
export function trace(
message: ParameterizedString,
attributes?: Log['attributes'],
client?: Client,
scope?: Scope,
): void {
captureLog('trace', message, attributes, client, scope);
}

/**
Expand Down Expand Up @@ -73,8 +78,13 @@ 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'],
client?: Client,
scope?: Scope,
): void {
captureLog('debug', message, attributes, client, scope);
}

/**
Expand Down Expand Up @@ -103,8 +113,13 @@ 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'],
client?: Client,
scope?: Scope,
): void {
captureLog('info', message, attributes, client, scope);
}

/**
Expand Down Expand Up @@ -134,8 +149,13 @@ 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'],
client?: Client,
scope?: Scope,
): void {
captureLog('warn', message, attributes, client, scope);
}

/**
Expand Down Expand Up @@ -166,8 +186,13 @@ 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'],
client?: Client,
scope?: Scope,
): void {
captureLog('error', message, attributes, client, scope);
}

/**
Expand Down Expand Up @@ -198,8 +223,13 @@ 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'],
client?: Client,
scope?: Scope,
): void {
captureLog('fatal', message, attributes, client, scope);
}

export { fmt } from '@sentry/core';
152 changes: 99 additions & 53 deletions packages/browser/test/log.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
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 { BrowserClient, init, logger } from '../src';
import { getDefaultBrowserClientOptions } from './helper/browser-client-options';
import { makeSimpleTransport } from './mocks/simpletransport';

const dsn = 'https://[email protected]/4291';
Expand Down Expand Up @@ -66,87 +67,132 @@ describe('Logger', () => {

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,
});
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'trace',
message: 'Test trace message',
attributes: { key: 'value' },
},
undefined,
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,
});
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'debug',
message: 'Test debug message',
attributes: { key: 'value' },
},
undefined,
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,
});
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'info',
message: 'Test info message',
attributes: { key: 'value' },
},
undefined,
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,
});
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'warn',
message: 'Test warn message',
attributes: { key: 'value' },
},
undefined,
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,
});
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'error',
message: 'Test error message',
attributes: { key: 'value' },
},
undefined,
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,
});
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'fatal',
message: 'Test fatal message',
attributes: { key: 'value' },
},
undefined,
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,
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,
undefined,
);
});

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'],
}),
});
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level: 'debug',
message: expect.objectContaining({
__sentry_template_string__: 'User %s logged in from %s',
__sentry_template_values__: ['Alice', 'mobile'],
}),
},
undefined,
undefined,
);
});

it.each(['trace', 'debug', 'info', 'warn', 'error', 'fatal'] as const)(
'should allow to pass a client and scope for %s level',
level => {
const client = new BrowserClient(getDefaultBrowserClientOptions());
const scope = new sentryCore.Scope();

logger[level]('Test message', { key: 'value' }, client, scope);
expect(mockCaptureLog).toHaveBeenCalledWith(
{
level,
message: 'Test message',
attributes: { key: 'value' },
},
client,
scope,
);
},
);
});
41 changes: 29 additions & 12 deletions packages/node-core/src/logs/capture.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { format } from 'node:util';
import type { Log, LogSeverityLevel, ParameterizedString } from '@sentry/core';
import type { Client, 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<unknown>, attributes?: Log['attributes']];
type CaptureLogsArgsParametrized = [
message: ParameterizedString,
attributes?: Log['attributes'],
client?: Client,
scope?: Scope,
];
type CaptureLogsArgsTemplate = [
messageTemplate: string,
messageParams: Array<unknown>,
attributes?: Log['attributes'],
client?: Client,
scope?: Scope,
];

export type CaptureLogArgs = CaptureLogsArgsParametrized | CaptureLogsArgsTemplate;

/**
* Capture a log with the given level.
Expand All @@ -14,16 +26,21 @@ 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;
if (Array.isArray(paramsOrAttributes)) {
const attributes = { ...maybeAttributes };
attributes['sentry.message.template'] = messageOrMessageTemplate;
paramsOrAttributes.forEach((param, index) => {
if (isTemplateArgs(args)) {
const [messageTemplate, messageParams, messageAttributes, client, scope] = args;
const attributes = { ...messageAttributes };
attributes['sentry.message.template'] = messageTemplate;
messageParams.forEach((param, index) => {
attributes[`sentry.message.parameter.${index}`] = param;
});
const message = format(messageOrMessageTemplate, ...paramsOrAttributes);
_INTERNAL_captureLog({ level, message, attributes });
const message = format(messageTemplate, ...messageParams);
_INTERNAL_captureLog({ level, message, attributes }, client, scope);
} else {
_INTERNAL_captureLog({ level, message: messageOrMessageTemplate, attributes: paramsOrAttributes });
const [message, attributes, client, scope] = args;
_INTERNAL_captureLog({ level, message, attributes }, client, scope);
}
}

function isTemplateArgs(args: CaptureLogArgs): args is CaptureLogsArgsTemplate {
return Array.isArray(args[1]);
}
Loading