Skip to content

Commit 9ead55d

Browse files
authored
feat(core): Extract objects as structured logs in consoleLoggingIntegration (#21385)
Aligns the console integration so object-first logs are structured, matching the consola integration behavior. If the first argument is an object, the keys are added as log attributes. Reference PR (consola): #19534 The universal principle is this: ``` IF first_argument is plain_object: attributes ← first_argument keys/values message ← second_argument (if string) OR empty remaining_args → sentry.message.parameter.{0, 1, 2...} ELSE: message ← formatted(all_args) args[1:] → sentry.message.parameter.{0, 1, 2...} ``` ### Example Snippet ```js console.warn({ component: 'PaymentService', retryCount: 3 }, 500, 'TIMEOUT'); console.info( { userId: 42, action: 'purchase' }, 'Console [OBJ-FIRST+PARAMS]: Checkout complete', testOrder.orderId, testOrder.total, ); ``` #### Before <img width="1434" height="662" alt="image" src="https://github.com/user-attachments/assets/19f310dd-d417-48fd-acb3-7695e28417b7" /> #### After <img width="1425" height="665" alt="image" src="https://github.com/user-attachments/assets/6f722ce3-e698-46fa-ba22-5f1f42304f65" />
1 parent f52787f commit 9ead55d

3 files changed

Lines changed: 540 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
- **feat(core): Extract objects as structured logs in `consoleLoggingIntegration`**
8+
9+
The `consoleLoggingIntegration` now extracts structured log attributes when the first argument is a plain object.
10+
11+
```js
12+
// Object keys become log attributes
13+
console.log({ userId: 123, action: 'login' }, 'User logged in');
14+
// → attributes: { userId: 123, action: "login" }
15+
16+
// Non-object first args use template + parameters (unchanged)
17+
console.log('Hello', 'world', 123);
18+
// → sentry.message.template: "Hello {} {}"
19+
```
20+
721
## 10.57.0
822

923
### Important Changes

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
66
import type { ConsoleLevel } from '../types/instrument';
77
import type { IntegrationFn } from '../types/integration';
88
import { CONSOLE_LEVELS, debug } from '../utils/debug-logger';
9+
import { isPlainObject } from '../utils/is';
10+
import { normalize } from '../utils/normalize';
911
import { _INTERNAL_captureLog } from './internal';
1012
import { createConsoleTemplateAttributes, formatConsoleArgs, hasConsoleSubstitutions } from './utils';
1113

@@ -52,12 +54,32 @@ const _consoleLoggingIntegration = ((options: Partial<CaptureConsoleOptions> = {
5254

5355
const isLevelLog = level === 'log';
5456

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-
};
57+
const attributes: Record<string, unknown> = { ...DEFAULT_ATTRIBUTES };
58+
59+
if (isPlainObject(firstArg)) {
60+
// Object-first: extract object keys as attributes, remaining args as parameters
61+
Object.assign(attributes, normalize(firstArg, normalizeDepth, normalizeMaxBreadth));
62+
63+
const remainingArgsStartIndex = typeof args[1] === 'string' ? 2 : 1;
64+
const remainingArgs = args.slice(remainingArgsStartIndex);
65+
66+
remainingArgs.forEach((arg, index) => {
67+
attributes[`sentry.message.parameter.${index}`] = normalize(arg, normalizeDepth, normalizeMaxBreadth);
68+
});
69+
} else {
70+
// Fallback: template + parameters when first arg is a string without substitutions
71+
const shouldGenerateTemplate =
72+
followingArgs.length > 0 && typeof firstArg === 'string' && !hasConsoleSubstitutions(firstArg);
73+
74+
if (shouldGenerateTemplate) {
75+
const templateAttrs = createConsoleTemplateAttributes(firstArg, followingArgs);
76+
for (const [key, value] of Object.entries(templateAttrs)) {
77+
attributes[key] = key.startsWith('sentry.message.parameter.')
78+
? normalize(value, normalizeDepth, normalizeMaxBreadth)
79+
: value;
80+
}
81+
}
82+
}
6183

6284
_INTERNAL_captureLog({
6385
level: isLevelLog ? 'info' : level,

0 commit comments

Comments
 (0)