diff --git a/package-lock.json b/package-lock.json index c12fc241b5..adb9293cdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38683,6 +38683,7 @@ "version": "0.36.0", "license": "Apache-2.0", "dependencies": { + "@opentelemetry/api-logs": "^0.50.0", "@opentelemetry/instrumentation": "^0.50.0" }, "devDependencies": { @@ -47258,6 +47259,7 @@ "version": "file:plugins/node/opentelemetry-instrumentation-winston", "requires": { "@opentelemetry/api": "^1.3.0", + "@opentelemetry/api-logs": "^0.50.0", "@opentelemetry/context-async-hooks": "^1.21.0", "@opentelemetry/instrumentation": "^0.50.0", "@opentelemetry/sdk-trace-base": "^1.21.0", diff --git a/packages/winston-transport/src/OpenTelemetryTransportV3.ts b/packages/winston-transport/src/OpenTelemetryTransportV3.ts index fe759676e9..a7e164adce 100644 --- a/packages/winston-transport/src/OpenTelemetryTransportV3.ts +++ b/packages/winston-transport/src/OpenTelemetryTransportV3.ts @@ -27,17 +27,15 @@ export class OpenTelemetryTransportV3 extends TransportStream { this._logger = logs.getLogger('@opentelemetry/winston-transport', VERSION); } - public override log(info: any, next: () => void) { + public override log(info: any, callback: () => void) { try { emitLogRecord(info, this._logger); } catch (error) { this.emit('warn', error); } - setImmediate(() => { - this.emit('logged', info); - }); - if (next) { - setImmediate(next); + this.emit('logged', info); + if (callback) { + callback(); } } } diff --git a/plugins/node/opentelemetry-instrumentation-winston/README.md b/plugins/node/opentelemetry-instrumentation-winston/README.md index d6bcbcca99..90d80acc66 100644 --- a/plugins/node/opentelemetry-instrumentation-winston/README.md +++ b/plugins/node/opentelemetry-instrumentation-winston/README.md @@ -60,6 +60,7 @@ logger.info('foobar'); | Option | Type | Description | | ----------------------- | ----------------- | ----------- | | `disableLogSending` | `boolean` | Whether to disable [log sending](#log-sending). Default `false`. | +| `logSeverity` | `SeverityNumber` | Control severity level for [log sending](#log-sending). Default `SeverityNumber.UNSPECIFIED`, it will use Winston Logger's current level when unspecified. | | `disableLogCorrelation` | `boolean` | Whether to disable [log correlation](#log-correlation). Default `false`. | | `logHook` | `LogHookFunction` | An option hook to inject additional context to a log record after trace-context has been added. This requires `disableLogCorrelation` to be false. | diff --git a/plugins/node/opentelemetry-instrumentation-winston/package.json b/plugins/node/opentelemetry-instrumentation-winston/package.json index faeb6280f3..0c25fbb7cb 100644 --- a/plugins/node/opentelemetry-instrumentation-winston/package.json +++ b/plugins/node/opentelemetry-instrumentation-winston/package.json @@ -64,6 +64,7 @@ "winston2": "npm:winston@2.4.7" }, "dependencies": { + "@opentelemetry/api-logs": "^0.50.0", "@opentelemetry/instrumentation": "^0.50.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-winston#readme" diff --git a/plugins/node/opentelemetry-instrumentation-winston/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-winston/src/instrumentation.ts index d89107144c..de72ed235b 100644 --- a/plugins/node/opentelemetry-instrumentation-winston/src/instrumentation.ts +++ b/plugins/node/opentelemetry-instrumentation-winston/src/instrumentation.ts @@ -15,6 +15,7 @@ */ import { context, trace, isSpanContextValid, Span } from '@opentelemetry/api'; +import { SeverityNumber } from '@opentelemetry/api-logs'; import { InstrumentationBase, InstrumentationNodeModuleDefinition, @@ -206,7 +207,17 @@ export class WinstonInstrumentation extends InstrumentationBase { let newTransports = Array.isArray(originalTransports) ? originalTransports : []; - const openTelemetryTransport = new OpenTelemetryTransportV3(); + let transportOptions = {}; + if (config.logSeverity) { + const winstonLevel = instrumentation._winstonLevelFromSeverity( + config.logSeverity, + args[0].levels + ); + transportOptions = { level: winstonLevel }; + } + const openTelemetryTransport = new OpenTelemetryTransportV3( + transportOptions + ); if (originalTransports && !Array.isArray(originalTransports)) { newTransports = [originalTransports]; } @@ -244,4 +255,118 @@ export class WinstonInstrumentation extends InstrumentationBase { } return record; } + + private _winstonLevelFromSeverity( + severity: SeverityNumber, + winstonLevels: { [key: string]: number } | undefined + ): string | undefined { + if (winstonLevels) { + if (isNpmLevels(winstonLevels)) { + if (severity >= SeverityNumber.ERROR) { + return 'error'; + } else if (severity >= SeverityNumber.WARN) { + return 'warn'; + } else if (severity >= SeverityNumber.INFO) { + return 'info'; + } else if (severity >= SeverityNumber.DEBUG3) { + return 'http'; + } else if (severity >= SeverityNumber.DEBUG2) { + return 'verbose'; + } else if (severity >= SeverityNumber.DEBUG) { + return 'debug'; + } else if (severity >= SeverityNumber.TRACE) { + return 'silly'; + } + } else if (isCliLevels(winstonLevels)) { + if (severity >= SeverityNumber.ERROR) { + return 'error'; + } else if (severity >= SeverityNumber.WARN) { + return 'warn'; + } else if (severity >= SeverityNumber.INFO3) { + return 'help'; + } else if (severity >= SeverityNumber.INFO2) { + return 'data'; + } else if (severity >= SeverityNumber.INFO) { + return 'info'; + } else if (severity >= SeverityNumber.DEBUG) { + return 'debug'; + } else if (severity >= SeverityNumber.TRACE4) { + return 'prompt'; + } else if (severity >= SeverityNumber.TRACE3) { + return 'verbose'; + } else if (severity >= SeverityNumber.TRACE2) { + return 'input'; + } else if (severity >= SeverityNumber.TRACE) { + return 'silly'; + } + } else if (isSyslogLevels(winstonLevels)) { + if (severity >= SeverityNumber.FATAL2) { + return 'emerg'; + } else if (severity >= SeverityNumber.FATAL) { + return 'alert'; + } else if (severity >= SeverityNumber.ERROR2) { + return 'crit'; + } else if (severity >= SeverityNumber.ERROR) { + return 'error'; + } else if (severity >= SeverityNumber.WARN) { + return 'warning'; + } else if (severity >= SeverityNumber.INFO2) { + return 'notice'; + } else if (severity >= SeverityNumber.INFO) { + return 'info'; + } else if (severity >= SeverityNumber.TRACE) { + return 'debug'; + } + } + // Unknown level + this._diag.warn( + 'failed to configure severity with existing winston levels' + ); + } + + function isCliLevels(arg: any): boolean { + return ( + arg && + arg.error !== undefined && + arg.warn && + arg.help && + arg.data && + arg.info && + arg.debug && + arg.prompt && + arg.verbose && + arg.input && + arg.silly + ); + } + + function isNpmLevels(arg: any): boolean { + return ( + arg && + arg.error !== undefined && + arg.warn && + arg.info && + arg.http && + arg.verbose && + arg.debug && + arg.silly + ); + } + + function isSyslogLevels(arg: any): boolean { + return ( + arg && + arg.emerg !== undefined && + arg.alert && + arg.crit && + arg.error && + arg.warning && + arg.notice && + arg.info && + arg.debug + ); + } + + return; + } } diff --git a/plugins/node/opentelemetry-instrumentation-winston/src/internal-types.ts b/plugins/node/opentelemetry-instrumentation-winston/src/internal-types.ts index cba7ee90d2..e71eff3c83 100644 --- a/plugins/node/opentelemetry-instrumentation-winston/src/internal-types.ts +++ b/plugins/node/opentelemetry-instrumentation-winston/src/internal-types.ts @@ -22,7 +22,6 @@ import type { export type Winston3LogMethod = Winston3Logger['write']; export type Winston3ConfigureMethod = Winston3Logger['configure']; export type { Winston3Logger }; - export type { Winston2LogMethod }; export type Winston2LoggerModule = { Logger: Winston2Logger & { diff --git a/plugins/node/opentelemetry-instrumentation-winston/src/types.ts b/plugins/node/opentelemetry-instrumentation-winston/src/types.ts index 5621bc8797..e6fabea502 100644 --- a/plugins/node/opentelemetry-instrumentation-winston/src/types.ts +++ b/plugins/node/opentelemetry-instrumentation-winston/src/types.ts @@ -15,6 +15,7 @@ */ import { Span } from '@opentelemetry/api'; +import { SeverityNumber } from '@opentelemetry/api-logs'; import { InstrumentationConfig } from '@opentelemetry/instrumentation'; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -28,6 +29,11 @@ export interface WinstonInstrumentationConfig extends InstrumentationConfig { */ disableLogSending?: boolean; + /** + * Control Log sending severity level, logs will be sent for specified severity and higher. + */ + logSeverity?: SeverityNumber; + /** * Whether to disable the injection trace-context fields, and possibly other * fields from `logHook()`, into log records for log correlation. diff --git a/plugins/node/opentelemetry-instrumentation-winston/test/winston.test.ts b/plugins/node/opentelemetry-instrumentation-winston/test/winston.test.ts index fe62a946b9..2ecb4eab37 100644 --- a/plugins/node/opentelemetry-instrumentation-winston/test/winston.test.ts +++ b/plugins/node/opentelemetry-instrumentation-winston/test/winston.test.ts @@ -383,4 +383,89 @@ describe('WinstonInstrumentation', () => { } }); }); + describe('logSeverity config', () => { + beforeEach(() => { + instrumentation.setConfig({ + disableLogSending: false, + }); + memoryLogExporter.getFinishedLogRecords().length = 0; // clear + }); + + it('npm levels', () => { + if (!isWinston2) { + instrumentation.setConfig({ + disableLogSending: false, + logSeverity: SeverityNumber.DEBUG, + }); + initLogger(LevelsType.npm); + logger.log('silly', 'silly'); + logger.log('debug', 'debug'); + logger.log('verbose', 'verbose'); + logger.log('http', 'http'); + logger.log('info', 'info'); + logger.log('warn', 'warn'); + logger.log('error', 'error'); + const logRecords = memoryLogExporter.getFinishedLogRecords(); + assert.strictEqual(logRecords.length, 6); + assert.strictEqual(logRecords[0].body, 'debug'); + assert.strictEqual(logRecords[1].body, 'verbose'); + assert.strictEqual(logRecords[2].body, 'http'); + assert.strictEqual(logRecords[3].body, 'info'); + assert.strictEqual(logRecords[4].body, 'warn'); + assert.strictEqual(logRecords[5].body, 'error'); + } + }); + + it('cli levels', () => { + if (!isWinston2) { + instrumentation.setConfig({ + disableLogSending: false, + logSeverity: SeverityNumber.INFO, + }); + initLogger(LevelsType.cli); + logger.log('silly', 'silly'); + logger.log('input', 'input'); + logger.log('verbose', 'verbose'); + logger.log('prompt', 'prompt'); + logger.log('debug', 'debug'); + logger.log('info', 'info'); + logger.log('data', 'data'); + logger.log('help', 'help'); + logger.log('warn', 'warn'); + logger.log('error', 'error'); + const logRecords = memoryLogExporter.getFinishedLogRecords(); + assert.strictEqual(logRecords.length, 5); + assert.strictEqual(logRecords[0].body, 'info'); + assert.strictEqual(logRecords[1].body, 'data'); + assert.strictEqual(logRecords[2].body, 'help'); + assert.strictEqual(logRecords[3].body, 'warn'); + assert.strictEqual(logRecords[4].body, 'error'); + } + }); + + it('syslog levels', () => { + if (!isWinston2) { + instrumentation.setConfig({ + disableLogSending: false, + logSeverity: SeverityNumber.WARN, + }); + initLogger(LevelsType.syslog); + logger.log('debug', 'debug'); + logger.log('info', 'info'); + logger.log('notice', 'notice'); + logger.log('warning', 'warning'); + logger.log('error', 'error'); + logger.log('crit', 'crit'); + logger.log('alert', 'alert'); + logger.log('emerg', 'emerg'); + const logRecords = memoryLogExporter.getFinishedLogRecords(); + assert.strictEqual(logRecords.length, 5); + assert.strictEqual(logRecords[0].body, 'warning'); + assert.strictEqual(logRecords[1].body, 'error'); + assert.strictEqual(logRecords[2].body, 'crit'); + assert.strictEqual(logRecords[3].body, 'alert'); + assert.strictEqual(logRecords[4].body, 'emerg'); + } + }); + }); });