Skip to content

Commit 043e606

Browse files
committed
feat(core): Create template attributes in consoleLoggingIntegration
1 parent 3237296 commit 043e606

File tree

4 files changed

+138
-5
lines changed

4 files changed

+138
-5
lines changed

dev-packages/browser-integration-tests/suites/public-api/logger/integration/subject.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ console.log('Mixed:', 'prefix', { obj: true }, [4, 5, 6], 'suffix');
1313

1414
console.log('');
1515

16+
// Test console substitution patterns (should NOT generate template attributes)
17+
console.log('String substitution %s %d', 'test', 42);
18+
console.log('Object substitution %o', { key: 'value' });
19+
20+
// Test multiple arguments without substitutions (should generate template attributes)
21+
console.log('first', 0, 1, 2);
22+
console.log('hello', true, null, undefined);
23+
1624
Sentry.flush();

dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
1818
expect(envelopeItems[0]).toEqual([
1919
{
2020
type: 'log',
21-
item_count: 11,
21+
item_count: 15,
2222
content_type: 'application/vnd.sentry.items.log+json',
2323
},
2424
{
@@ -33,6 +33,9 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
3333
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
3434
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
3535
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
36+
'sentry.message.template': { value: 'console.trace {} {}', type: 'string' },
37+
'sentry.message.parameter.0': { value: 123, type: 'integer' },
38+
'sentry.message.parameter.1': { value: false, type: 'boolean' },
3639
},
3740
},
3841
{
@@ -45,6 +48,9 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
4548
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
4649
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
4750
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
51+
'sentry.message.template': { value: 'console.debug {} {}', type: 'string' },
52+
'sentry.message.parameter.0': { value: 123, type: 'integer' },
53+
'sentry.message.parameter.1': { value: false, type: 'boolean' },
4854
},
4955
},
5056
{
@@ -57,6 +63,9 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
5763
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
5864
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
5965
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
66+
'sentry.message.template': { value: 'console.log {} {}', type: 'string' },
67+
'sentry.message.parameter.0': { value: 123, type: 'integer' },
68+
'sentry.message.parameter.1': { value: false, type: 'boolean' },
6069
},
6170
},
6271
{
@@ -69,6 +78,9 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
6978
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
7079
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
7180
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
81+
'sentry.message.template': { value: 'console.info {} {}', type: 'string' },
82+
'sentry.message.parameter.0': { value: 123, type: 'integer' },
83+
'sentry.message.parameter.1': { value: false, type: 'boolean' },
7284
},
7385
},
7486
{
@@ -81,6 +93,9 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
8193
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
8294
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
8395
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
96+
'sentry.message.template': { value: 'console.warn {} {}', type: 'string' },
97+
'sentry.message.parameter.0': { value: 123, type: 'integer' },
98+
'sentry.message.parameter.1': { value: false, type: 'boolean' },
8499
},
85100
},
86101
{
@@ -93,6 +108,9 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
93108
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
94109
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
95110
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
111+
'sentry.message.template': { value: 'console.error {} {}', type: 'string' },
112+
'sentry.message.parameter.0': { value: 123, type: 'integer' },
113+
'sentry.message.parameter.1': { value: false, type: 'boolean' },
96114
},
97115
},
98116
{
@@ -117,6 +135,8 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
117135
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
118136
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
119137
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
138+
'sentry.message.template': { value: 'Object: {}', type: 'string' },
139+
'sentry.message.parameter.0': { value: '{"key":"value","nested":{"prop":123}}', type: 'string' },
120140
},
121141
},
122142
{
@@ -129,6 +149,8 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
129149
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
130150
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
131151
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
152+
'sentry.message.template': { value: 'Array: {}', type: 'string' },
153+
'sentry.message.parameter.0': { value: '[1,2,3,"string"]', type: 'string' },
132154
},
133155
},
134156
{
@@ -141,6 +163,11 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
141163
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
142164
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
143165
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
166+
'sentry.message.template': { value: 'Mixed: {} {} {} {}', type: 'string' },
167+
'sentry.message.parameter.0': { value: 'prefix', type: 'string' },
168+
'sentry.message.parameter.1': { value: '{"obj":true}', type: 'string' },
169+
'sentry.message.parameter.2': { value: '[4,5,6]', type: 'string' },
170+
'sentry.message.parameter.3': { value: 'suffix', type: 'string' },
144171
},
145172
},
146173
{
@@ -155,6 +182,62 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
155182
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
156183
},
157184
},
185+
{
186+
timestamp: expect.any(Number),
187+
level: 'info',
188+
severity_number: 10,
189+
trace_id: expect.any(String),
190+
body: 'String substitution %s %d test 42',
191+
attributes: {
192+
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
193+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
194+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
195+
},
196+
},
197+
{
198+
timestamp: expect.any(Number),
199+
level: 'info',
200+
severity_number: 10,
201+
trace_id: expect.any(String),
202+
body: 'Object substitution %o {"key":"value"}',
203+
attributes: {
204+
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
205+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
206+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
207+
},
208+
},
209+
{
210+
timestamp: expect.any(Number),
211+
level: 'info',
212+
severity_number: 10,
213+
trace_id: expect.any(String),
214+
body: 'first 0 1 2',
215+
attributes: {
216+
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
217+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
218+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
219+
'sentry.message.template': { value: 'first {} {} {}', type: 'string' },
220+
'sentry.message.parameter.0': { value: 0, type: 'integer' },
221+
'sentry.message.parameter.1': { value: 1, type: 'integer' },
222+
'sentry.message.parameter.2': { value: 2, type: 'integer' },
223+
},
224+
},
225+
{
226+
timestamp: expect.any(Number),
227+
level: 'info',
228+
severity_number: 10,
229+
trace_id: expect.any(String),
230+
body: 'hello true null undefined',
231+
attributes: {
232+
'sentry.origin': { value: 'auto.console.logging', type: 'string' },
233+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
234+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
235+
'sentry.message.template': { value: 'hello {} {} {}', type: 'string' },
236+
'sentry.message.parameter.0': { value: true, type: 'boolean' },
237+
'sentry.message.parameter.1': { value: 'null', type: 'string' },
238+
'sentry.message.parameter.2': { value: '', type: 'string' },
239+
},
240+
},
158241
],
159242
},
160243
]);

