Azure Monitor OpenTelemetry SDK for TypeScript
Auto-instrument Node.js applications with distributed tracing, metrics, and logs.
Installation
Distro (recommended - auto-instrumentation)
npm install @azure/monitor-opentelemetry
Low-level exporters (custom OpenTelemetry setup)
npm install @azure/monitor-opentelemetry-exporter
Custom logs ingestion
npm install @azure/monitor-ingestion
Environment Variables
APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=...;IngestionEndpoint=...
Quick Start (Auto-Instrumentation)
IMPORTANT: Call useAzureMonitor() BEFORE importing other modules.
import { useAzureMonitor } from "@azure/monitor-opentelemetry";
useAzureMonitor({ azureMonitorExporterOptions: { connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING } });
// Now import your application import express from "express"; const app = express();
ESM Support (Node.js 18.19+)
node --import @azure/monitor-opentelemetry/loader ./dist/index.js
package.json:
{ "scripts": { "start": "node --import @azure/monitor-opentelemetry/loader ./dist/index.js" } }
Full Configuration
import { useAzureMonitor, AzureMonitorOpenTelemetryOptions } from "@azure/monitor-opentelemetry"; import { resourceFromAttributes } from "@opentelemetry/resources";
const options: AzureMonitorOpenTelemetryOptions = { azureMonitorExporterOptions: { connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING, storageDirectory: "/path/to/offline/storage", disableOfflineStorage: false },
// Sampling samplingRatio: 1.0, // 0-1, percentage of traces
// Features enableLiveMetrics: true, enableStandardMetrics: true, enablePerformanceCounters: true,
// Instrumentation libraries instrumentationOptions: { azureSdk: { enabled: true }, http: { enabled: true }, mongoDb: { enabled: true }, mySql: { enabled: true }, postgreSql: { enabled: true }, redis: { enabled: true }, bunyan: { enabled: false }, winston: { enabled: false } },
// Custom resource resource: resourceFromAttributes({ "service.name": "my-service" }) };
useAzureMonitor(options);
Custom Traces
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("my-tracer");
const span = tracer.startSpan("doWork"); try { span.setAttribute("component", "worker"); span.setAttribute("operation.id", "42"); span.addEvent("processing started");
// Your work here
} catch (error) { span.recordException(error as Error); span.setStatus({ code: 2, message: (error as Error).message }); } finally { span.end(); }
Custom Metrics
import { metrics } from "@opentelemetry/api";
const meter = metrics.getMeter("my-meter");
// Counter const counter = meter.createCounter("requests_total"); counter.add(1, { route: "/api/users", method: "GET" });
// Histogram const histogram = meter.createHistogram("request_duration_ms"); histogram.record(150, { route: "/api/users" });
// Observable Gauge const gauge = meter.createObservableGauge("active_connections"); gauge.addCallback((result) => { result.observe(getActiveConnections(), { pool: "main" }); });
Manual Exporter Setup
Trace Exporter
import { AzureMonitorTraceExporter } from "@azure/monitor-opentelemetry-exporter"; import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
const exporter = new AzureMonitorTraceExporter({ connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING });
const provider = new NodeTracerProvider({ spanProcessors: [new BatchSpanProcessor(exporter)] });
provider.register();
Metric Exporter
import { AzureMonitorMetricExporter } from "@azure/monitor-opentelemetry-exporter"; import { PeriodicExportingMetricReader, MeterProvider } from "@opentelemetry/sdk-metrics"; import { metrics } from "@opentelemetry/api";
const exporter = new AzureMonitorMetricExporter({ connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING });
const meterProvider = new MeterProvider({ readers: [new PeriodicExportingMetricReader({ exporter })] });
metrics.setGlobalMeterProvider(meterProvider);
Log Exporter
import { AzureMonitorLogExporter } from "@azure/monitor-opentelemetry-exporter"; import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs"; import { logs } from "@opentelemetry/api-logs";
const exporter = new AzureMonitorLogExporter({ connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING });
const loggerProvider = new LoggerProvider(); loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(exporter));
logs.setGlobalLoggerProvider(loggerProvider);
Custom Logs Ingestion
import { DefaultAzureCredential } from "@azure/identity"; import { LogsIngestionClient, isAggregateLogsUploadError } from "@azure/monitor-ingestion";
const endpoint = "https://<dce>.ingest.monitor.azure.com"; const ruleId = "<data-collection-rule-id>"; const streamName = "Custom-MyTable_CL";
const client = new LogsIngestionClient(endpoint, new DefaultAzureCredential());
const logs = [ { Time: new Date().toISOString(), Computer: "Server1", Message: "Application started", Level: "Information" } ];
try { await client.upload(ruleId, streamName, logs); } catch (error) { if (isAggregateLogsUploadError(error)) { for (const uploadError of error.errors) { console.error("Failed logs:", uploadError.failedLogs); } } }
Custom Span Processor
import { SpanProcessor, ReadableSpan } from "@opentelemetry/sdk-trace-base"; import { Span, Context, SpanKind, TraceFlags } from "@opentelemetry/api"; import { useAzureMonitor } from "@azure/monitor-opentelemetry";
class FilteringSpanProcessor implements SpanProcessor { forceFlush(): Promise<void> { return Promise.resolve(); } shutdown(): Promise<void> { return Promise.resolve(); } onStart(span: Span, context: Context): void {}
onEnd(span: ReadableSpan): void { // Add custom attributes span.attributes["CustomDimension"] = "value";
// Filter out internal spans
if (span.kind === SpanKind.INTERNAL) {
span.spanContext().traceFlags = TraceFlags.NONE;
}
} }
useAzureMonitor({ spanProcessors: [new FilteringSpanProcessor()] });
Sampling
import { ApplicationInsightsSampler } from "@azure/monitor-opentelemetry-exporter"; import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
// Sample 75% of traces const sampler = new ApplicationInsightsSampler(0.75);
const provider = new NodeTracerProvider({ sampler });
Shutdown
import { useAzureMonitor, shutdownAzureMonitor } from "@azure/monitor-opentelemetry";
useAzureMonitor();
// On application shutdown process.on("SIGTERM", async () => { await shutdownAzureMonitor(); process.exit(0); });
Key Types
import { useAzureMonitor, shutdownAzureMonitor, AzureMonitorOpenTelemetryOptions, InstrumentationOptions } from "@azure/monitor-opentelemetry";
import { AzureMonitorTraceExporter, AzureMonitorMetricExporter, AzureMonitorLogExporter, ApplicationInsightsSampler, AzureMonitorExporterOptions } from "@azure/monitor-opentelemetry-exporter";
import { LogsIngestionClient, isAggregateLogsUploadError } from "@azure/monitor-ingestion";
Best Practices
-
Call useAzureMonitor() first - Before importing other modules
-
Use ESM loader for ESM projects - --import @azure/monitor-opentelemetry/loader
-
Enable offline storage - For reliable telemetry in disconnected scenarios
-
Set sampling ratio - For high-traffic applications
-
Add custom dimensions - Use span processors for enrichment
-
Graceful shutdown - Call shutdownAzureMonitor() to flush telemetry