Skip to content

Commit 8ef1584

Browse files
committed
feat(winston): Add customLevelMap for winston transport
1 parent 2f0d9dc commit 8ef1584

3 files changed

Lines changed: 148 additions & 1 deletion

File tree

dev-packages/node-integration-tests/suites/winston/subject.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,44 @@ async function run(): Promise<void> {
6464
});
6565
}
6666

67+
// If custom level mapping is requested
68+
if (process.env.CUSTOM_LEVEL_MAPPING === 'true') {
69+
const customLevels = {
70+
levels: {
71+
customCritical: 0,
72+
customWarning: 1,
73+
customNotice: 2,
74+
},
75+
};
76+
77+
const SentryWinstonTransport = Sentry.createSentryWinstonTransport(Transport, {
78+
customLevelMap: {
79+
customCritical: 'fatal',
80+
customWarning: 'warn',
81+
customNotice: 'info',
82+
},
83+
});
84+
85+
const mappedLogger = winston.createLogger({
86+
levels: customLevels.levels,
87+
// https://github.com/winstonjs/winston/issues/1491
88+
// when custom levels are set with a transport,
89+
// the level must be set on the logger
90+
level: 'customNotice',
91+
transports: [new SentryWinstonTransport()],
92+
});
93+
94+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
95+
// @ts-expect-error - custom levels are not part of the winston logger
96+
mappedLogger.customCritical('This is a critical message');
97+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
98+
// @ts-expect-error - custom levels are not part of the winston logger
99+
mappedLogger.customWarning('This is a warning message');
100+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
101+
// @ts-expect-error - custom levels are not part of the winston logger
102+
mappedLogger.customNotice('This is a notice message');
103+
}
104+
67105
await Sentry.flush();
68106
}
69107

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,95 @@ describe('winston integration', () => {
183183

184184
await runner.completed();
185185
});
186+
187+
test('should map custom winston levels to Sentry severity levels', async () => {
188+
const runner = createRunner(__dirname, 'subject.ts')
189+
.withEnv({ CUSTOM_LEVEL_MAPPING: 'true' })
190+
.expect({
191+
log: {
192+
items: [
193+
// First, the default logger captures info and error
194+
{
195+
timestamp: expect.any(Number),
196+
level: 'info',
197+
body: 'Test info message',
198+
severity_number: expect.any(Number),
199+
trace_id: expect.any(String),
200+
attributes: {
201+
'sentry.origin': { value: 'auto.log.winston', type: 'string' },
202+
'sentry.release': { value: '1.0.0', type: 'string' },
203+
'sentry.environment': { value: 'test', type: 'string' },
204+
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
205+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
206+
'server.address': { value: expect.any(String), type: 'string' },
207+
},
208+
},
209+
{
210+
timestamp: expect.any(Number),
211+
level: 'error',
212+
body: 'Test error message',
213+
severity_number: expect.any(Number),
214+
trace_id: expect.any(String),
215+
attributes: {
216+
'sentry.origin': { value: 'auto.log.winston', type: 'string' },
217+
'sentry.release': { value: '1.0.0', type: 'string' },
218+
'sentry.environment': { value: 'test', type: 'string' },
219+
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
220+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
221+
'server.address': { value: expect.any(String), type: 'string' },
222+
},
223+
},
224+
// Then the mapped logger uses custom level mappings
225+
{
226+
timestamp: expect.any(Number),
227+
level: 'fatal', // 'critical' maps to 'fatal'
228+
body: 'This is a critical message',
229+
severity_number: expect.any(Number),
230+
trace_id: expect.any(String),
231+
attributes: {
232+
'sentry.origin': { value: 'auto.log.winston', type: 'string' },
233+
'sentry.release': { value: '1.0.0', type: 'string' },
234+
'sentry.environment': { value: 'test', type: 'string' },
235+
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
236+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
237+
'server.address': { value: expect.any(String), type: 'string' },
238+
},
239+
},
240+
{
241+
timestamp: expect.any(Number),
242+
level: 'warn', // 'warning' maps to 'warn'
243+
body: 'This is a warning message',
244+
severity_number: expect.any(Number),
245+
trace_id: expect.any(String),
246+
attributes: {
247+
'sentry.origin': { value: 'auto.log.winston', type: 'string' },
248+
'sentry.release': { value: '1.0.0', type: 'string' },
249+
'sentry.environment': { value: 'test', type: 'string' },
250+
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
251+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
252+
'server.address': { value: expect.any(String), type: 'string' },
253+
},
254+
},
255+
{
256+
timestamp: expect.any(Number),
257+
level: 'info', // 'notice' maps to 'info'
258+
body: 'This is a notice message',
259+
severity_number: expect.any(Number),
260+
trace_id: expect.any(String),
261+
attributes: {
262+
'sentry.origin': { value: 'auto.log.winston', type: 'string' },
263+
'sentry.release': { value: '1.0.0', type: 'string' },
264+
'sentry.environment': { value: 'test', type: 'string' },
265+
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
266+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
267+
'server.address': { value: expect.any(String), type: 'string' },
268+
},
269+
},
270+
],
271+
},
272+
})
273+
.start();
274+
275+
await runner.completed();
276+
});
186277
});

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ interface WinstonTransportOptions {
2525
* ```
2626
*/
2727
levels?: Array<LogSeverityLevel>;
28+
29+
/**
30+
* Use this option to map custom levels to Sentry log severity levels.
31+
*
32+
* @example
33+
* ```ts
34+
* const SentryWinstonTransport = Sentry.createSentryWinstonTransport(Transport, {
35+
* customLevelMap: {
36+
* myCustomLevel: 'info',
37+
* customError: 'error',
38+
* },
39+
* });
40+
* ```
41+
*/
42+
customLevelMap?: Record<string, LogSeverityLevel>;
2843
}
2944

3045
/**
@@ -85,7 +100,10 @@ export function createSentryWinstonTransport<TransportStreamInstance extends obj
85100
attributes[MESSAGE_SYMBOL] = undefined;
86101
attributes[SPLAT_SYMBOL] = undefined;
87102

88-
const logSeverityLevel = WINSTON_LEVEL_TO_LOG_SEVERITY_LEVEL_MAP[levelFromSymbol as string] ?? 'info';
103+
const customLevel = sentryWinstonOptions?.customLevelMap?.[levelFromSymbol as string];
104+
const logSeverityLevel =
105+
customLevel ?? WINSTON_LEVEL_TO_LOG_SEVERITY_LEVEL_MAP[levelFromSymbol as string] ?? 'info';
106+
89107
if (this._levels.has(logSeverityLevel)) {
90108
captureLog(logSeverityLevel, message as string, {
91109
...attributes,

0 commit comments

Comments
 (0)