Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sdk/monitor/monitor-opentelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 6 additions & 0 deletions sdk/monitor/monitor-opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const options: AzureMonitorOpenTelemetryOptions = {
resource: resource,
logRecordProcessors: [],
spanProcessors: [],
views: [],
};
useAzureMonitor(options);
```
Expand Down Expand Up @@ -159,6 +160,11 @@ useAzureMonitor(options);
<td>Array of span processors to register to the global tracer provider. </td>
<td></td>
</tr>
<tr>
<td><code>views</code></td>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the documentation for the configure opentelemetry needs to be updated for this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will need to be, but waiting on PR review & API review.

<td>Array of metric views to register to the global meter provider.</td>
<td></td>
</tr>
<tr>
<td><code>enableTraceBasedSamplingForLogs</code></td>
<td>Enable log sampling based on trace.</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -27,6 +28,7 @@ export interface AzureMonitorOpenTelemetryOptions {
samplingRatio?: number;
spanProcessors?: SpanProcessor[];
tracesPerSecond?: number;
views?: ViewOptions[];
}

// @public
Expand Down
7 changes: 5 additions & 2 deletions sdk/monitor/monitor-opentelemetry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -81,18 +81,21 @@ 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[] = [
metricHandler.getMetricReader(),
...(options?.metricReaders || []),
];

const views: ViewOptions[] = metricHandler.getViews().concat(customViews);

// Initialize OpenTelemetry SDK
const sdkConfig: Partial<NodeSDKConfiguration> = {
autoDetectResources: true,
metricReaders: metricReaders,
views: metricHandler.getViews(),
views,
instrumentations: instrumentations,
logRecordProcessors: [
logHandler.getAzureLogRecordProcessor(),
Expand Down
4 changes: 3 additions & 1 deletion sdk/monitor/monitor-opentelemetry/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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[];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -155,6 +155,20 @@ 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],
Copy link
Member

@rads-1996 rads-1996 Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to have multiple custom views right? In that case, does config have the ability to process them as a list of views?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we just pass this to the NodeSDKConfiguration in OpenTelemetry which takes a ViewOptions array.

};
useAzureMonitor(config);
const meterConfig = (_getSdkInstance() as any)?._meterProviderConfig;
expect(meterConfig).toBeDefined();
expect(meterConfig?.views).toContain(customView);
});

it("should set statsbeat features", () => {
const config: AzureMonitorOpenTelemetryOptions = {
azureMonitorExporterOptions: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +29,7 @@ describe("Library/TraceHandler", () => {
const mockHttpServerPort = 8085;
let tracerProvider: NodeTracerProvider;
let exportSpy: MockInstance<AzureMonitorTraceExporter["export"]>;
let activeInstrumentations: Instrumentation[] = [];

beforeAll(async () => {
_config = new InternalConfig();
Expand Down Expand Up @@ -71,6 +73,8 @@ describe("Library/TraceHandler", () => {
});

afterEach(async () => {
activeInstrumentations.forEach((instrumentation) => instrumentation.disable());
activeInstrumentations = [];
await metricHandler.shutdown();
await handler.shutdown();
metrics.disable();
Expand All @@ -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
Expand Down Expand Up @@ -158,6 +166,9 @@ describe("Library/TraceHandler", () => {
],
});
trace.setGlobalTracerProvider(tracerProvider);
activeInstrumentations.forEach((instrumentation) => {
instrumentation.setTracerProvider(tracerProvider);
});
await makeHttpRequest();
await tracerProvider.forceFlush();
expect(exportSpy).toHaveBeenCalledOnce();
Expand Down
1 change: 1 addition & 0 deletions sdk/monitor/monitor-opentelemetry/test/snippets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe("snippets", () => {
resource: resource,
logRecordProcessors: [],
spanProcessors: [],
views: [],
};

useAzureMonitor(options);
Expand Down
Loading