From 0f9c6bb434193de35e5396cd1e56585dffddbaac Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Wed, 22 May 2024 23:13:08 +0300 Subject: [PATCH 1/9] Configure logger provider from env --- .../opentelemetry-sdk-node/package.json | 3 + .../src/TracerProviderWithEnvExporter.ts | 7 +- .../opentelemetry-sdk-node/src/sdk.ts | 82 +++++++++++-- .../opentelemetry-sdk-node/src/types.ts | 2 + .../opentelemetry-sdk-node/src/utils.ts | 4 + .../opentelemetry-sdk-node/test/sdk.test.ts | 109 +++++++++++++++++- .../opentelemetry-sdk-node/tsconfig.json | 3 + 7 files changed, 193 insertions(+), 17 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/package.json b/experimental/packages/opentelemetry-sdk-node/package.json index 52e916c068..3597ded776 100644 --- a/experimental/packages/opentelemetry-sdk-node/package.json +++ b/experimental/packages/opentelemetry-sdk-node/package.json @@ -46,6 +46,9 @@ "dependencies": { "@opentelemetry/api-logs": "0.50.0", "@opentelemetry/core": "1.23.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.50.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.50.0", + "@opentelemetry/exporter-logs-otlp-proto": "^0.50.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.50.0", "@opentelemetry/exporter-trace-otlp-http": "0.50.0", "@opentelemetry/exporter-trace-otlp-proto": "0.50.0", diff --git a/experimental/packages/opentelemetry-sdk-node/src/TracerProviderWithEnvExporter.ts b/experimental/packages/opentelemetry-sdk-node/src/TracerProviderWithEnvExporter.ts index a4b9ee1286..4db4969939 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/TracerProviderWithEnvExporter.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/TracerProviderWithEnvExporter.ts @@ -32,6 +32,7 @@ import { OTLPTraceExporter as OTLPProtoTraceExporter } from '@opentelemetry/expo import { OTLPTraceExporter as OTLPHttpTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { OTLPTraceExporter as OTLPGrpcTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; +import { filterBlanksAndNulls } from './utils'; export class TracerProviderWithEnvExporters extends NodeTracerProvider { private _configuredExporters: SpanExporter[] = []; @@ -94,7 +95,7 @@ export class TracerProviderWithEnvExporters extends NodeTracerProvider { public constructor(config: NodeTracerConfig = {}) { super(config); - let traceExportersList = this.filterBlanksAndNulls( + let traceExportersList = filterBlanksAndNulls( Array.from(new Set(getEnv().OTEL_TRACES_EXPORTER.split(','))) ); @@ -175,8 +176,4 @@ export class TracerProviderWithEnvExporters extends NodeTracerProvider { } }); } - - private filterBlanksAndNulls(list: string[]): string[] { - return list.map(item => item.trim()).filter(s => s !== 'null' && s !== ''); - } } diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 90bf2d96c3..252324c046 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -37,7 +37,16 @@ import { Resource, ResourceDetectionConfig, } from '@opentelemetry/resources'; -import { LogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; +import { + LogRecordProcessor, + LoggerProvider, + BatchLogRecordProcessor, + ConsoleLogRecordExporter, + LogRecordExporter, +} from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter as OTLPHttpLogExporter} from '@opentelemetry/exporter-logs-otlp-http'; +import { OTLPLogExporter as OTLPGrpcLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; +import { OTLPLogExporter as OTLPProtoLogExporter } from '@opentelemetry/exporter-logs-otlp-proto'; import { MeterProvider, MetricReader, View } from '@opentelemetry/sdk-metrics'; import { BatchSpanProcessor, @@ -50,10 +59,11 @@ import { import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { NodeSDKConfiguration } from './types'; import { TracerProviderWithEnvExporters } from './TracerProviderWithEnvExporter'; -import { getEnv, getEnvWithoutDefaults } from '@opentelemetry/core'; +import { ENVIRONMENT, getEnv, getEnvWithoutDefaults } from '@opentelemetry/core'; import { getResourceDetectorsFromEnv, parseInstrumentationOptions, + filterBlanksAndNulls, } from './utils'; /** This class represents everything needed to register a fully configured OpenTelemetry Node.js SDK */ @@ -73,7 +83,7 @@ export type LoggerProviderConfig = { /** * Reference to the LoggerRecordProcessor instance by the NodeSDK */ - logRecordProcessor: LogRecordProcessor; + logRecordProcessors: LogRecordProcessor[]; }; export class NodeSDK { @@ -177,10 +187,12 @@ export class NodeSDK { }; } - if (configuration.logRecordProcessor) { + if (configuration.logRecordProcessor || configuration.logRecordProcessors) { this._loggerProviderConfig = { - logRecordProcessor: configuration.logRecordProcessor, + logRecordProcessors: configuration.logRecordProcessors ? configuration.logRecordProcessors : [configuration.logRecordProcessor!] }; + } else { + this.configureLoggerProviderFromEnv(envWithoutDefaults); } if (configuration.metricReader || configuration.views) { @@ -262,9 +274,10 @@ export class NodeSDK { const loggerProvider = new LoggerProvider({ resource: this._resource, }); - loggerProvider.addLogRecordProcessor( - this._loggerProviderConfig.logRecordProcessor - ); + + for (const logRecordProcessor of this._loggerProviderConfig.logRecordProcessors) { + loggerProvider.addLogRecordProcessor(logRecordProcessor); + } this._loggerProvider = loggerProvider; @@ -315,4 +328,57 @@ export class NodeSDK { .then(() => {}) ); } + + private configureLoggerProviderFromEnv(envWithoutDefaults: ENVIRONMENT): void { + const logsExporter = envWithoutDefaults.OTEL_LOGS_EXPORTER; + if (logsExporter) { + const exporters: LogRecordExporter[] = []; + const enabledLogExporters = filterBlanksAndNulls(logsExporter.split(',')); + + if (enabledLogExporters.includes('none')) { + diag.warn( + `Logs will not be exported.` + ); + return; + } + + enabledLogExporters.forEach((exporter) => { + if (exporter === 'otlp') { + const protocol = envWithoutDefaults.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? envWithoutDefaults.OTEL_EXPORTER_OTLP_PROTOCOL; + switch (protocol?.trim()) { //FIXME trim? + case 'grpc': + exporters.push(new OTLPGrpcLogExporter()); + break; + case 'http/json': + exporters.push(new OTLPHttpLogExporter()); + break; + case 'http/protobuf': + exporters.push(new OTLPProtoLogExporter()); + break; + default: + diag.warn( + `Unsupported or undefined OTLP logs protocol. Using http/protobuf.` + ); + exporters.push(new OTLPProtoLogExporter()); + } + } else if (exporter === 'console') { + exporters.push(new ConsoleLogRecordExporter()); + } else { + diag.warn( + `Unsupported log exporter: ${exporter}. Logs will not be exported.` + ); + } + }); + + if (exporters.length > 0) { + this._loggerProviderConfig = { + logRecordProcessors: exporters.map((exporter) => new BatchLogRecordProcessor(exporter)) + }; + } + } else { + diag.warn( + `No log exporter specified. Logs will not be exported.` + ); + } + } } diff --git a/experimental/packages/opentelemetry-sdk-node/src/types.ts b/experimental/packages/opentelemetry-sdk-node/src/types.ts index 2ed7cae48d..37d915b3e5 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/types.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/types.ts @@ -32,7 +32,9 @@ export interface NodeSDKConfiguration { autoDetectResources: boolean; contextManager: ContextManager; textMapPropagator: TextMapPropagator; + /** @deprecated use logRecordProcessors instead*/ logRecordProcessor: LogRecordProcessor; + logRecordProcessors?: LogRecordProcessor[]; metricReader: MetricReader; views: View[]; instrumentations: InstrumentationOption[]; diff --git a/experimental/packages/opentelemetry-sdk-node/src/utils.ts b/experimental/packages/opentelemetry-sdk-node/src/utils.ts index 6591224506..0d17f501da 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/utils.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/utils.ts @@ -88,3 +88,7 @@ export function getResourceDetectorsFromEnv(): Array { return resourceDetector || []; }); } + +export function filterBlanksAndNulls(list: string[]): string[] { + return list.map(item => item.trim()).filter(s => s !== 'null' && s !== ''); +} diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 22e1794ccc..828354b94d 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -70,7 +70,11 @@ import { SimpleLogRecordProcessor, InMemoryLogRecordExporter, LoggerProvider, + ConsoleLogRecordExporter, } from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter as OTLPProtoLogExporter } from '@opentelemetry/exporter-logs-otlp-proto'; +import { OTLPLogExporter as OTLPHttpLogExporter} from '@opentelemetry/exporter-logs-otlp-http'; +import { OTLPLogExporter as OTLPGrpcLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; import { SEMRESATTRS_HOST_NAME, SEMRESATTRS_PROCESS_PID, @@ -91,6 +95,7 @@ describe('Node SDK', () => { trace.disable(); propagation.disable(); metrics.disable(); + logs.disable(); ctxManager = context['_getContextManager'](); propagator = propagation['_getGlobalPropagator'](); @@ -325,6 +330,11 @@ describe('Node SDK', () => { await sdk.shutdown(); delete env.OTEL_TRACES_EXPORTER; }); + + it('should register a logger provider if multiple log record processors are provided', async () => { + //FIXME + assert.ok(false); + }); }); async function waitForNumberOfMetrics( @@ -834,6 +844,97 @@ describe('Node SDK', () => { await sdk.shutdown(); }); }); + + describe('configuring logger provider from env', () => { + let stubLogger = Sinon.stub(diag, 'warn'); + + afterEach(() => { + stubLogger.reset(); + }); + + it('should log a warning if OTEL_LOGS_EXPORTER not set', async () => { + const sdk = new NodeSDK(); + sdk.start(); + + assert.strictEqual( + stubLogger.args[0][0], + 'No log exporter specified. Logs will not be exported.' + ); + + await sdk.shutdown(); + }); + + it('should not register the provider if OTEL_LOGS_EXPORTER contains none', async () => { + assert(true); + }); + + it('should set up all allowed exporters', async () => { + env.OTEL_LOGS_EXPORTER = 'console,otlp'; + const sdk = new NodeSDK(); + + sdk.start(); + + const loggerProvider = logs.getLoggerProvider(); + const sharedState = (loggerProvider as any)['_sharedState']; + assert(sharedState.registeredLogRecordProcessors.length === 2); + assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof ConsoleLogRecordExporter); + // defaults to http/protobuf + assert(sharedState.registeredLogRecordProcessors[1]._exporter instanceof OTLPProtoLogExporter); + // FIXME check that BatchLogProcessor is used + delete env.OTEL_LOGS_EXPORTER; + await sdk.shutdown(); + }); + + it('should use OTEL_EXPORTER_OTLP_LOGS_PROTOCOL for otlp protocol', async () => { + env.OTEL_LOGS_EXPORTER = 'otlp'; + env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = 'grpc'; + const sdk = new NodeSDK(); + + sdk.start(); + + const loggerProvider = logs.getLoggerProvider(); + const sharedState = (loggerProvider as any)['_sharedState']; + assert(sharedState.registeredLogRecordProcessors.length === 1); + assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPGrpcLogExporter); + delete env.OTEL_LOGS_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; + await sdk.shutdown(); + }); + + it('should fall back to OTEL_EXPORTER_OTLP_PROTOCOL', async () => { + env.OTEL_LOGS_EXPORTER = 'otlp'; + env.OTEL_EXPORTER_OTLP_PROTOCOL = 'grpc'; + const sdk = new NodeSDK(); + + sdk.start(); + + const loggerProvider = logs.getLoggerProvider(); + const sharedState = (loggerProvider as any)['_sharedState']; + assert(sharedState.registeredLogRecordProcessors.length === 1); + assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPGrpcLogExporter); + + delete env.OTEL_LOGS_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_PROTOCOL; + await sdk.shutdown(); + }); + + it('should fall back to http/protobuf if invalid protocol is set', async () => { + env.OTEL_LOGS_EXPORTER = 'otlp'; + env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = 'grpc2'; + const sdk = new NodeSDK(); + + sdk.start(); + + const loggerProvider = logs.getLoggerProvider(); + const sharedState = (loggerProvider as any)['_sharedState']; + assert(sharedState.registeredLogRecordProcessors.length === 1); + assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPProtoLogExporter); + + delete env.OTEL_LOGS_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; + await sdk.shutdown(); + }); + }); }); describe('setup exporter from env', () => { @@ -967,7 +1068,7 @@ describe('setup exporter from env', () => { sdk.start(); assert.strictEqual( - stubLoggerError.args[0][0], + stubLoggerError.args[1][0], 'OTEL_TRACES_EXPORTER contains "none". SDK will not be initialized.' ); delete env.OTEL_TRACES_EXPORTER; @@ -1018,7 +1119,7 @@ describe('setup exporter from env', () => { sdk.start(); assert.strictEqual( - stubLoggerError.args[0][0], + stubLoggerError.args[1][0], 'OTEL_TRACES_EXPORTER contains "none" along with other exporters. Using default otlp exporter.' ); delete env.OTEL_TRACES_EXPORTER; @@ -1030,12 +1131,12 @@ describe('setup exporter from env', () => { sdk.start(); assert.strictEqual( - stubLoggerError.args[0][0], + stubLoggerError.args[1][0], 'Unrecognized OTEL_TRACES_EXPORTER value: invalid.' ); assert.strictEqual( - stubLoggerError.args[1][0], + stubLoggerError.args[2][0], 'Unable to set up trace exporter(s) due to invalid exporter and/or protocol values.' ); diff --git a/experimental/packages/opentelemetry-sdk-node/tsconfig.json b/experimental/packages/opentelemetry-sdk-node/tsconfig.json index 94c80b8846..65eee6bca0 100644 --- a/experimental/packages/opentelemetry-sdk-node/tsconfig.json +++ b/experimental/packages/opentelemetry-sdk-node/tsconfig.json @@ -42,6 +42,9 @@ { "path": "../api-logs" }, + { + "path": "../exporter-logs-otlp-http" + }, { "path": "../exporter-trace-otlp-grpc" }, From b68b9d8fc95604921e8424815dc5ffcdcfcae52a Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Tue, 28 May 2024 13:35:15 +0300 Subject: [PATCH 2/9] Tests --- .../opentelemetry-sdk-node/src/sdk.ts | 48 +++++--- .../opentelemetry-sdk-node/test/sdk.test.ts | 114 ++++++++++++++++-- 2 files changed, 132 insertions(+), 30 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 252324c046..e740ceb524 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -44,7 +44,7 @@ import { ConsoleLogRecordExporter, LogRecordExporter, } from '@opentelemetry/sdk-logs'; -import { OTLPLogExporter as OTLPHttpLogExporter} from '@opentelemetry/exporter-logs-otlp-http'; +import { OTLPLogExporter as OTLPHttpLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; import { OTLPLogExporter as OTLPGrpcLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; import { OTLPLogExporter as OTLPProtoLogExporter } from '@opentelemetry/exporter-logs-otlp-proto'; import { MeterProvider, MetricReader, View } from '@opentelemetry/sdk-metrics'; @@ -59,7 +59,11 @@ import { import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { NodeSDKConfiguration } from './types'; import { TracerProviderWithEnvExporters } from './TracerProviderWithEnvExporter'; -import { ENVIRONMENT, getEnv, getEnvWithoutDefaults } from '@opentelemetry/core'; +import { + ENVIRONMENT, + getEnv, + getEnvWithoutDefaults, +} from '@opentelemetry/core'; import { getResourceDetectorsFromEnv, parseInstrumentationOptions, @@ -189,7 +193,9 @@ export class NodeSDK { if (configuration.logRecordProcessor || configuration.logRecordProcessors) { this._loggerProviderConfig = { - logRecordProcessors: configuration.logRecordProcessors ? configuration.logRecordProcessors : [configuration.logRecordProcessor!] + logRecordProcessors: configuration.logRecordProcessors + ? configuration.logRecordProcessors + : [configuration.logRecordProcessor!], }; } else { this.configureLoggerProviderFromEnv(envWithoutDefaults); @@ -275,7 +281,8 @@ export class NodeSDK { resource: this._resource, }); - for (const logRecordProcessor of this._loggerProviderConfig.logRecordProcessors) { + for (const logRecordProcessor of this._loggerProviderConfig + .logRecordProcessors) { loggerProvider.addLogRecordProcessor(logRecordProcessor); } @@ -329,23 +336,30 @@ export class NodeSDK { ); } - private configureLoggerProviderFromEnv(envWithoutDefaults: ENVIRONMENT): void { - const logsExporter = envWithoutDefaults.OTEL_LOGS_EXPORTER; - if (logsExporter) { + private configureLoggerProviderFromEnv( + envWithoutDefaults: ENVIRONMENT + ): void { + const logExportersList = envWithoutDefaults.OTEL_LOGS_EXPORTER; + if (logExportersList) { const exporters: LogRecordExporter[] = []; - const enabledLogExporters = filterBlanksAndNulls(logsExporter.split(',')); + const enabledExporters = filterBlanksAndNulls( + logExportersList.split(',') + ); - if (enabledLogExporters.includes('none')) { + if (enabledExporters.includes('none')) { diag.warn( - `Logs will not be exported.` + `OTEL_LOGS_EXPORTER contains "none". Logger provider will not be initialized.` ); return; } - enabledLogExporters.forEach((exporter) => { + enabledExporters.forEach(exporter => { if (exporter === 'otlp') { - const protocol = envWithoutDefaults.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? envWithoutDefaults.OTEL_EXPORTER_OTLP_PROTOCOL; - switch (protocol?.trim()) { //FIXME trim? + const protocol = ( + envWithoutDefaults.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? + envWithoutDefaults.OTEL_EXPORTER_OTLP_PROTOCOL + )?.trim(); + switch (protocol) { case 'grpc': exporters.push(new OTLPGrpcLogExporter()); break; @@ -372,13 +386,13 @@ export class NodeSDK { if (exporters.length > 0) { this._loggerProviderConfig = { - logRecordProcessors: exporters.map((exporter) => new BatchLogRecordProcessor(exporter)) + logRecordProcessors: exporters.map( + exporter => new BatchLogRecordProcessor(exporter) + ), }; } } else { - diag.warn( - `No log exporter specified. Logs will not be exported.` - ); + diag.warn(`No log exporters specified. Logs will not be exported.`); } } } diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 828354b94d..95b7cdcdcb 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -65,15 +65,16 @@ import { serviceInstanceIdDetectorSync, } from '@opentelemetry/resources'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { logs } from '@opentelemetry/api-logs'; +import { logs, NoopLoggerProvider } from '@opentelemetry/api-logs'; import { SimpleLogRecordProcessor, InMemoryLogRecordExporter, LoggerProvider, ConsoleLogRecordExporter, + BatchLogRecordProcessor, } from '@opentelemetry/sdk-logs'; import { OTLPLogExporter as OTLPProtoLogExporter } from '@opentelemetry/exporter-logs-otlp-proto'; -import { OTLPLogExporter as OTLPHttpLogExporter} from '@opentelemetry/exporter-logs-otlp-http'; +import { OTLPLogExporter as OTLPHttpLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; import { OTLPLogExporter as OTLPGrpcLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; import { SEMRESATTRS_HOST_NAME, @@ -332,8 +333,41 @@ describe('Node SDK', () => { }); it('should register a logger provider if multiple log record processors are provided', async () => { - //FIXME - assert.ok(false); + const logRecordExporter = new InMemoryLogRecordExporter(); + const simpleLogRecordProcessor = new SimpleLogRecordProcessor( + logRecordExporter + ); + const batchLogRecordProcessor = new BatchLogRecordProcessor( + logRecordExporter + ); + const sdk = new NodeSDK({ + logRecordProcessors: [ + simpleLogRecordProcessor, + batchLogRecordProcessor, + ], + }); + + sdk.start(); + + const loggerProvider = logs.getLoggerProvider(); + const sharedState = (loggerProvider as any)['_sharedState']; + assert(sharedState.registeredLogRecordProcessors.length === 2); + assert( + sharedState.registeredLogRecordProcessors[0]._exporter instanceof + InMemoryLogRecordExporter + ); + assert( + sharedState.registeredLogRecordProcessors[0] instanceof + SimpleLogRecordProcessor + ); + assert( + sharedState.registeredLogRecordProcessors[1]._exporter instanceof + InMemoryLogRecordExporter + ); + assert( + sharedState.registeredLogRecordProcessors[1] instanceof + BatchLogRecordProcessor + ); }); }); @@ -846,7 +880,11 @@ describe('Node SDK', () => { }); describe('configuring logger provider from env', () => { - let stubLogger = Sinon.stub(diag, 'warn'); + let stubLogger: Sinon.SinonStub; + + beforeEach(() => { + stubLogger = Sinon.stub(diag, 'warn'); + }); afterEach(() => { stubLogger.reset(); @@ -858,14 +896,23 @@ describe('Node SDK', () => { assert.strictEqual( stubLogger.args[0][0], - 'No log exporter specified. Logs will not be exported.' + 'No log exporters specified. Logs will not be exported.' ); await sdk.shutdown(); }); it('should not register the provider if OTEL_LOGS_EXPORTER contains none', async () => { - assert(true); + env.OTEL_LOGS_EXPORTER = 'console,none'; + const sdk = new NodeSDK(); + sdk.start(); + assert.strictEqual( + stubLogger.args[0][0], + 'OTEL_LOGS_EXPORTER contains "none". Logger provider will not be initialized.' + ); + + assert(logs.getLoggerProvider() instanceof NoopLoggerProvider); + await sdk.shutdown(); }); it('should set up all allowed exporters', async () => { @@ -877,10 +924,23 @@ describe('Node SDK', () => { const loggerProvider = logs.getLoggerProvider(); const sharedState = (loggerProvider as any)['_sharedState']; assert(sharedState.registeredLogRecordProcessors.length === 2); - assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof ConsoleLogRecordExporter); + assert( + sharedState.registeredLogRecordProcessors[0]._exporter instanceof + ConsoleLogRecordExporter + ); + assert( + sharedState.registeredLogRecordProcessors[0] instanceof + BatchLogRecordProcessor + ); // defaults to http/protobuf - assert(sharedState.registeredLogRecordProcessors[1]._exporter instanceof OTLPProtoLogExporter); - // FIXME check that BatchLogProcessor is used + assert( + sharedState.registeredLogRecordProcessors[1]._exporter instanceof + OTLPProtoLogExporter + ); + assert( + sharedState.registeredLogRecordProcessors[1] instanceof + BatchLogRecordProcessor + ); delete env.OTEL_LOGS_EXPORTER; await sdk.shutdown(); }); @@ -895,7 +955,29 @@ describe('Node SDK', () => { const loggerProvider = logs.getLoggerProvider(); const sharedState = (loggerProvider as any)['_sharedState']; assert(sharedState.registeredLogRecordProcessors.length === 1); - assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPGrpcLogExporter); + assert( + sharedState.registeredLogRecordProcessors[0]._exporter instanceof + OTLPGrpcLogExporter + ); + delete env.OTEL_LOGS_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; + await sdk.shutdown(); + }); + + it('should use OTLPHttpLogExporter when http/json is set', async () => { + env.OTEL_LOGS_EXPORTER = 'otlp'; + env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = 'http/json'; + const sdk = new NodeSDK(); + + sdk.start(); + + const loggerProvider = logs.getLoggerProvider(); + const sharedState = (loggerProvider as any)['_sharedState']; + assert(sharedState.registeredLogRecordProcessors.length === 1); + assert( + sharedState.registeredLogRecordProcessors[0]._exporter instanceof + OTLPHttpLogExporter + ); delete env.OTEL_LOGS_EXPORTER; delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; await sdk.shutdown(); @@ -911,7 +993,10 @@ describe('Node SDK', () => { const loggerProvider = logs.getLoggerProvider(); const sharedState = (loggerProvider as any)['_sharedState']; assert(sharedState.registeredLogRecordProcessors.length === 1); - assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPGrpcLogExporter); + assert( + sharedState.registeredLogRecordProcessors[0]._exporter instanceof + OTLPGrpcLogExporter + ); delete env.OTEL_LOGS_EXPORTER; delete env.OTEL_EXPORTER_OTLP_PROTOCOL; @@ -928,7 +1013,10 @@ describe('Node SDK', () => { const loggerProvider = logs.getLoggerProvider(); const sharedState = (loggerProvider as any)['_sharedState']; assert(sharedState.registeredLogRecordProcessors.length === 1); - assert(sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPProtoLogExporter); + assert( + sharedState.registeredLogRecordProcessors[0]._exporter instanceof + OTLPProtoLogExporter + ); delete env.OTEL_LOGS_EXPORTER; delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; From b07a622edb1a58c04aea12941a2468b24b45a401 Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Tue, 28 May 2024 16:58:40 +0300 Subject: [PATCH 3/9] Readme --- .../packages/opentelemetry-sdk-node/README.md | 14 ++- .../opentelemetry-sdk-node/src/sdk.ts | 103 +++++++++--------- 2 files changed, 64 insertions(+), 53 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/README.md b/experimental/packages/opentelemetry-sdk-node/README.md index 1722da44d1..f376e54534 100644 --- a/experimental/packages/opentelemetry-sdk-node/README.md +++ b/experimental/packages/opentelemetry-sdk-node/README.md @@ -91,6 +91,14 @@ Use a custom context manager. Default: [AsyncHooksContextManager](../../../packa Use a custom propagator. Default: [CompositePropagator](../../../packages/opentelemetry-core/src/propagation/composite.ts) using [W3C Trace Context](../../../packages/opentelemetry-core/README.md#w3ctracecontextpropagator-propagator) and [Baggage](../../../packages/opentelemetry-core/README.md#baggage-propagator) +### logRecordProcessor + +Deprecated, please use [logRecordProcessors](#logRecordProcessors) instead. + +### logRecordProcessors + +An array of log record processors to register to the logger provider. + ### metricReader Add a [MetricReader](../opentelemetry-sdk-metrics/src/export/MetricReader.ts) @@ -177,15 +185,16 @@ Set the log level by setting the `OTEL_LOG_LEVEL` environment variable to enums: The default level is `INFO`. -## Configure Trace Exporter from environment +## Configure Exporters from environment -This is an alternative to programmatically configuring an exporter or span processor. This package will auto setup the default `otlp` exporter with `http/protobuf` protocol if `traceExporter` or `spanProcessor` hasn't been passed into the `NodeSDK` constructor. +This is an alternative to programmatically configuring an exporter or span processor. For traces this package will auto setup the default `otlp` exporter with `http/protobuf` protocol if `traceExporter` or `spanProcessor` hasn't been passed into the `NodeSDK` constructor. ### Exporters | Environment variable | Description | |----------------------|-------------| | OTEL_TRACES_EXPORTER | List of exporters to be used for tracing, separated by commas. Options include `otlp`, `jaeger`, `zipkin`, and `none`. Default is `otlp`. `none` means no autoconfigured exporter. +| OTEL_LOGS_EXPORTER | List of exporters to be used for logging, separated by commas. Options include `otlp`, `console` and `none`. Default is `otlp`. `none` means no autoconfigured exporter. ### OTLP Exporter @@ -194,6 +203,7 @@ This is an alternative to programmatically configuring an exporter or span proce | OTEL_EXPORTER_OTLP_PROTOCOL | The transport protocol to use on OTLP trace, metric, and log requests. Options include `grpc`, `http/protobuf`, and `http/json`. Default is `http/protobuf`. | | OTEL_EXPORTER_OTLP_TRACES_PROTOCOL | The transport protocol to use on OTLP trace requests. Options include `grpc`, `http/protobuf`, and `http/json`. Default is `http/protobuf`. | | OTEL_EXPORTER_OTLP_METRICS_PROTOCOL | The transport protocol to use on OTLP metric requests. Options include `grpc`, `http/protobuf`, and `http/json`. Default is `http/protobuf`. | +| OTEL_EXPORTER_OTLP_LOGS_PROTOCOL | The transport protocol to use on OTLP log requests. Options include `grpc`, `http/protobuf`, and `http/json`. Default is `http/protobuf`. | Additionally, you can specify other applicable environment variables that apply to each exporter such as the following: diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index e740ceb524..2a82a89ee0 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -191,11 +191,13 @@ export class NodeSDK { }; } - if (configuration.logRecordProcessor || configuration.logRecordProcessors) { + if (configuration.logRecordProcessors) { this._loggerProviderConfig = { - logRecordProcessors: configuration.logRecordProcessors - ? configuration.logRecordProcessors - : [configuration.logRecordProcessor!], + logRecordProcessors: configuration.logRecordProcessors, + }; + } else if (configuration.logRecordProcessor) { + this._loggerProviderConfig = { + logRecordProcessors: [configuration.logRecordProcessor], }; } else { this.configureLoggerProviderFromEnv(envWithoutDefaults); @@ -340,59 +342,58 @@ export class NodeSDK { envWithoutDefaults: ENVIRONMENT ): void { const logExportersList = envWithoutDefaults.OTEL_LOGS_EXPORTER; - if (logExportersList) { - const exporters: LogRecordExporter[] = []; - const enabledExporters = filterBlanksAndNulls( - logExportersList.split(',') + if (!logExportersList) { + diag.warn(`No log exporters specified. Logs will not be exported.`); + return; + } + + const exporters: LogRecordExporter[] = []; + const enabledExporters = filterBlanksAndNulls(logExportersList.split(',')); + + if (enabledExporters.includes('none')) { + diag.warn( + `OTEL_LOGS_EXPORTER contains "none". Logger provider will not be initialized.` ); + return; + } - if (enabledExporters.includes('none')) { + enabledExporters.forEach(exporter => { + if (exporter === 'otlp') { + const protocol = ( + envWithoutDefaults.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? + envWithoutDefaults.OTEL_EXPORTER_OTLP_PROTOCOL + )?.trim(); + switch (protocol) { + case 'grpc': + exporters.push(new OTLPGrpcLogExporter()); + break; + case 'http/json': + exporters.push(new OTLPHttpLogExporter()); + break; + case 'http/protobuf': + exporters.push(new OTLPProtoLogExporter()); + break; + default: + diag.debug( + `Unsupported or undefined OTLP logs protocol. Using http/protobuf.` + ); + exporters.push(new OTLPProtoLogExporter()); + } + } else if (exporter === 'console') { + exporters.push(new ConsoleLogRecordExporter()); + } else { diag.warn( - `OTEL_LOGS_EXPORTER contains "none". Logger provider will not be initialized.` + `Unsupported log exporter: ${exporter}. Supported exporters are: otlp, console.` ); - return; } + }); - enabledExporters.forEach(exporter => { - if (exporter === 'otlp') { - const protocol = ( - envWithoutDefaults.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? - envWithoutDefaults.OTEL_EXPORTER_OTLP_PROTOCOL - )?.trim(); - switch (protocol) { - case 'grpc': - exporters.push(new OTLPGrpcLogExporter()); - break; - case 'http/json': - exporters.push(new OTLPHttpLogExporter()); - break; - case 'http/protobuf': - exporters.push(new OTLPProtoLogExporter()); - break; - default: - diag.warn( - `Unsupported or undefined OTLP logs protocol. Using http/protobuf.` - ); - exporters.push(new OTLPProtoLogExporter()); - } - } else if (exporter === 'console') { - exporters.push(new ConsoleLogRecordExporter()); - } else { - diag.warn( - `Unsupported log exporter: ${exporter}. Logs will not be exported.` - ); - } - }); - - if (exporters.length > 0) { - this._loggerProviderConfig = { - logRecordProcessors: exporters.map( - exporter => new BatchLogRecordProcessor(exporter) - ), - }; - } - } else { - diag.warn(`No log exporters specified. Logs will not be exported.`); + if (exporters.length > 0) { + this._loggerProviderConfig = { + logRecordProcessors: exporters.map( + exporter => new BatchLogRecordProcessor(exporter) + ), + }; } } } From 4f136a95440edef7fda5c0d30299719b9647f2ba Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Mon, 10 Jun 2024 11:56:34 +0300 Subject: [PATCH 4/9] Fix lint --- experimental/CHANGELOG.md | 1 + experimental/packages/opentelemetry-sdk-node/README.md | 2 +- experimental/packages/opentelemetry-sdk-node/tsconfig.json | 6 ++++++ experimental/packages/otlp-grpc-exporter-base/protos | 1 + experimental/packages/otlp-proto-exporter-base/protos | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) create mode 160000 experimental/packages/otlp-grpc-exporter-base/protos create mode 160000 experimental/packages/otlp-proto-exporter-base/protos diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index b9d176321b..278193253c 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to experimental packages in this project will be documented ### :rocket: (Enhancement) * refactor(instrumentation-fetch): move fetch to use SEMATRR [#4632](https://github.com/open-telemetry/opentelemetry-js/pull/4632) +* feat(sdk-node): Automatically configure logs exporter [#4740](https://github.com/open-telemetry/opentelemetry-js/pull/4740) ### :bug: (Bug Fix) diff --git a/experimental/packages/opentelemetry-sdk-node/README.md b/experimental/packages/opentelemetry-sdk-node/README.md index 87d12d645b..e2e5fe39d6 100644 --- a/experimental/packages/opentelemetry-sdk-node/README.md +++ b/experimental/packages/opentelemetry-sdk-node/README.md @@ -93,7 +93,7 @@ Use a custom propagator. Default: [CompositePropagator](../../../packages/opente ### logRecordProcessor -Deprecated, please use [logRecordProcessors](#logRecordProcessors) instead. +Deprecated, please use [logRecordProcessors](#logrecordprocessors) instead. ### logRecordProcessors diff --git a/experimental/packages/opentelemetry-sdk-node/tsconfig.json b/experimental/packages/opentelemetry-sdk-node/tsconfig.json index 65eee6bca0..f04c9fdb8e 100644 --- a/experimental/packages/opentelemetry-sdk-node/tsconfig.json +++ b/experimental/packages/opentelemetry-sdk-node/tsconfig.json @@ -42,9 +42,15 @@ { "path": "../api-logs" }, + { + "path": "../exporter-logs-otlp-grpc" + }, { "path": "../exporter-logs-otlp-http" }, + { + "path": "../exporter-logs-otlp-proto" + }, { "path": "../exporter-trace-otlp-grpc" }, diff --git a/experimental/packages/otlp-grpc-exporter-base/protos b/experimental/packages/otlp-grpc-exporter-base/protos new file mode 160000 index 0000000000..1608f92cf0 --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/protos @@ -0,0 +1 @@ +Subproject commit 1608f92cf08119f9aec237c910b200d1317ec696 diff --git a/experimental/packages/otlp-proto-exporter-base/protos b/experimental/packages/otlp-proto-exporter-base/protos new file mode 160000 index 0000000000..1608f92cf0 --- /dev/null +++ b/experimental/packages/otlp-proto-exporter-base/protos @@ -0,0 +1 @@ +Subproject commit 1608f92cf08119f9aec237c910b200d1317ec696 From 9f1d913380089696d90eafdad44fef45912705fe Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Mon, 10 Jun 2024 13:59:15 +0300 Subject: [PATCH 5/9] Remove protos --- experimental/packages/otlp-grpc-exporter-base/protos | 1 - experimental/packages/otlp-proto-exporter-base/protos | 1 - 2 files changed, 2 deletions(-) delete mode 160000 experimental/packages/otlp-grpc-exporter-base/protos delete mode 160000 experimental/packages/otlp-proto-exporter-base/protos diff --git a/experimental/packages/otlp-grpc-exporter-base/protos b/experimental/packages/otlp-grpc-exporter-base/protos deleted file mode 160000 index 1608f92cf0..0000000000 --- a/experimental/packages/otlp-grpc-exporter-base/protos +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1608f92cf08119f9aec237c910b200d1317ec696 diff --git a/experimental/packages/otlp-proto-exporter-base/protos b/experimental/packages/otlp-proto-exporter-base/protos deleted file mode 160000 index 1608f92cf0..0000000000 --- a/experimental/packages/otlp-proto-exporter-base/protos +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1608f92cf08119f9aec237c910b200d1317ec696 From 7b66f848358a7c90c45c88a6f7f80d95c8b5ef51 Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Mon, 1 Jul 2024 17:47:16 +0300 Subject: [PATCH 6/9] Fix PR comments --- .../opentelemetry-sdk-node/src/sdk.ts | 22 +++++++------------ .../opentelemetry-sdk-node/test/sdk.test.ts | 15 +++++++------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 70cfd3a672..e2a6ff3ec4 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -59,11 +59,7 @@ import { import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { NodeSDKConfiguration } from './types'; import { TracerProviderWithEnvExporters } from './TracerProviderWithEnvExporter'; -import { - ENVIRONMENT, - getEnv, - getEnvWithoutDefaults, -} from '@opentelemetry/core'; +import { getEnv, getEnvWithoutDefaults } from '@opentelemetry/core'; import { getResourceDetectorsFromEnv, filterBlanksAndNulls } from './utils'; /** This class represents everything needed to register a fully configured OpenTelemetry Node.js SDK */ @@ -196,7 +192,7 @@ export class NodeSDK { logRecordProcessors: [configuration.logRecordProcessor], }; } else { - this.configureLoggerProviderFromEnv(envWithoutDefaults); + this.configureLoggerProviderFromEnv(); } if (configuration.metricReader || configuration.views) { @@ -331,12 +327,10 @@ export class NodeSDK { ); } - private configureLoggerProviderFromEnv( - envWithoutDefaults: ENVIRONMENT - ): void { - const logExportersList = envWithoutDefaults.OTEL_LOGS_EXPORTER; + private configureLoggerProviderFromEnv(): void { + const logExportersList = process.env.OTEL_LOGS_EXPORTER; if (!logExportersList) { - diag.warn(`No log exporters specified. Logs will not be exported.`); + diag.info(`No log exporters specified. Logs will not be exported.`); return; } @@ -344,7 +338,7 @@ export class NodeSDK { const enabledExporters = filterBlanksAndNulls(logExportersList.split(',')); if (enabledExporters.includes('none')) { - diag.warn( + diag.info( `OTEL_LOGS_EXPORTER contains "none". Logger provider will not be initialized.` ); return; @@ -353,8 +347,8 @@ export class NodeSDK { enabledExporters.forEach(exporter => { if (exporter === 'otlp') { const protocol = ( - envWithoutDefaults.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? - envWithoutDefaults.OTEL_EXPORTER_OTLP_PROTOCOL + process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? + process.env.OTEL_EXPORTER_OTLP_PROTOCOL )?.trim(); switch (protocol) { case 'grpc': diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 8fb675bca2..cae93cedf5 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -65,7 +65,7 @@ import { serviceInstanceIdDetectorSync, } from '@opentelemetry/resources'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { logs, NoopLoggerProvider } from '@opentelemetry/api-logs'; +import { logs } from '@opentelemetry/api-logs'; import { SimpleLogRecordProcessor, InMemoryLogRecordExporter, @@ -899,7 +899,7 @@ describe('Node SDK', () => { let stubLogger: Sinon.SinonStub; beforeEach(() => { - stubLogger = Sinon.stub(diag, 'warn'); + stubLogger = Sinon.stub(diag, 'info'); }); afterEach(() => { @@ -919,6 +919,7 @@ describe('Node SDK', () => { }); it('should not register the provider if OTEL_LOGS_EXPORTER contains none', async () => { + const logsAPIStub = Sinon.spy(logs, 'setGlobalLoggerProvider'); env.OTEL_LOGS_EXPORTER = 'console,none'; const sdk = new NodeSDK(); sdk.start(); @@ -927,7 +928,7 @@ describe('Node SDK', () => { 'OTEL_LOGS_EXPORTER contains "none". Logger provider will not be initialized.' ); - assert(logs.getLoggerProvider() instanceof NoopLoggerProvider); + Sinon.assert.notCalled(logsAPIStub); await sdk.shutdown(); }); @@ -1172,7 +1173,7 @@ describe('setup exporter from env', () => { sdk.start(); assert.strictEqual( - stubLoggerError.args[1][0], + stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none". SDK will not be initialized.' ); delete env.OTEL_TRACES_EXPORTER; @@ -1223,7 +1224,7 @@ describe('setup exporter from env', () => { sdk.start(); assert.strictEqual( - stubLoggerError.args[1][0], + stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none" along with other exporters. Using default otlp exporter.' ); delete env.OTEL_TRACES_EXPORTER; @@ -1235,12 +1236,12 @@ describe('setup exporter from env', () => { sdk.start(); assert.strictEqual( - stubLoggerError.args[1][0], + stubLoggerError.args[0][0], 'Unrecognized OTEL_TRACES_EXPORTER value: invalid.' ); assert.strictEqual( - stubLoggerError.args[2][0], + stubLoggerError.args[1][0], 'Unable to set up trace exporter(s) due to invalid exporter and/or protocol values.' ); From c57496c29a98b93da5af579280731954f59447f9 Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Wed, 10 Jul 2024 21:26:50 +0300 Subject: [PATCH 7/9] Fix PR comments --- experimental/CHANGELOG.md | 3 +- .../opentelemetry-sdk-node/src/sdk.ts | 39 ++++++++++++------- .../opentelemetry-sdk-node/test/sdk.test.ts | 15 +------ 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index aba1df9a3c..c992750eba 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to experimental packages in this project will be documented ## Unreleased +* feat(sdk-node): Automatically configure logs exporter [#4740](https://github.com/open-telemetry/opentelemetry-js/pull/4740) + ### :boom: Breaking Change ### :rocket: (Enhancement) @@ -22,7 +24,6 @@ All notable changes to experimental packages in this project will be documented ### :rocket: (Enhancement) * refactor(instrumentation-fetch): move fetch to use SEMATRR [#4632](https://github.com/open-telemetry/opentelemetry-js/pull/4632) -* feat(sdk-node): Automatically configure logs exporter [#4740](https://github.com/open-telemetry/opentelemetry-js/pull/4740) * refactor(otlp-transformer): use explicit exports [#4785](https://github.com/open-telemetry/opentelemetry-js/pull/4785) @pichlermarc ### :bug: (Bug Fix) diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index e2a6ff3ec4..9321e04a6d 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -43,6 +43,7 @@ import { BatchLogRecordProcessor, ConsoleLogRecordExporter, LogRecordExporter, + SimpleLogRecordProcessor, } from '@opentelemetry/sdk-logs'; import { OTLPLogExporter as OTLPHttpLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; import { OTLPLogExporter as OTLPGrpcLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; @@ -191,6 +192,9 @@ export class NodeSDK { this._loggerProviderConfig = { logRecordProcessors: [configuration.logRecordProcessor], }; + diag.warn( + "The 'logRecordProcessor' option is deprecated. Please use 'logRecordProcessors' instead." + ); } else { this.configureLoggerProviderFromEnv(); } @@ -328,15 +332,14 @@ export class NodeSDK { } private configureLoggerProviderFromEnv(): void { - const logExportersList = process.env.OTEL_LOGS_EXPORTER; - if (!logExportersList) { - diag.info(`No log exporters specified. Logs will not be exported.`); - return; - } - - const exporters: LogRecordExporter[] = []; + const logExportersList = process.env.OTEL_LOGS_EXPORTER ?? ''; const enabledExporters = filterBlanksAndNulls(logExportersList.split(',')); + if (enabledExporters.length === 0) { + diag.info('OTEL_LOGS_EXPORTER is empty. Using default otlp exporter.'); + enabledExporters.push('otlp'); + } + if (enabledExporters.includes('none')) { diag.info( `OTEL_LOGS_EXPORTER contains "none". Logger provider will not be initialized.` @@ -344,12 +347,15 @@ export class NodeSDK { return; } + const exporters: LogRecordExporter[] = []; + enabledExporters.forEach(exporter => { if (exporter === 'otlp') { const protocol = ( process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ?? process.env.OTEL_EXPORTER_OTLP_PROTOCOL )?.trim(); + switch (protocol) { case 'grpc': exporters.push(new OTLPGrpcLogExporter()); @@ -360,10 +366,13 @@ export class NodeSDK { case 'http/protobuf': exporters.push(new OTLPProtoLogExporter()); break; + case undefined: + case '': + diag.info(`OTLP logs protocol is not set. Using http/protobuf.`); + exporters.push(new OTLPProtoLogExporter()); + break; default: - diag.debug( - `Unsupported or undefined OTLP logs protocol. Using http/protobuf.` - ); + diag.warn(`Unsupported OTLP logs protocol. Using http/protobuf.`); exporters.push(new OTLPProtoLogExporter()); } } else if (exporter === 'console') { @@ -377,9 +386,13 @@ export class NodeSDK { if (exporters.length > 0) { this._loggerProviderConfig = { - logRecordProcessors: exporters.map( - exporter => new BatchLogRecordProcessor(exporter) - ), + logRecordProcessors: exporters.map(exporter => { + if (exporter instanceof ConsoleLogRecordExporter) { + return new SimpleLogRecordProcessor(exporter); + } else { + return new BatchLogRecordProcessor(exporter); + } + }), }; } } diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index cae93cedf5..2af3558158 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -134,7 +134,6 @@ describe('Node SDK', () => { 'tracer provider should not have changed' ); assert.ok(!(metrics.getMeterProvider() instanceof MeterProvider)); - assert.ok(!(logs.getLoggerProvider() instanceof LoggerProvider)); delete env.OTEL_TRACES_EXPORTER; await sdk.shutdown(); }); @@ -906,18 +905,6 @@ describe('Node SDK', () => { stubLogger.reset(); }); - it('should log a warning if OTEL_LOGS_EXPORTER not set', async () => { - const sdk = new NodeSDK(); - sdk.start(); - - assert.strictEqual( - stubLogger.args[0][0], - 'No log exporters specified. Logs will not be exported.' - ); - - await sdk.shutdown(); - }); - it('should not register the provider if OTEL_LOGS_EXPORTER contains none', async () => { const logsAPIStub = Sinon.spy(logs, 'setGlobalLoggerProvider'); env.OTEL_LOGS_EXPORTER = 'console,none'; @@ -947,7 +934,7 @@ describe('Node SDK', () => { ); assert( sharedState.registeredLogRecordProcessors[0] instanceof - BatchLogRecordProcessor + SimpleLogRecordProcessor ); // defaults to http/protobuf assert( From 081212facf8411efcc3b72a3d35394cdcfa3e2e0 Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Tue, 13 Aug 2024 13:06:20 +0300 Subject: [PATCH 8/9] PR comments --- .../opentelemetry-sdk-node/src/sdk.ts | 7 ++--- .../opentelemetry-sdk-node/test/sdk.test.ts | 26 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 30a0d7f4cb..727be5d70e 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -367,18 +367,19 @@ export class NodeSDK { break; case undefined: case '': - diag.info(`OTLP logs protocol is not set. Using http/protobuf.`); exporters.push(new OTLPProtoLogExporter()); break; default: - diag.warn(`Unsupported OTLP logs protocol. Using http/protobuf.`); + diag.warn( + `Unsupported OTLP logs protocol: "${protocol}". Using http/protobuf.` + ); exporters.push(new OTLPProtoLogExporter()); } } else if (exporter === 'console') { exporters.push(new ConsoleLogRecordExporter()); } else { diag.warn( - `Unsupported log exporter: ${exporter}. Supported exporters are: otlp, console.` + `Unsupported OTEL_LOGS_EXPORTER value: "${exporter}". Supported values are: otlp, console, none.` ); } }); diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 100ce4a245..1d8bc87fbd 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -951,6 +951,9 @@ describe('Node SDK', () => { afterEach(() => { stubLogger.reset(); + delete env.OTEL_LOGS_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; + delete env.OTEL_EXPORTER_OTLP_PROTOCOL; }); it('should not register the provider if OTEL_LOGS_EXPORTER contains none', async () => { @@ -967,6 +970,18 @@ describe('Node SDK', () => { await sdk.shutdown(); }); + it('should use otlp with http/protobuf by default', async () => { + const sdk = new NodeSDK(); + sdk.start(); + const loggerProvider = logs.getLoggerProvider(); + const sharedState = (loggerProvider as any)['_sharedState']; + assert( + sharedState.registeredLogRecordProcessors[0]._exporter instanceof + OTLPProtoLogExporter + ); + await sdk.shutdown(); + }); + it('should set up all allowed exporters', async () => { env.OTEL_LOGS_EXPORTER = 'console,otlp'; const sdk = new NodeSDK(); @@ -993,7 +1008,6 @@ describe('Node SDK', () => { sharedState.registeredLogRecordProcessors[1] instanceof BatchLogRecordProcessor ); - delete env.OTEL_LOGS_EXPORTER; await sdk.shutdown(); }); @@ -1011,8 +1025,6 @@ describe('Node SDK', () => { sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPGrpcLogExporter ); - delete env.OTEL_LOGS_EXPORTER; - delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; await sdk.shutdown(); }); @@ -1030,8 +1042,6 @@ describe('Node SDK', () => { sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPHttpLogExporter ); - delete env.OTEL_LOGS_EXPORTER; - delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; await sdk.shutdown(); }); @@ -1049,9 +1059,6 @@ describe('Node SDK', () => { sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPGrpcLogExporter ); - - delete env.OTEL_LOGS_EXPORTER; - delete env.OTEL_EXPORTER_OTLP_PROTOCOL; await sdk.shutdown(); }); @@ -1069,9 +1076,6 @@ describe('Node SDK', () => { sharedState.registeredLogRecordProcessors[0]._exporter instanceof OTLPProtoLogExporter ); - - delete env.OTEL_LOGS_EXPORTER; - delete env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL; await sdk.shutdown(); }); }); From 04df7d9465abe28fadbc5e307c1594e7778c1dae Mon Sep 17 00:00:00 2001 From: Marten Hennoch Date: Fri, 16 Aug 2024 12:21:59 +0300 Subject: [PATCH 9/9] Update changelog, mark as breaking --- experimental/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 02783fe435..d1dff04359 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -5,8 +5,6 @@ All notable changes to experimental packages in this project will be documented ## Unreleased -* feat(sdk-node): Automatically configure logs exporter [#4740](https://github.com/open-telemetry/opentelemetry-js/pull/4740) - ### :boom: Breaking Change * fix(instrumentation)!:remove unused description property from interface [#4847](https://github.com/open-telemetry/opentelemetry-js/pull/4847) @blumamir @@ -18,6 +16,7 @@ All notable changes to experimental packages in this project will be documented * allowing overrides of the `User-Agent` header was not specification compliant. * feat(exporter-*-otlp*)!: remove environment-variable specific code from browser exporters * (user-facing) removes the ability to configure browser exporters by using `process.env` polyfills +* feat(sdk-node)!: Automatically configure logs exporter [#4740](https://github.com/open-telemetry/opentelemetry-js/pull/4740) ### :rocket: (Enhancement)