Skip to content

Commit 0f8d1d9

Browse files
committed
feat(node): Add support for winston logger
1 parent d7fbb17 commit 0f8d1d9

File tree

4 files changed

+171
-31
lines changed

4 files changed

+171
-31
lines changed

Diff for: packages/node/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,6 @@ export type {
152152
Span,
153153
} from '@sentry/core';
154154

155-
import * as logger from './log';
155+
import * as logger from './logs/exports';
156156

157157
export { logger };

Diff for: packages/node/src/integrations/winston.ts

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/* eslint-disable @typescript-eslint/ban-ts-comment */
2+
import { getClient } from '@sentry/core';
3+
import type { LogSeverityLevel } from '@sentry/core';
4+
import { captureLog } from '../logs/capture';
5+
6+
const DEFAULT_CAPTURED_LEVELS: Array<LogSeverityLevel> = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
7+
8+
/**
9+
* Options for the Sentry Winston transport.
10+
*/
11+
interface WinstonTransportOptions {
12+
/**
13+
* Use this option to filter which levels should be captured. By default, all levels are captured.
14+
*
15+
* @example
16+
* ```ts
17+
* const transport = Sentry.createSentryWinstonTransport(Transport, {
18+
* // Only capture error and warn logs
19+
* levels: ['error', 'warn'],
20+
* });
21+
* ```
22+
*/
23+
levels?: Array<LogSeverityLevel>;
24+
}
25+
26+
/**
27+
* Creates a new Sentry Winston transport that fowards logs to Sentry.
28+
*
29+
* @param TransportClass - The Winston transport class to extend.
30+
* @returns The extended transport class.
31+
*
32+
* @experimental This method will experience breaking changes. This is not yet part of
33+
* the stable Sentry SDK API and can be changed or removed without warning.
34+
*
35+
* @example
36+
* ```ts
37+
* const winston = require('winston');
38+
* const Transport = require('winston-transport');
39+
*
40+
* const transport = Sentry.createSentryWinstonTransport(Transport);
41+
*
42+
* const logger = winston.createLogger({
43+
* transports: [transport],
44+
* });
45+
* ```
46+
*/
47+
export function createSentryWinstonTransport<TransportStreamInstance extends object>(
48+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
49+
TransportClass: new (options?: any) => TransportStreamInstance,
50+
sentryWinstonOptions?: WinstonTransportOptions,
51+
): typeof TransportClass {
52+
// @ts-ignore - We know this is safe because SentryWinstonTransport extends TransportClass
53+
class SentryWinstonTransport extends TransportClass {
54+
private _levels: Set<LogSeverityLevel>;
55+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56+
public constructor(options?: any) {
57+
super(options);
58+
this._levels = new Set(sentryWinstonOptions?.levels ?? DEFAULT_CAPTURED_LEVELS);
59+
}
60+
61+
/**
62+
* Foward a winston log to the Sentry SDK.
63+
*/
64+
public log(info: unknown, callback: () => void): void {
65+
try {
66+
setImmediate(() => {
67+
// @ts-ignore - We know this is safe because SentryWinstonTransport extends TransportClass
68+
this.emit('logged', info);
69+
});
70+
71+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
72+
const { level, message, timestamp, ...attributes } = info as Record<string, unknown>;
73+
const logSeverityLevel = WINSTON_LEVEL_TO_LOG_SEVERITY_LEVEL_MAP[level as string] ?? 'info';
74+
if (this._levels.has(logSeverityLevel)) {
75+
captureLog(logSeverityLevel, message as string, attributes);
76+
} else {
77+
getClient()?.recordDroppedEvent('event_processor', 'log_item', 1);
78+
}
79+
} catch {
80+
// do nothing
81+
}
82+
83+
if (callback) {
84+
callback();
85+
}
86+
}
87+
}
88+
89+
return SentryWinstonTransport as typeof TransportClass;
90+
}
91+
92+
// npm
93+
// {
94+
// error: 0,
95+
// warn: 1,
96+
// info: 2,
97+
// http: 3,
98+
// verbose: 4,
99+
// debug: 5,
100+
// silly: 6
101+
// }
102+
//
103+
// syslog
104+
// {
105+
// emerg: 0,
106+
// alert: 1,
107+
// crit: 2,
108+
// error: 3,
109+
// warning: 4,
110+
// notice: 5,
111+
// info: 6,
112+
// debug: 7,
113+
// }
114+
const WINSTON_LEVEL_TO_LOG_SEVERITY_LEVEL_MAP: Record<string, LogSeverityLevel> = {
115+
// npm
116+
silly: 'trace',
117+
// npm and syslog
118+
debug: 'debug',
119+
// npm
120+
verbose: 'debug',
121+
// npm
122+
http: 'debug',
123+
// npm and syslog
124+
info: 'info',
125+
// syslog
126+
notice: 'info',
127+
// npm
128+
warn: 'warn',
129+
// syslog
130+
warning: 'warn',
131+
// npm and syslog
132+
error: 'error',
133+
// syslog
134+
emerg: 'fatal',
135+
// syslog
136+
alert: 'fatal',
137+
// syslog
138+
crit: 'fatal',
139+
};

