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
21 changes: 17 additions & 4 deletions packages/core/src/integrations/consola.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getClient } from '../currentScopes';
import { _INTERNAL_captureLog } from '../logs/internal';
import { formatConsoleArgs } from '../logs/utils';
import type { LogSeverityLevel } from '../types-hoist/log';
import { normalize } from '../utils/normalize';

/**
* Options for the Sentry Consola reporter.
Expand Down Expand Up @@ -61,8 +62,9 @@ export interface ConsolaReporter {
*/
export interface ConsolaLogObject {
/**
* Allows additional custom properties to be set on the log object.
* These properties will be captured as log attributes with a 'consola.' prefix.
* Allows additional custom properties to be set on the log object. These properties will be captured as log attributes.
*
* Additional properties are set when passing a single object with a `message` (`consola.[type]({ message: '', ... })`) or if the reporter is called directly
*
* @example
* ```ts
Expand All @@ -73,7 +75,7 @@ export interface ConsolaLogObject {
* userId: 123,
* sessionId: 'abc-123'
* });
* // Will create attributes: consola.userId and consola.sessionId
* // Will create attributes: `userId` and `sessionId`
* ```
*/
[key: string]: unknown;
Expand Down Expand Up @@ -152,6 +154,10 @@ export interface ConsolaLogObject {
*
* When provided, this is the final formatted message. When not provided,
* the message should be constructed from the `args` array.
*
* Note: In reporters, `message` is typically undefined. It is primarily for
* `consola.[type]({ message: 'xxx' })` usage and is normalized into `args` before
* reporters receive the log object. See: https://github.com/unjs/consola/issues/406#issuecomment-3684792551
*/
message?: string;
}
Expand Down Expand Up @@ -194,8 +200,9 @@ export function createConsolaReporter(options: ConsolaReporterOptions = {}): Con

return {
log(logObj: ConsolaLogObject) {
// We need to exclude certain known properties from being added as additional attributes
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { type, level, message: consolaMessage, args, tag, date: _date, ...attributes } = logObj;
const { type, level, message: consolaMessage, args, tag, date: _date, ...rest } = logObj;

// Get client - use provided client or current client
const client = providedClient || getClient();
Expand Down Expand Up @@ -223,7 +230,13 @@ export function createConsolaReporter(options: ConsolaReporterOptions = {}): Con
}
const message = messageParts.join(' ');

const attributes: Record<string, unknown> = {};

// Build attributes
for (const [key, value] of Object.entries(rest)) {
attributes[key] = normalize(value, normalizeDepth, normalizeMaxBreadth);
}

attributes['sentry.origin'] = 'auto.log.consola';

if (tag) {
Expand Down
47 changes: 46 additions & 1 deletion packages/core/test/lib/integrations/consola.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('createConsolaReporter', () => {
});

describe('message and args handling', () => {
it('should format message from args when message is not provided', () => {
it('should format message from args', () => {
const logObj = {
type: 'info',
args: ['Hello', 'world', 123, { key: 'value' }],
Expand Down Expand Up @@ -101,6 +101,51 @@ describe('createConsolaReporter', () => {
},
});
});

it('consola-merged: args=[message] with extra keys on log object', () => {
sentryReporter.log({
type: 'log',
level: 2,
args: ['Hello', 'world', { some: 'obj' }],
userId: 123,
action: 'login',
time: '2026-02-24T10:24:04.477Z',
smallObj: { firstLevel: { secondLevel: { thirdLevel: { fourthLevel: 'deep' } } } },
tag: '',
});

const call = vi.mocked(_INTERNAL_captureLog).mock.calls[0]![0];

// Message from args
expect(call.message).toBe('Hello world {"some":"obj"}');
expect(call.attributes).toMatchObject({
'consola.type': 'log',
'consola.level': 2,
userId: 123,
smallObj: { firstLevel: { secondLevel: { thirdLevel: '[Object]' } } }, // Object is normalized
action: 'login',
time: '2026-02-24T10:24:04.477Z',
'sentry.origin': 'auto.log.consola',
});
expect(call.attributes?.['sentry.message.parameter.0']).toBeUndefined();
});

it('capturing custom keys mimicking direct reporter.log({ type, message, userId, sessionId })', () => {
sentryReporter.log({
type: 'info',
message: 'User action',
userId: 123,
sessionId: 'abc-123',
});

const call = vi.mocked(_INTERNAL_captureLog).mock.calls[0]![0];
expect(call.message).toBe('User action');
expect(call.attributes).toMatchObject({
'consola.type': 'info',
userId: 123,
sessionId: 'abc-123',
});
});
});

describe('level mapping', () => {
Expand Down
Loading