From 4dc812efed1c26eeab19d9eda7280631a761860e Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:28:43 -0700 Subject: [PATCH] feat(instrumentation-bunyan): Allow log level to be configured for log sending (#1919) * Allow log level to be configured for auto instrumentation and BunyanStream * Addressing comments * Update * Update plugins/node/opentelemetry-instrumentation-bunyan/test/bunyan.test.ts Co-authored-by: Trent Mick * Add test coverage --------- Co-authored-by: Trent Mick --- .../README.md | 1 + .../src/instrumentation.ts | 25 +++++- .../src/types.ts | 6 ++ .../test/bunyan.test.ts | 86 +++++++++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) diff --git a/plugins/node/opentelemetry-instrumentation-bunyan/README.md b/plugins/node/opentelemetry-instrumentation-bunyan/README.md index 5706fe8fe2..5d4c2f5f28 100644 --- a/plugins/node/opentelemetry-instrumentation-bunyan/README.md +++ b/plugins/node/opentelemetry-instrumentation-bunyan/README.md @@ -87,6 +87,7 @@ Log injection can be disabled with the `disableLogCorrelation: true` option. | 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 Bunnyan 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-bunyan/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-bunyan/src/instrumentation.ts index c78e27b50b..d3e76a74ce 100644 --- a/plugins/node/opentelemetry-instrumentation-bunyan/src/instrumentation.ts +++ b/plugins/node/opentelemetry-instrumentation-bunyan/src/instrumentation.ts @@ -25,6 +25,7 @@ import { BunyanInstrumentationConfig } from './types'; import { VERSION } from './version'; import { OpenTelemetryBunyanStream } from './OpenTelemetryBunyanStream'; import type * as BunyanLogger from 'bunyan'; +import { SeverityNumber } from '@opentelemetry/api-logs'; const DEFAULT_CONFIG: BunyanInstrumentationConfig = { disableLogSending: false, @@ -157,10 +158,15 @@ export class BunyanInstrumentation extends InstrumentationBase< return; } this._diag.debug('Adding OpenTelemetryBunyanStream to logger'); + let streamLevel = logger.level(); + if (config.logSeverity) { + const bunyanLevel = bunyanLevelFromSeverity(config.logSeverity); + streamLevel = bunyanLevel || streamLevel; + } logger.addStream({ type: 'raw', stream: new OpenTelemetryBunyanStream(), - level: logger.level(), + level: streamLevel, }); } @@ -182,3 +188,20 @@ export class BunyanInstrumentation extends InstrumentationBase< ); } } + +function bunyanLevelFromSeverity(severity: SeverityNumber): string | undefined { + if (severity >= SeverityNumber.FATAL) { + return 'fatal'; + } else if (severity >= SeverityNumber.ERROR) { + return 'error'; + } else if (severity >= SeverityNumber.WARN) { + return 'warn'; + } else if (severity >= SeverityNumber.INFO) { + return 'info'; + } else if (severity >= SeverityNumber.DEBUG) { + return 'debug'; + } else if (severity >= SeverityNumber.TRACE) { + return 'trace'; + } + return; +} diff --git a/plugins/node/opentelemetry-instrumentation-bunyan/src/types.ts b/plugins/node/opentelemetry-instrumentation-bunyan/src/types.ts index 7a2f5f686b..ee915064f5 100644 --- a/plugins/node/opentelemetry-instrumentation-bunyan/src/types.ts +++ b/plugins/node/opentelemetry-instrumentation-bunyan/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 BunyanInstrumentationConfig 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-bunyan/test/bunyan.test.ts b/plugins/node/opentelemetry-instrumentation-bunyan/test/bunyan.test.ts index 5ca3744d3d..13f2cf3356 100644 --- a/plugins/node/opentelemetry-instrumentation-bunyan/test/bunyan.test.ts +++ b/plugins/node/opentelemetry-instrumentation-bunyan/test/bunyan.test.ts @@ -319,6 +319,92 @@ describe('BunyanInstrumentation', () => { assert.strictEqual(rec.body, 'hi'); assert.strictEqual(rec.attributes.aProperty, 'bar'); }); + + it('log record error level', () => { + instrumentation.setConfig({ logSeverity: SeverityNumber.FATAL }); + // Setting `logSeverity` only has an impact on Loggers created + // *after* it is set. So we cannot test with the `log` created in + // `beforeEach()` above. + log = Logger.createLogger({ name: 'test-logger-name', stream }); + log.error('error log'); + log.fatal('fatal log'); + const logRecords = memExporter.getFinishedLogRecords(); + // Only one log record match configured severity + assert.strictEqual(logRecords.length, 1); + assert.strictEqual(logRecords[0].body, 'fatal log'); + }); + + it('log record error level', () => { + instrumentation.setConfig({ logSeverity: SeverityNumber.ERROR }); + // Setting `logSeverity` only has an impact on Loggers created + // *after* it is set. So we cannot test with the `log` created in + // `beforeEach()` above. + log = Logger.createLogger({ name: 'test-logger-name', stream }); + log.warn('warn log'); + log.error('error log'); + const logRecords = memExporter.getFinishedLogRecords(); + // Only one log record match configured severity + assert.strictEqual(logRecords.length, 1); + assert.strictEqual(logRecords[0].body, 'error log'); + }); + + it('log record warn level', () => { + instrumentation.setConfig({ logSeverity: SeverityNumber.WARN }); + // Setting `logSeverity` only has an impact on Loggers created + // *after* it is set. So we cannot test with the `log` created in + // `beforeEach()` above. + log = Logger.createLogger({ name: 'test-logger-name', stream }); + log.info('info log'); + log.warn('warn log'); + const logRecords = memExporter.getFinishedLogRecords(); + // Only one log record match configured severity + assert.strictEqual(logRecords.length, 1); + assert.strictEqual(logRecords[0].body, 'warn log'); + }); + + it('log record info level', () => { + instrumentation.setConfig({ logSeverity: SeverityNumber.INFO }); + // Setting `logSeverity` only has an impact on Loggers created + // *after* it is set. So we cannot test with the `log` created in + // `beforeEach()` above. + log = Logger.createLogger({ name: 'test-logger-name', stream }); + log.debug('debug log'); + log.info('info log'); + const logRecords = memExporter.getFinishedLogRecords(); + // Only one log record match configured severity + assert.strictEqual(logRecords.length, 1); + assert.strictEqual(logRecords[0].body, 'info log'); + }); + + it('log record debug level', () => { + instrumentation.setConfig({ logSeverity: SeverityNumber.DEBUG }); + log = Logger.createLogger({ name: 'test-logger-name', stream }); + log.info('info log'); + log.debug('debug log'); + // Just the log.info() writes to `stream`. + sinon.assert.calledOnce(writeSpy); + // Both log.info() and log.debug() should be written to the OTel Logs SDK. + const logRecords = memExporter.getFinishedLogRecords(); + assert.strictEqual(logRecords.length, 2); + assert.strictEqual(logRecords[0].body, 'info log'); + assert.strictEqual(logRecords[1].body, 'debug log'); + }); + + it('log record trace level', () => { + instrumentation.setConfig({ logSeverity: SeverityNumber.TRACE }); + log = Logger.createLogger({ name: 'test-logger-name', stream }); + log.info('info log'); + log.debug('debug log'); + log.debug('trace log'); + // Just the log.info() writes to `stream`. + sinon.assert.calledOnce(writeSpy); + // Both log.info() and log.debug() should be written to the OTel Logs SDK. + const logRecords = memExporter.getFinishedLogRecords(); + assert.strictEqual(logRecords.length, 3); + assert.strictEqual(logRecords[0].body, 'info log'); + assert.strictEqual(logRecords[1].body, 'debug log'); + assert.strictEqual(logRecords[2].body, 'trace log'); + }); }); describe('disabled instrumentation', () => {