diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 762a2b9aa6..c629931887 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes to experimental packages in this project will be documented ### :rocket: (Enhancement) +* feat(sdk-node): logs support added [#3969](https://github.com/open-telemetry/opentelemetry-js/pull/3969) @psk001 + ### :bug: (Bug Fix) ### :books: (Refine Doc) diff --git a/experimental/packages/opentelemetry-sdk-node/package.json b/experimental/packages/opentelemetry-sdk-node/package.json index dcf827d0fd..a99bab01a9 100644 --- a/experimental/packages/opentelemetry-sdk-node/package.json +++ b/experimental/packages/opentelemetry-sdk-node/package.json @@ -44,6 +44,7 @@ "access": "public" }, "dependencies": { + "@opentelemetry/api-logs":"0.41.0", "@opentelemetry/core": "1.15.0", "@opentelemetry/exporter-jaeger": "1.15.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.41.0", @@ -52,6 +53,7 @@ "@opentelemetry/exporter-zipkin": "1.15.0", "@opentelemetry/instrumentation": "0.41.0", "@opentelemetry/resources": "1.15.0", + "@opentelemetry/sdk-logs":"0.41.0", "@opentelemetry/sdk-metrics": "1.15.0", "@opentelemetry/sdk-trace-base": "1.15.0", "@opentelemetry/sdk-trace-node": "1.15.0", diff --git a/experimental/packages/opentelemetry-sdk-node/src/index.ts b/experimental/packages/opentelemetry-sdk-node/src/index.ts index d246174208..891288ee6e 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/index.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/index.ts @@ -17,6 +17,7 @@ export * as api from '@opentelemetry/api'; export * as contextBase from '@opentelemetry/api'; export * as core from '@opentelemetry/core'; +export * as logs from '@opentelemetry/sdk-logs'; export * as metrics from '@opentelemetry/sdk-metrics'; export * as node from '@opentelemetry/sdk-trace-node'; export * as resources from '@opentelemetry/resources'; diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 07921a88b9..8b96dddeab 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -21,6 +21,7 @@ import { diag, DiagConsoleLogger, } from '@opentelemetry/api'; +import { logs } from '@opentelemetry/api-logs'; import { InstrumentationOption, registerInstrumentations, @@ -35,6 +36,7 @@ import { Resource, ResourceDetectionConfig, } from '@opentelemetry/resources'; +import { LogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; import { MeterProvider, MetricReader, View } from '@opentelemetry/sdk-metrics'; import { BatchSpanProcessor, @@ -63,6 +65,13 @@ export type MeterProviderConfig = { views?: View[]; }; +export type LoggerProviderConfig = { + /** + * Reference to the LoggerRecordProcessor instance by the NodeSDK + */ + logRecordProcessor: LogRecordProcessor; +}; + export class NodeSDK { private _tracerProviderConfig?: { tracerConfig: NodeTracerConfig; @@ -70,6 +79,7 @@ export class NodeSDK { contextManager?: ContextManager; textMapPropagator?: TextMapPropagator; }; + private _loggerProviderConfig?: LoggerProviderConfig; private _meterProviderConfig?: MeterProviderConfig; private _instrumentations: InstrumentationOption[]; @@ -79,6 +89,7 @@ export class NodeSDK { private _autoDetectResources: boolean; private _tracerProvider?: NodeTracerProvider | TracerProviderWithEnvExporters; + private _loggerProvider?: LoggerProvider; private _meterProvider?: MeterProvider; private _serviceName?: string; @@ -140,6 +151,13 @@ export class NodeSDK { ); } + if (configuration.logRecordProcessor) { + const loggerProviderConfig: LoggerProviderConfig = { + logRecordProcessor: configuration.logRecordProcessor, + }; + this.configureLoggerProvider(loggerProviderConfig); + } + if (configuration.metricReader || configuration.views) { const meterProviderConfig: MeterProviderConfig = {}; if (configuration.metricReader) { @@ -175,6 +193,30 @@ export class NodeSDK { }; } + /**Set configurations needed to register a LoggerProvider */ + public configureLoggerProvider(config: LoggerProviderConfig): void { + // nothing is set yet, we can set config and then return + if (this._loggerProviderConfig == null) { + this._loggerProviderConfig = config; + return; + } + + // make sure we do not override existing logRecordProcessor with other logRecordProcessors. + if ( + this._loggerProviderConfig.logRecordProcessor != null && + config.logRecordProcessor != null + ) { + throw new Error( + 'LogRecordProcessor passed but LogRecordProcessor has already been configured.' + ); + } + + // set logRecordProcessor, but make sure we do not override existing logRecordProcessors with null/undefined. + if (config.logRecordProcessor != null) { + this._loggerProviderConfig.logRecordProcessor = config.logRecordProcessor; + } + } + /** Set configurations needed to register a MeterProvider */ public configureMeterProvider(config: MeterProviderConfig): void { // nothing is set yet, we can set config and return. @@ -269,6 +311,19 @@ export class NodeSDK { propagator: this._tracerProviderConfig?.textMapPropagator, }); + if (this._loggerProviderConfig) { + const loggerProvider = new LoggerProvider({ + resource: this._resource, + }); + loggerProvider.addLogRecordProcessor( + this._loggerProviderConfig.logRecordProcessor + ); + + this._loggerProvider = loggerProvider; + + logs.setGlobalLoggerProvider(loggerProvider); + } + if (this._meterProviderConfig) { const meterProvider = new MeterProvider({ resource: this._resource, @@ -299,6 +354,9 @@ export class NodeSDK { if (this._tracerProvider) { promises.push(this._tracerProvider.shutdown()); } + if (this._loggerProvider) { + promises.push(this._loggerProvider.shutdown()); + } if (this._meterProvider) { promises.push(this._meterProvider.shutdown()); } diff --git a/experimental/packages/opentelemetry-sdk-node/src/types.ts b/experimental/packages/opentelemetry-sdk-node/src/types.ts index 03efc31142..9292e0ae70 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/types.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/types.ts @@ -18,6 +18,7 @@ import type { ContextManager } from '@opentelemetry/api'; import { TextMapPropagator } from '@opentelemetry/api'; import { InstrumentationOption } from '@opentelemetry/instrumentation'; import { Detector, DetectorSync, IResource } from '@opentelemetry/resources'; +import { LogRecordProcessor } from '@opentelemetry/sdk-logs'; import { MetricReader, View } from '@opentelemetry/sdk-metrics'; import { Sampler, @@ -31,6 +32,7 @@ export interface NodeSDKConfiguration { autoDetectResources: boolean; contextManager: ContextManager; textMapPropagator: TextMapPropagator; + logRecordProcessor: LogRecordProcessor; metricReader: MetricReader; views: View[]; instrumentations: InstrumentationOption[]; diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 7a385a7604..6fb76f3dad 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -59,6 +59,12 @@ import { Resource, } from '@opentelemetry/resources'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { logs } from '@opentelemetry/api-logs'; +import { + SimpleLogRecordProcessor, + InMemoryLogRecordExporter, + LoggerProvider, +} from '@opentelemetry/sdk-logs'; const DefaultContextManager = semver.gte(process.version, '14.8.0') ? AsyncLocalStorageContextManager @@ -112,6 +118,7 @@ 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; }); @@ -233,6 +240,40 @@ describe('Node SDK', () => { await sdk.shutdown(); delete env.OTEL_TRACES_EXPORTER; }); + + it('should register a logger provider if a log record processor is provided', async () => { + env.OTEL_TRACES_EXPORTER = 'none'; + const logRecordExporter = new InMemoryLogRecordExporter(); + const logRecordProcessor = new SimpleLogRecordProcessor( + logRecordExporter + ); + const sdk = new NodeSDK({ + logRecordProcessor: logRecordProcessor, + autoDetectResources: false, + }); + + sdk.start(); + + assert.strictEqual( + context['_getContextManager'](), + ctxManager, + 'context manager should not change' + ); + assert.strictEqual( + propagation['_getGlobalPropagator'](), + propagator, + 'propagator should not change' + ); + assert.strictEqual( + (trace.getTracerProvider() as ProxyTracerProvider).getDelegate(), + delegate, + 'tracer provider should not have changed' + ); + + assert.ok(logs.getLoggerProvider() instanceof LoggerProvider); + await sdk.shutdown(); + delete env.OTEL_TRACES_EXPORTER; + }); }); async function waitForNumberOfMetrics( @@ -406,6 +447,28 @@ describe('Node SDK', () => { ); }); + it('should throw error when calling configureLoggerProvider when logRecordProcessor is already configured', () => { + const logRecordExporter = new InMemoryLogRecordExporter(); + const logRecordProcessor = new SimpleLogRecordProcessor(logRecordExporter); + const sdk = new NodeSDK({ + logRecordProcessor: logRecordProcessor, + autoDetectResources: false, + }); + + assert.throws( + () => { + sdk.configureLoggerProvider({ + logRecordProcessor: logRecordProcessor, + }); + }, + (error: Error) => { + return error.message.includes( + 'LogRecordProcessor passed but LogRecordProcessor has already been configured.' + ); + } + ); + }); + describe('detectResources', async () => { beforeEach(() => { process.env.OTEL_RESOURCE_ATTRIBUTES = diff --git a/experimental/packages/opentelemetry-sdk-node/tsconfig.json b/experimental/packages/opentelemetry-sdk-node/tsconfig.json index add730f8bc..4091bf66e9 100644 --- a/experimental/packages/opentelemetry-sdk-node/tsconfig.json +++ b/experimental/packages/opentelemetry-sdk-node/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../../../api" }, + { + "path": "../../packages/api-logs" + }, { "path": "../../../packages/opentelemetry-context-async-hooks" }, @@ -36,6 +39,9 @@ { "path": "../../../packages/opentelemetry-semantic-conventions" }, + { + "path": "../../packages/sdk-logs" + }, { "path": "../../../packages/sdk-metrics" },