Diff for: packages/node/src/logs/capture.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { format } from 'node:util';
2+
3+
import type { LogSeverityLevel, Log, ParameterizedString } from '@sentry/core';
4+
import { _INTERNAL_captureLog } from '@sentry/core';
5+
6+
export type CaptureLogArgs =
7+
| [message: ParameterizedString, attributes?: Log['attributes']]
8+
| [messageTemplate: string, messageParams: Array<unknown>, attributes?: Log['attributes']];
9+
10+
/**
11+
* Capture a log with the given level.
12+
*
13+
* @param level - The level of the log.
14+
* @param message - The message to log.
15+
* @param attributes - Arbitrary structured data that stores information about the log - e.g., userId: 100.
16+
*/
17+
export function captureLog(level: LogSeverityLevel, ...args: CaptureLogArgs): void {
18+
const [messageOrMessageTemplate, paramsOrAttributes, maybeAttributes] = args;
19+
if (Array.isArray(paramsOrAttributes)) {
20+
const attributes = { ...maybeAttributes };
21+
attributes['sentry.message.template'] = messageOrMessageTemplate;
22+
paramsOrAttributes.forEach((param, index) => {
23+
attributes[`sentry.message.param.${index}`] = param;
24+
});
25+
const message = format(messageOrMessageTemplate, ...paramsOrAttributes);
26+
_INTERNAL_captureLog({ level, message, attributes });
27+
} else {
28+
_INTERNAL_captureLog({ level, message: messageOrMessageTemplate, attributes: paramsOrAttributes });
29+
}
30+
}

Diff for: packages/node/src/log.ts renamed to packages/node/src/logs/exports.ts

+1-30
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,4 @@
1-
import { format } from 'node:util';
2-
3-
import type { LogSeverityLevel, Log, ParameterizedString } from '@sentry/core';
4-
import { _INTERNAL_captureLog } from '@sentry/core';
5-
6-
type CaptureLogArgs =
7-
| [message: ParameterizedString, attributes?: Log['attributes']]
8-
| [messageTemplate: string, messageParams: Array<unknown>, attributes?: Log['attributes']];
9-
10-
/**
11-
* Capture a log with the given level.
12-
*
13-
* @param level - The level of the log.
14-
* @param message - The message to log.
15-
* @param attributes - Arbitrary structured data that stores information about the log - e.g., userId: 100.
16-
*/
17-
function captureLog(level: LogSeverityLevel, ...args: CaptureLogArgs): void {
18-
const [messageOrMessageTemplate, paramsOrAttributes, maybeAttributes] = args;
19-
if (Array.isArray(paramsOrAttributes)) {
20-
const attributes = { ...maybeAttributes };
21-
attributes['sentry.message.template'] = messageOrMessageTemplate;
22-
paramsOrAttributes.forEach((param, index) => {
23-
attributes[`sentry.message.param.${index}`] = param;
24-
});
25-
const message = format(messageOrMessageTemplate, ...paramsOrAttributes);
26-
_INTERNAL_captureLog({ level, message, attributes });
27-
} else {
28-
_INTERNAL_captureLog({ level, message: messageOrMessageTemplate, attributes: paramsOrAttributes });
29-
}
30-
}
1+
import { captureLog, type CaptureLogArgs } from './capture';
312

323
/**
334
* @summary Capture a log with the `trace` level. Requires `_experiments.enableLogs` to be enabled.

0 commit comments

Comments
 (0)