Skip to content

Commit

Permalink
feat(instrumentation-winston): Allow log level to be configured for l…
Browse files Browse the repository at this point in the history
…og sending (#2016)

* Allow log level to be configured for log sending

* Support other winston levels

* Updating tests, transport callback update
  • Loading branch information
hectorhdzg committed Apr 19, 2024
1 parent df2fa5a commit b903bce
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 8 deletions.
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 4 additions & 6 deletions packages/winston-transport/src/OpenTelemetryTransportV3.ts
Expand Up @@ -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();
}
}
}
Expand Up @@ -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. |

Expand Down
Expand Up @@ -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"
Expand Down
Expand Up @@ -15,6 +15,7 @@
*/

import { context, trace, isSpanContextValid, Span } from '@opentelemetry/api';
import { SeverityNumber } from '@opentelemetry/api-logs';
import {
InstrumentationBase,
InstrumentationNodeModuleDefinition,
Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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;
}
}
Expand Up @@ -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 & {
Expand Down
Expand Up @@ -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
Expand All @@ -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.
Expand Down
Expand Up @@ -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');
}
});
});
});

0 comments on commit b903bce

Please sign in to comment.