packages/core/src/logs/console-integration.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { ConsoleLevel } from '../types-hoist/instrument';
77
import type { IntegrationFn } from '../types-hoist/integration';
88
import { CONSOLE_LEVELS, debug } from '../utils/debug-logger';
99
import { _INTERNAL_captureLog } from './exports';
10-
import { formatConsoleArgs } from './utils';
10+
import { createConsoleTemplateAttributes, formatConsoleArgs, hasConsoleSubstitutions } from './utils';
1111

1212
interface CaptureConsoleOptions {
1313
levels: ConsoleLevel[];
@@ -36,9 +36,11 @@ const _consoleLoggingIntegration = ((options: Partial<CaptureConsoleOptions> = {
3636
return;
3737
}
3838

39+
const firstArg = args[0];
40+
const followingArgs = args.slice(1);
41+
3942
if (level === 'assert') {
40-
if (!args[0]) {
41-
const followingArgs = args.slice(1);
43+
if (!firstArg) {
4244
const assertionMessage =
4345
followingArgs.length > 0
4446
? `Assertion failed: ${formatConsoleArgs(followingArgs, normalizeDepth, normalizeMaxBreadth)}`
@@ -49,11 +51,19 @@ const _consoleLoggingIntegration = ((options: Partial<CaptureConsoleOptions> = {
4951
}
5052

5153
const isLevelLog = level === 'log';
54+
55+
const shouldGenerateTemplate =
56+
args.length > 1 && typeof args[0] === 'string' && !hasConsoleSubstitutions(args[0]);
57+
const attributes = {
58+
...DEFAULT_ATTRIBUTES,
59+
...(shouldGenerateTemplate ? createConsoleTemplateAttributes(firstArg, followingArgs) : {}),
60+
};
61+
5262
_INTERNAL_captureLog({
5363
level: isLevelLog ? 'info' : level,
5464
message: formatConsoleArgs(args, normalizeDepth, normalizeMaxBreadth),
5565
severityNumber: isLevelLog ? 10 : undefined,
56-
attributes: DEFAULT_ATTRIBUTES,
66+
attributes,
5767
});
5868
});
5969
},

packages/core/src/logs/utils.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,35 @@ export function safeJoinConsoleArgs(values: unknown[], normalizeDepth: number, n
3737
)
3838
.join(' ');
3939
}
40+
41+
/**
42+
* Checks if a string contains console substitution patterns like %s, %d, %i, %f, %o, %O, %c.
43+
*
44+
* @param str - The string to check
45+
* @returns true if the string contains console substitution patterns
46+
*/
47+
export function hasConsoleSubstitutions(str: string): boolean {
48+
// Match console substitution patterns: %s, %d, %i, %f, %o, %O, %c
49+
return /%[sdifocO]/.test(str);
50+
}
51+
52+
/**
53+
* Creates template attributes for multiple console arguments.
54+
*
55+
* @param args - The console arguments
56+
* @returns An object with template and parameter attributes
57+
*/
58+
export function createConsoleTemplateAttributes(firstArg: unknown, followingArgs: unknown[]): Record<string, unknown> {
59+
const attributes: Record<string, unknown> = {};
60+
61+
// Create template with placeholders for each argument
62+
const template = new Array(followingArgs.length).fill('{}').join(' ');
63+
attributes['sentry.message.template'] = `${firstArg} ${template}`;
64+
65+
// Add each argument as a parameter
66+
followingArgs.forEach((arg, index) => {
67+
attributes[`sentry.message.parameter.${index}`] = arg;
68+
});
69+
70+
return attributes;
71+
}

0 commit comments

Comments
 (0)