diff --git a/sdk/monitor/monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/monitor-opentelemetry/CHANGELOG.md index a55cc88912ce..78c75bb38934 100644 --- a/sdk/monitor/monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/monitor-opentelemetry/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +### 1.15.0 () + +### Features Added + +- Allow configuring additional metric views through `AzureMonitorOpenTelemetryOptions` and pass them to the NodeSDK. + ### 1.14.2 (2025-11-13) ### Bugs Fixed diff --git a/sdk/monitor/monitor-opentelemetry/README.md b/sdk/monitor/monitor-opentelemetry/README.md index 9ebde71f3c27..0691e2515f8a 100644 --- a/sdk/monitor/monitor-opentelemetry/README.md +++ b/sdk/monitor/monitor-opentelemetry/README.md @@ -81,6 +81,7 @@ const options: AzureMonitorOpenTelemetryOptions = { resource: resource, logRecordProcessors: [], spanProcessors: [], + views: [], }; useAzureMonitor(options); ``` @@ -159,6 +160,11 @@ useAzureMonitor(options); Array of span processors to register to the global tracer provider. + + views + Array of metric views to register to the global meter provider. + + enableTraceBasedSamplingForLogs Enable log sampling based on trace. diff --git a/sdk/monitor/monitor-opentelemetry/review/monitor-opentelemetry-node.api.md b/sdk/monitor/monitor-opentelemetry/review/monitor-opentelemetry-node.api.md index 6552dc12512d..2fa9d64d9116 100644 --- a/sdk/monitor/monitor-opentelemetry/review/monitor-opentelemetry-node.api.md +++ b/sdk/monitor/monitor-opentelemetry/review/monitor-opentelemetry-node.api.md @@ -11,6 +11,7 @@ import type { MetricReader } from '@opentelemetry/sdk-metrics'; import { NodeSDK } from '@opentelemetry/sdk-node'; import type { Resource } from '@opentelemetry/resources'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; +import type { ViewOptions } from '@opentelemetry/sdk-metrics'; // @public export interface AzureMonitorOpenTelemetryOptions { @@ -27,6 +28,7 @@ export interface AzureMonitorOpenTelemetryOptions { samplingRatio?: number; spanProcessors?: SpanProcessor[]; tracesPerSecond?: number; + views?: ViewOptions[]; } // @public diff --git a/sdk/monitor/monitor-opentelemetry/src/index.ts b/sdk/monitor/monitor-opentelemetry/src/index.ts index 12bea406987d..1ccdfefe38cf 100644 --- a/sdk/monitor/monitor-opentelemetry/src/index.ts +++ b/sdk/monitor/monitor-opentelemetry/src/index.ts @@ -5,7 +5,7 @@ import { metrics, trace } from "@opentelemetry/api"; import { logs } from "@opentelemetry/api-logs"; import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node"; import { NodeSDK } from "@opentelemetry/sdk-node"; -import type { MetricReader } from "@opentelemetry/sdk-metrics"; +import type { MetricReader, ViewOptions } from "@opentelemetry/sdk-metrics"; import { InternalConfig } from "./shared/config.js"; import { MetricHandler } from "./metrics/index.js"; import { TraceHandler } from "./traces/handler.js"; @@ -81,6 +81,7 @@ export function useAzureMonitor(options?: AzureMonitorOpenTelemetryOptions): voi // Add extra SpanProcessors, and logRecordProcessors from user configuration const spanProcessors: SpanProcessor[] = options?.spanProcessors || []; const logRecordProcessors: LogRecordProcessor[] = options?.logRecordProcessors || []; + const customViews: ViewOptions[] = options?.views || []; // Prepare metric readers - always include Azure Monitor const metricReaders: MetricReader[] = [ @@ -88,11 +89,13 @@ export function useAzureMonitor(options?: AzureMonitorOpenTelemetryOptions): voi ...(options?.metricReaders || []), ]; + const views: ViewOptions[] = metricHandler.getViews().concat(customViews); + // Initialize OpenTelemetry SDK const sdkConfig: Partial = { autoDetectResources: true, metricReaders: metricReaders, - views: metricHandler.getViews(), + views, instrumentations: instrumentations, logRecordProcessors: [ logHandler.getAzureLogRecordProcessor(), diff --git a/sdk/monitor/monitor-opentelemetry/src/types.ts b/sdk/monitor/monitor-opentelemetry/src/types.ts index 6370c27d962f..8a95d35a4a3e 100644 --- a/sdk/monitor/monitor-opentelemetry/src/types.ts +++ b/sdk/monitor/monitor-opentelemetry/src/types.ts @@ -4,7 +4,7 @@ import type { AzureMonitorExporterOptions } from "@azure/monitor-opentelemetry-e import type { InstrumentationConfig } from "@opentelemetry/instrumentation"; import type { Resource } from "@opentelemetry/resources"; import type { LogRecordProcessor } from "@opentelemetry/sdk-logs"; -import type { MetricReader } from "@opentelemetry/sdk-metrics"; +import type { MetricReader, ViewOptions } from "@opentelemetry/sdk-metrics"; import type { SpanProcessor } from "@opentelemetry/sdk-trace-base"; /** @@ -37,6 +37,8 @@ export interface AzureMonitorOpenTelemetryOptions { spanProcessors?: SpanProcessor[]; /** An array of metric readers to register to the meter provider.*/ metricReaders?: MetricReader[]; + /** An array of metric views to register to the meter provider.*/ + views?: ViewOptions[]; } /** diff --git a/sdk/monitor/monitor-opentelemetry/test/internal/unit/main.test.ts b/sdk/monitor/monitor-opentelemetry/test/internal/unit/main.test.ts index b19fb907a8c6..222da1b00c48 100644 --- a/sdk/monitor/monitor-opentelemetry/test/internal/unit/main.test.ts +++ b/sdk/monitor/monitor-opentelemetry/test/internal/unit/main.test.ts @@ -6,7 +6,7 @@ import { metrics, trace } from "@opentelemetry/api"; import { logs } from "@opentelemetry/api-logs"; import type { AzureMonitorOpenTelemetryOptions } from "../../../src/index.js"; import { useAzureMonitor, shutdownAzureMonitor, _getSdkInstance } from "../../../src/index.js"; -import type { MeterProvider } from "@opentelemetry/sdk-metrics"; +import type { MeterProvider, ViewOptions } from "@opentelemetry/sdk-metrics"; import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; import type { StatsbeatEnvironmentConfig } from "../../../src/types.js"; @@ -155,6 +155,21 @@ describe("Main functions", () => { expect(spyonEmit).toHaveBeenCalled(); }); + it("should add custom metric views", () => { + const customView: ViewOptions = { meterName: "custom-meter" }; + const config: AzureMonitorOpenTelemetryOptions = { + azureMonitorExporterOptions: { + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000", + }, + views: [customView], + }; + useAzureMonitor(config); + // eslint-disable-next-line no-underscore-dangle + const meterConfig = (_getSdkInstance() as any)?._meterProviderConfig; + expect(meterConfig).toBeDefined(); + expect(meterConfig?.views).toContain(customView); + }); + it("should set statsbeat features", () => { const config: AzureMonitorOpenTelemetryOptions = { azureMonitorExporterOptions: { diff --git a/sdk/monitor/monitor-opentelemetry/test/internal/unit/traces/traceHandler.test.ts b/sdk/monitor/monitor-opentelemetry/test/internal/unit/traces/traceHandler.test.ts index 74c58d200d92..c05317150624 100644 --- a/sdk/monitor/monitor-opentelemetry/test/internal/unit/traces/traceHandler.test.ts +++ b/sdk/monitor/monitor-opentelemetry/test/internal/unit/traces/traceHandler.test.ts @@ -17,6 +17,7 @@ import { expect, afterEach, assert, beforeAll, describe, it, afterAll, vi } from import type Http from "node:http"; import { ExportResultCode } from "@opentelemetry/core"; import type { AzureMonitorTraceExporter } from "@azure/monitor-opentelemetry-exporter"; +import type { Instrumentation } from "@opentelemetry/instrumentation"; describe("Library/TraceHandler", () => { let http: typeof Http | null = null; @@ -28,6 +29,7 @@ describe("Library/TraceHandler", () => { const mockHttpServerPort = 8085; let tracerProvider: NodeTracerProvider; let exportSpy: MockInstance; + let activeInstrumentations: Instrumentation[] = []; beforeAll(async () => { _config = new InternalConfig(); @@ -71,6 +73,8 @@ describe("Library/TraceHandler", () => { }); afterEach(async () => { + activeInstrumentations.forEach((instrumentation) => instrumentation.disable()); + activeInstrumentations = []; await metricHandler.shutdown(); await handler.shutdown(); metrics.disable(); @@ -82,6 +86,10 @@ describe("Library/TraceHandler", () => { _config.instrumentationOptions.http = httpConfig; metricHandler = new MetricHandler(_config); handler = new TraceHandler(_config, metricHandler); + handler.getInstrumentations().forEach((instrumentation) => { + instrumentation.enable(); + activeInstrumentations.push(instrumentation); + }); // Because the instrumentation is registered globally, its config is not updated // when the handler is created. We need to mock the getConfig method to return @@ -158,6 +166,9 @@ describe("Library/TraceHandler", () => { ], }); trace.setGlobalTracerProvider(tracerProvider); + activeInstrumentations.forEach((instrumentation) => { + instrumentation.setTracerProvider(tracerProvider); + }); await makeHttpRequest(); await tracerProvider.forceFlush(); expect(exportSpy).toHaveBeenCalledOnce(); diff --git a/sdk/monitor/monitor-opentelemetry/test/snippets.spec.ts b/sdk/monitor/monitor-opentelemetry/test/snippets.spec.ts index f6faf0bf3557..c9d6fe379d88 100644 --- a/sdk/monitor/monitor-opentelemetry/test/snippets.spec.ts +++ b/sdk/monitor/monitor-opentelemetry/test/snippets.spec.ts @@ -67,6 +67,7 @@ describe("snippets", () => { resource: resource, logRecordProcessors: [], spanProcessors: [], + views: [], }; useAzureMonitor(options);