Skip to content

Commit b7a340a

Browse files
committed
Merge branch 'develop' into cg-sync-master-develop
2 parents cedef2e + 7bc1380 commit b7a340a

File tree

6 files changed

+155
-9
lines changed

6 files changed

+155
-9
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/node';
2+
3+
Sentry.init({
4+
dsn: process.env.SENTRY_DSN,
5+
release: '1.0',
6+
enableLogs: true,
7+
integrations: [Sentry.pinoIntegration({ autoInstrument: false })],
8+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as Sentry from '@sentry/node';
2+
import pino from 'pino';
3+
4+
const logger = pino({ name: 'myapp' });
5+
Sentry.pinoIntegration.trackLogger(logger);
6+
7+
const loggerIgnore = pino({ name: 'ignore' });
8+
9+
loggerIgnore.info('this should be ignored');
10+
11+
Sentry.withIsolationScope(() => {
12+
Sentry.startSpan({ name: 'startup' }, () => {
13+
logger.info({ user: 'user-id', something: { more: 3, complex: 'nope' } }, 'hello world');
14+
});
15+
});
16+
17+
setTimeout(() => {
18+
Sentry.withIsolationScope(() => {
19+
Sentry.startSpan({ name: 'later' }, () => {
20+
logger.error(new Error('oh no'));
21+
});
22+
});
23+
}, 1000);

dev-packages/node-integration-tests/suites/pino/scenario.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import pino from 'pino';
33

44
const logger = pino({ name: 'myapp' });
55

6+
const ignoredLogger = pino({ name: 'ignored' });
7+
Sentry.pinoIntegration.untrackLogger(ignoredLogger);
8+
9+
ignoredLogger.info('this will not be tracked');
10+
611
Sentry.withIsolationScope(() => {
712
Sentry.startSpan({ name: 'startup' }, () => {
813
logger.info({ user: 'user-id', something: { more: 3, complex: 'nope' } }, 'hello world');

dev-packages/node-integration-tests/suites/pino/test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,54 @@ conditionalTest({ min: 20 })('Pino integration', () => {
173173
.start()
174174
.completed();
175175
});
176+
177+
test('captures logs when autoInstrument is false and logger is tracked', async () => {
178+
const instrumentPath = join(__dirname, 'instrument-auto-off.mjs');
179+
180+
await createRunner(__dirname, 'scenario-track.mjs')
181+
.withMockSentryServer()
182+
.withInstrument(instrumentPath)
183+
.expect({
184+
log: {
185+
items: [
186+
{
187+
timestamp: expect.any(Number),
188+
level: 'info',
189+
body: 'hello world',
190+
trace_id: expect.any(String),
191+
severity_number: 9,
192+
attributes: expect.objectContaining({
193+
'pino.logger.name': { value: 'myapp', type: 'string' },
194+
'pino.logger.level': { value: 30, type: 'integer' },
195+
user: { value: 'user-id', type: 'string' },
196+
something: {
197+
type: 'string',
198+
value: '{"more":3,"complex":"nope"}',
199+
},
200+
'sentry.origin': { value: 'auto.logging.pino', type: 'string' },
201+
'sentry.release': { value: '1.0', type: 'string' },
202+
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
203+
}),
204+
},
205+
{
206+
timestamp: expect.any(Number),
207+
level: 'error',
208+
body: 'oh no',
209+
trace_id: expect.any(String),
210+
severity_number: 17,
211+
attributes: expect.objectContaining({
212+
'pino.logger.name': { value: 'myapp', type: 'string' },
213+
'pino.logger.level': { value: 50, type: 'integer' },
214+
err: { value: '{}', type: 'string' },
215+
'sentry.origin': { value: 'auto.logging.pino', type: 'string' },
216+
'sentry.release': { value: '1.0', type: 'string' },
217+
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
218+
}),
219+
},
220+
],
221+
},
222+
})
223+
.start()
224+
.completed();
225+
});
176226
});

packages/node-core/src/integrations/pino.ts

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { tracingChannel } from 'node:diagnostics_channel';
2-
import type { IntegrationFn, LogSeverityLevel } from '@sentry/core';
2+
import type { Integration, IntegrationFn, LogSeverityLevel } from '@sentry/core';
33
import {
44
_INTERNAL_captureLog,
55
addExceptionMechanism,
@@ -11,13 +11,16 @@ import {
1111
} from '@sentry/core';
1212
import { addInstrumentationConfig } from '../sdk/injectLoader';
1313

14+
const SENTRY_TRACK_SYMBOL = Symbol('sentry-track-pino-logger');
15+
1416
type LevelMapping = {
1517
// Fortunately pino uses the same levels as Sentry
1618
labels: { [level: number]: LogSeverityLevel };
1719
};
1820

1921
type Pino = {
2022
levels: LevelMapping;
23+
[SENTRY_TRACK_SYMBOL]?: 'track' | 'ignore';
2124
};
2225

2326
type MergeObject = {
@@ -28,6 +31,17 @@ type MergeObject = {
2831
type PinoHookArgs = [MergeObject, string, number];
2932

3033
type PinoOptions = {
34+
/**
35+
* Automatically instrument all Pino loggers.
36+
*
37+
* When set to `false`, only loggers marked with `pinoIntegration.trackLogger(logger)` will be captured.
38+
*
39+
* @default true
40+
*/
41+
autoInstrument: boolean;
42+
/**
43+
* Options to enable capturing of error events.
44+
*/
3145
error: {
3246
/**
3347
* Levels that trigger capturing of events.
@@ -43,6 +57,9 @@ type PinoOptions = {
4357
*/
4458
handled: boolean;
4559
};
60+
/**
61+
* Options to enable capturing of logs.
62+
*/
4663
log: {
4764
/**
4865
* Levels that trigger capturing of logs. Logs are only captured if
@@ -55,6 +72,7 @@ type PinoOptions = {
5572
};
5673

5774
const DEFAULT_OPTIONS: PinoOptions = {
75+
autoInstrument: true,
5876
error: { levels: [], handled: true },
5977
log: { levels: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] },
6078
};
@@ -63,18 +81,18 @@ type DeepPartial<T> = {
6381
[P in keyof T]?: T[P] extends object ? Partial<T[P]> : T[P];
6482
};
6583

66-
/**
67-
* Integration for Pino logging library.
68-
* Captures Pino logs as Sentry logs and optionally captures some log levels as events.
69-
*
70-
* Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0
71-
*/
72-
export const pinoIntegration = defineIntegration((userOptions: DeepPartial<PinoOptions> = {}) => {
84+
const _pinoIntegration = defineIntegration((userOptions: DeepPartial<PinoOptions> = {}) => {
7385
const options: PinoOptions = {
86+
autoInstrument: userOptions.autoInstrument === false ? userOptions.autoInstrument : DEFAULT_OPTIONS.autoInstrument,
7487
error: { ...DEFAULT_OPTIONS.error, ...userOptions.error },
7588
log: { ...DEFAULT_OPTIONS.log, ...userOptions.log },
7689
};
7790

91+
function shouldTrackLogger(logger: Pino): boolean {
92+
const override = logger[SENTRY_TRACK_SYMBOL];
93+
return override === 'track' || (override !== 'ignore' && options.autoInstrument);
94+
}
95+
7896
return {
7997
name: 'Pino',
8098
setup: client => {
@@ -95,6 +113,10 @@ export const pinoIntegration = defineIntegration((userOptions: DeepPartial<PinoO
95113
const integratedChannel = tracingChannel('pino_asJson');
96114

97115
function onPinoStart(self: Pino, args: PinoHookArgs, result: string): void {
116+
if (!shouldTrackLogger(self)) {
117+
return;
118+
}
119+
98120
const [obj, message, levelNumber] = args;
99121
const level = self?.levels?.labels?.[levelNumber] || 'info';
100122

@@ -157,3 +179,42 @@ export const pinoIntegration = defineIntegration((userOptions: DeepPartial<PinoO
157179
},
158180
};
159181
}) satisfies IntegrationFn;
182+
183+
interface PinoIntegrationFunction {
184+
(userOptions?: DeepPartial<PinoOptions>): Integration;
185+
/**
186+
* Marks a Pino logger to be tracked by the Pino integration.
187+
*
188+
* @param logger A Pino logger instance.
189+
*/
190+
trackLogger(logger: unknown): void;
191+
/**
192+
* Marks a Pino logger to be ignored by the Pino integration.
193+
*
194+
* @param logger A Pino logger instance.
195+
*/
196+
untrackLogger(logger: unknown): void;
197+
}
198+
199+
/**
200+
* Integration for Pino logging library.
201+
* Captures Pino logs as Sentry logs and optionally captures some log levels as events.
202+
*
203+
* By default, all Pino loggers will be captured. To ignore a specific logger, use `pinoIntegration.untrackLogger(logger)`.
204+
*
205+
* If you disable automatic instrumentation with `autoInstrument: false`, you can mark specific loggers to be tracked with `pinoIntegration.trackLogger(logger)`.
206+
*
207+
* Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0
208+
*/
209+
export const pinoIntegration = Object.assign(_pinoIntegration, {
210+
trackLogger(logger: unknown): void {
211+
if (logger && typeof logger === 'object' && 'levels' in logger) {
212+
(logger as Pino)[SENTRY_TRACK_SYMBOL] = 'track';
213+
}
214+
},
215+
untrackLogger(logger: unknown): void {
216+
if (logger && typeof logger === 'object' && 'levels' in logger) {
217+
(logger as Pino)[SENTRY_TRACK_SYMBOL] = 'ignore';
218+
}
219+
},
220+
}) as PinoIntegrationFunction;

packages/solid/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ Pass your router instance from `createRouter` to the integration.
6767
```js
6868
import * as Sentry from '@sentry/solid';
6969
import { tanstackRouterBrowserTracingIntegration } from '@sentry/solid/tanstackrouter';
70-
import { Route, Router } from '@solidjs/router';
7170

7271
const router = createRouter({
7372
// your router config

0 commit comments

Comments
 (0)