Microsoft OpenTelemetry distribution for Node.js — one import, one call, full observability across Azure Monitor, OTLP-compatible backends, and A365.
npm install @microsoft/opentelemetryImportant: Import and call
useMicrosoftOpenTelemetry()as early as possible in your application entry point so instrumentations can patch libraries before they are loaded.
Note: This package requires Node.js 22.0.0 or later. Its ESM loader flow relies on
--importandnode:module.register(). For background on why startup ordering matters, see the OpenTelemetry ESM Support documentation.
For ESM applications, instrumentation hooks must be registered before any instrumented modules (for example http, express, axios, or loggers) are loaded. This is a fundamental ESM constraint: modules cannot be instrumented after they are already loaded.
This pattern is not reliable for auto-instrumentation:
import { useMicrosoftOpenTelemetry } from "@microsoft/opentelemetry";
useMicrosoftOpenTelemetry();
import express from "express";Use --import so the loader is registered at process startup.
node --import @microsoft/opentelemetry/loader ./app.mjsFor example, in package.json:
{
"scripts": {
"start": "node --import @microsoft/opentelemetry/loader ./dist/index.js"
}
}If you prefer explicit telemetry configuration in a bootstrap file, preload that file instead:
telemetry.mjs:
import "@microsoft/opentelemetry/loader";
import { useMicrosoftOpenTelemetry } from "@microsoft/opentelemetry";
useMicrosoftOpenTelemetry({
azureMonitor: {
azureMonitorExporterOptions: {
connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
},
enableLiveMetrics: false,
},
instrumentationOptions: {
bunyan: { enabled: true },
winston: { enabled: true },
},
});Start your app:
node --import ./telemetry.mjs ./app.mjsYour application code can keep regular static ESM imports in app.mjs.
import { useMicrosoftOpenTelemetry } from "@microsoft/opentelemetry";
useMicrosoftOpenTelemetry({
a365: {
enabled: true,
tokenResolver: (agentId, tenantId) => getToken(agentId, tenantId),
},
});When you enable the A365 HTTP exporter, durable delivery is enabled by default. Tune the durable store if you need a protected persistent volume or different bounds:
useMicrosoftOpenTelemetry({
a365: {
enabled: true,
enableObservabilityExporter: true,
tokenResolver: (agentId, tenantId, authScopes) => getToken(agentId, tenantId, authScopes),
durableDelivery: {
storageDirectory: process.env.A365_DURABLE_STORAGE_DIRECTORY,
maxStorageBytes: 50 * 1024 * 1024,
maxRecordAgeMilliseconds: 2 * 24 * 60 * 60 * 1000,
},
},
});Set durableDelivery.enabled: false to force legacy network-only delivery.
With durable delivery enabled, retryable A365 exporter payloads (including HTTP 401, 408, 429,
and 5xx responses) are stored as plaintext local files in a stable per-application partition and
replayed with a freshly resolved token plus the exporter's current cluster/domain routing; durable
files never store bearer tokens or authoritative route metadata. Delivery is at-least-once, so
duplicates are possible. The SDK restricts durable storage to the current user and Administrators
on Windows and uses owner-only 0700 directories and 0600 files on POSIX. Use a protected
persistent volume if records must survive container or host restarts; ephemeral container storage
only survives process restarts and still counts against the container's ephemeral-storage quota.
If durable storage permissions cannot be secured, the exporter logs the initialization failure and
degrades to network-only delivery; successful and non-retryable live sends still complete, but
retryable results that cannot be persisted fail.
For A365 scenarios, scope APIs, baggage, hosting middleware, and official terminology alignment, see A365_DOCUMENTATION.md.
import { useMicrosoftOpenTelemetry } from "@microsoft/opentelemetry";
useMicrosoftOpenTelemetry({
azureMonitor: {
azureMonitorExporterOptions: {
connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
},
},
});import { useMicrosoftOpenTelemetry } from "@microsoft/opentelemetry";
// Set OTEL_EXPORTER_OTLP_ENDPOINT in your environment
useMicrosoftOpenTelemetry();That's it — traces, metrics, and logs are collected automatically with built-in instrumentations for HTTP, databases, and more.
| Option | Type | Default | Description |
|---|---|---|---|
resource |
Resource |
auto-detected | OpenTelemetry Resource (service name, version, etc.) |
samplingRatio |
number |
1.0 |
Ratio of telemetry items to transmit (0.0–1.0) |
tracesPerSecond |
number |
5 |
Max traces per second. Set to 0 to use samplingRatio instead |
instrumentationOptions |
InstrumentationOptions |
all enabled | Toggle built-in instrumentations (see below) |
spanProcessors |
SpanProcessor[] |
— | Additional span processors |
logRecordProcessors |
LogRecordProcessor[] |
— | Additional log record processors |
metricReaders |
MetricReader[] |
— | Additional metric readers |
views |
ViewOptions[] |
— | Metric views |
azureMonitor |
AzureMonitorOpenTelemetryOptions |
— | Azure Monitor backend config. When provided, Azure Monitor export is enabled |
a365 |
A365Options |
— | A365 observability config |
enableConsoleExporters |
boolean |
auto | Enable console exporters for traces, metrics, and logs |
enableSensitiveData |
boolean |
false |
Capture GenAI message content (prompts, completions, tool args/results, system instructions) on LangChain spans. Only enable in trusted, non-production environments |
Most instrumentations use InstrumentationConfig shape ({ enabled?: boolean, ... }).
- Built-in infra instrumentations (
http,azureSdk,mongoDb,mySql,postgreSql,redis,redis4) are enabled by default. - Logging instrumentations (
bunyan,winston,console) are disabled by default. - GenAI instrumentations (
openaiAgents,langchain) are enabled by default. - When
a365.enabledistrue, non-GenAI instrumentations (http,azureSdk, DB/cache, and logging) are disabled by default unless explicitly set ininstrumentationOptions.
Set enabled: true or enabled: false explicitly for predictable behavior.
| Key | Type | Default | Description |
|---|---|---|---|
http |
InstrumentationConfig |
enabled | HTTP client/server instrumentation |
azureSdk |
InstrumentationConfig |
enabled | Azure SDK instrumentation |
mongoDb |
InstrumentationConfig |
enabled | MongoDB instrumentation |
mySql |
InstrumentationConfig |
enabled | MySQL instrumentation |
postgreSql |
InstrumentationConfig |
enabled | PostgreSQL instrumentation |
redis |
InstrumentationConfig |
enabled | Redis instrumentation |
redis4 |
InstrumentationConfig |
enabled | Redis 4 instrumentation |
bunyan |
InstrumentationConfig |
disabled | Bunyan log instrumentation |
winston |
InstrumentationConfig |
disabled | Winston log instrumentation |
console |
InstrumentationConfig |
disabled | Console log instrumentation |
openaiAgents |
OpenAIAgentsInstrumentationConfig |
enabled | OpenAI Agents SDK instrumentation (requires @openai/agents) |
langchain |
LangChainInstrumentationConfig |
enabled | LangChain instrumentation (requires @langchain/core) |
useMicrosoftOpenTelemetry({
// Capture GenAI message content on LangChain spans (hidden by default).
// Only enable in trusted, non-production environments.
enableSensitiveData: true,
instrumentationOptions: {
// Disable specific built-in instrumentations
http: { enabled: false },
redis: { enabled: false },
// Enable GenAI instrumentations
openaiAgents: {
enabled: true,
isContentRecordingEnabled: true,
},
langchain: {
enabled: true,
},
},
});Capturing GenAI message content (
enableSensitiveData)LangChain instrumentation hides sensitive message content, prompts, completions, tool arguments/results, and system instructions, by default. Set the top-level
enableSensitiveData: trueto record it. Only enable content capture in trusted, non-production environments where capturing message content is intentional.
Disable most built-in auto-instrumentation:
useMicrosoftOpenTelemetry({
instrumentationOptions: {
http: { enabled: false },
azureSdk: { enabled: false },
azureFunctions: { enabled: false },
mongoDb: { enabled: false },
mySql: { enabled: false },
postgreSql: { enabled: false },
redis: { enabled: false },
redis4: { enabled: false },
bunyan: { enabled: false },
winston: { enabled: false },
openaiAgents: { enabled: false },
langchain: { enabled: false },
},
});Use console exporters when validating local telemetry or debugging setup.
useMicrosoftOpenTelemetry({
enableConsoleExporters: true,
});Behavior:
enableConsoleExporters: true: always enable console exporters (traces, metrics, logs).enableConsoleExporters: false: do not auto-add the standard console exporters.- Omitted: console exporters auto-enable only when no other exporter path is active.
| Option | Type | Default | Description |
|---|---|---|---|
azureMonitorExporterOptions |
AzureMonitorExporterOptions |
— | Exporter config including connectionString, storageDirectory, disableOfflineStorage |
enableLiveMetrics |
boolean |
true |
Enable Live Metrics streaming |
enableStandardMetrics |
boolean |
true |
Enable standard metrics collection |
enableTraceBasedSamplingForLogs |
boolean |
false |
Enable log sampling based on trace |
enablePerformanceCounters |
boolean |
true |
Enable performance counter collection |
browserSdkLoaderOptions |
BrowserSdkLoaderOptions |
disabled | Application Insights browser SDK loader config (enabled, connectionString) |
Set OTEL_EXPORTER_OTLP_ENDPOINT and OTLP export is enabled automatically — no code changes needed. Signal-specific variables (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, etc.) override the base endpoint.
See the OpenTelemetry OTLP Exporter specification for the full list.
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
false |
Enable A365 observability. Registers the A365SpanProcessor for baggage/attribute enrichment of downstream exporters (Azure Monitor, OTLP, console). Does not send data to the A365 service on its own — set enableObservabilityExporter for that |
enableObservabilityExporter |
boolean |
false |
Enable the A365 HTTP exporter (Agent365Exporter) to send spans to the A365 observability service. Requires enabled: true. Equivalent to ENABLE_A365_OBSERVABILITY_EXPORTER env var |
tokenResolver |
(agentId, tenantId, authScopes?) => string | Promise<string> |
— | Token resolver for A365 service authentication. When both this and contextualTokenResolver are set, contextualTokenResolver takes precedence |
contextualTokenResolver |
(context: TokenResolverContext) => string | null | Promise<string | null> |
— | Contextual token resolver with rich context including the agentic user ID. Takes precedence over tokenResolver when set |
clusterCategory |
ClusterCategory |
"prod" |
Cluster category for endpoint resolution (local, dev, test, preprod, firstrelease, prod, gov, high, dod, mooncake, ex, rx) |
domainOverride |
string |
— | Override the A365 observability service domain |
authScopes |
string[] |
["api://9b975845-388f-4429-889e-eab1ef63949c/.default"] |
OAuth scopes for A365 service authentication |
observabilityScopeOverride |
string |
— | Single-string scope override (highest precedence). Equivalent to A365_OBSERVABILITY_SCOPES_OVERRIDE env var |
logLevel |
string |
"none" |
A365 internal log level: none, info, warn, error, or pipe-separated combination. Overrides A365_OBSERVABILITY_LOG_LEVEL env var |
useS2SEndpoint |
boolean |
false |
Use the S2S (service-to-service) endpoint path for export |
durableDelivery |
Agent365DurableDeliveryOptions |
enabled with defaults | Enabled-by-default local store-and-replay for retryable A365 HTTP export requests when the A365 HTTP exporter is active. Set durableDelivery.enabled: false to force network-only delivery. Replays use fresh tokens/current routing, records stay in bounded plaintext local storage, and storage-init failures degrade to network-only delivery |
When A365 export is enabled, Microsoft OpenTelemetry defaults to GenAI-focused telemetry. To opt back into non-GenAI auto-instrumentation, set explicit overrides:
useMicrosoftOpenTelemetry({
a365: {
enabled: true,
tokenResolver: (agentId, tenantId) => getToken(agentId, tenantId),
},
instrumentationOptions: {
http: { enabled: true },
azureSdk: { enabled: true },
mongoDb: { enabled: true },
mySql: { enabled: true },
postgreSql: { enabled: true },
redis: { enabled: true },
redis4: { enabled: true },
bunyan: { enabled: true },
winston: { enabled: true },
},
});Hosting middleware is configured separately from a365 exporter options.
To use A365 hosting middleware, attach it to your adapter explicitly.
Use the one-liner helper:
import { configureA365Hosting } from "@microsoft/opentelemetry";
configureA365Hosting(adapter);By default this registers both BaggageMiddleware and OutputLoggingMiddleware.
OutputLoggingMiddleware captures outgoing message content as span attributes. If your responses may contain sensitive content, disable output logging:
configureA365Hosting(adapter, {
enableBaggage: true,
enableOutputLogging: false,
});If you need explicit flags:
configureA365Hosting(adapter, {
enableBaggage: true,
enableOutputLogging: true,
});For previously published package versions that do not include configureA365Hosting, use:
import { ObservabilityHostingManager } from "@microsoft/opentelemetry";
new ObservabilityHostingManager().configure(adapter as unknown as { use(...m: unknown[]): void }, {
enableBaggage: true,
enableOutputLogging: true,
});| Option | Type | Default | Description |
|---|---|---|---|
maxQueueSize |
number |
2048 |
Maximum span queue size before drops occur |
scheduledDelayMilliseconds |
number |
5000 |
Delay (ms) between automatic batch flush attempts |
exporterTimeoutMilliseconds |
number |
90000 |
Maximum time (ms) for the entire export call |
httpRequestTimeoutMilliseconds |
number |
30000 |
HTTP request timeout (ms) when sending spans to A365 service |
maxExportBatchSize |
number |
512 |
Maximum number of spans per export batch |
maxPayloadBytes |
number |
900 * 1024 |
Maximum estimated payload size (bytes) per HTTP chunk |
Durable delivery applies only to the A365 HTTP exporter, so set both a365.enabled: true and
a365.enableObservabilityExporter: true when you use it. Durable delivery is enabled by default;
set durableDelivery.enabled: false to force legacy network-only delivery.
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Durable delivery stays on unless you explicitly disable it |
storageDirectory |
string |
auto | Root directory for durable records. When omitted, the SDK probes LOCALAPPDATA, then TEMP, then os.tmpdir() on Windows and appends Microsoft/A365/otel-durable; on POSIX it probes TMPDIR, then /var/tmp, then /tmp and uses the single a365-otel-durable-<uid> leaf. Every base root then gets a stable per-application app-<hash> child |
maxStorageBytes |
number |
50 * 1024 * 1024 |
Maximum total bytes retained for pending and quarantined durable records |
maxRecordAgeMilliseconds |
number |
2 * 24 * 60 * 60 * 1000 |
Maximum record age before expiry pruning |
replayIntervalMilliseconds |
number |
2 * 60 * 1000 |
Delay between scheduled replay passes |
maxReplayBatchSize |
number |
10 |
Maximum records claimed per replay pass |
leaseDurationMilliseconds |
number |
2 * 60 * 1000 |
How long a claimed replay lease stays active before recovery |
shutdownTimeoutMilliseconds |
number |
10_000 |
Shared graceful-shutdown budget for accepted live exports and admitted durable handoff completion |
tokenResolutionTimeoutMilliseconds |
number |
30_000 |
Timeout for each replay token-resolution attempt |
Operational notes:
- Durable records are stored as plaintext JSON files. On POSIX, the SDK creates owner-only durable
directories/files (
0700/0600). On Windows, the SDK removes inherited ACLs and grants full control only to the current Windows identity and built-in Administrators. ACL hardening failures cause durable storage initialization to fail and the exporter to use network-only delivery. - Durable delivery is enabled by default when the A365 HTTP exporter is active. Set
durableDelivery.enabled: falseto force legacy network-only delivery. - Durable delivery is at-least-once. If a retryable request succeeds immediately before a crash or after replay, duplicates are possible and receivers must be idempotent.
- Every replay attempt and
forceFlush()pass resolves a fresh token and uses the exporter's current cluster/domain routing; durable files do not store bearer tokens or authoritative route metadata. - If durable storage cannot initialize, the exporter logs the error and continues in network-only mode. Successful and non-retryable live sends still complete, but retryable results that cannot be persisted are reported as failures.
- If token resolution returns no token, throws, or times out, live delivery attempts to persist the record for replay and replay releases the claim without extending the shared transmission backoff.
- Storage is bounded by both
maxStorageBytesandmaxRecordAgeMilliseconds; expired records are pruned first, then the oldest retained records are evicted until the new record fits. - Live delivery and replay share the same
Retry-After/ exponential-backoff transmission gate, so a retryable response (401, 408, 429, or 5xx) pauses both immediate sends and replay probes until the gate reopens. - If you run in containers and need records to survive container restarts, rescheduling, or host
restarts outside the current container filesystem, set
storageDirectoryto a protected persistent volume and sizemaxStorageByteswithin your platform's storage quota. - During
shutdownMicrosoftOpenTelemetry(), the exporter first waits up toshutdownTimeoutMillisecondsfor already accepted live exports to settle. When durable delivery is enabled, it also stops replay scheduling immediately, aborts in-flight durable HTTP, and uses the same deadline for already admitted durable handoff completion. Retryable aborted payloads stay on disk for replay on the next process or startup pass; shutdown does not drain the existing spool. If you useAgent365Exporterdirectly, you may callforceFlush()before shutdown for one bounded replay pass. The distro shutdown path does not callexporter.forceFlush(). When durable delivery is disabled—or durable storage never initialized and the exporter stayed network-only—shutdown still drains accepted live exports but does not replay or persist anything.
Example:
useMicrosoftOpenTelemetry({
a365: {
enabled: true,
enableObservabilityExporter: true,
tokenResolver: (agentId, tenantId, authScopes) => getToken(agentId, tenantId, authScopes),
maxQueueSize: 4096,
maxExportBatchSize: 1024,
scheduledDelayMilliseconds: 10000,
httpRequestTimeoutMilliseconds: 15000,
durableDelivery: {
storageDirectory: process.env.A365_DURABLE_STORAGE_DIRECTORY,
maxStorageBytes: 50 * 1024 * 1024,
maxRecordAgeMilliseconds: 2 * 24 * 60 * 60 * 1000,
},
},
});A365 also reads the following environment overrides:
| Environment Variable | Description |
|---|---|
ENABLE_A365_OBSERVABILITY_EXPORTER |
"true" / "false" — secondary toggle for the A365 HTTP exporter when a365 options are provided in code; it does not enable a365.enabled on its own |
A365_OBSERVABILITY_SCOPES_OVERRIDE |
Space-separated list of OAuth scopes |
A365_OBSERVABILITY_DOMAIN_OVERRIDE |
Override service domain |
CLUSTER_CATEGORY |
Override cluster category |
A365_OBSERVABILITY_LOG_LEVEL |
A365 internal logger filter level (none, info, warn, error, or pipe-delimited combination) — overrides observabilityLogLevel |
A365_PER_REQUEST_MAX_TRACES |
Max buffered traces (default: 1000) |
A365_PER_REQUEST_MAX_SPANS_PER_TRACE |
Max spans per trace (default: 5000) |
A365_PER_REQUEST_MAX_CONCURRENT_EXPORTS |
Max concurrent exports (default: 20) |
A365_PER_REQUEST_FLUSH_GRACE_MS |
Grace period after root span ends (default: 250) |
A365_PER_REQUEST_MAX_TRACE_AGE_MS |
Max trace age before forced flush (default: 1800000) |
A365_OBSERVABILITY_TOKEN_EXCHANGE_TIMEOUT_MS |
Per-attempt timeout (ms) for agentic token exchange; 0 or negative disables (default: 30000) |
import {
useMicrosoftOpenTelemetry,
shutdownMicrosoftOpenTelemetry,
} from "@microsoft/opentelemetry";
import { resourceFromAttributes } from "@opentelemetry/resources";
useMicrosoftOpenTelemetry({
resource: resourceFromAttributes({ "service.name": "my-app" }),
samplingRatio: 0.5,
azureMonitor: {
azureMonitorExporterOptions: {
connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
},
},
});
// On shutdown
await shutdownMicrosoftOpenTelemetry();- Fabric Getting Started — Send telemetry to Microsoft Fabric / Azure Data Explorer via OTLP + OTel Collector
See the samples/ directory for working TypeScript examples covering connection setup, custom metrics, custom traces, sampling, OTLP dual-export, and more.
See CONTRIBUTING.md.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
As this SDK is designed to enable applications to perform data collection which is sent to the Microsoft collection endpoints the following is required to identify our privacy statement.
The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkID=824704. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.
Internal telemetry can be disabled by setting the environment variable APPLICATIONINSIGHTS_STATSBEAT_DISABLED to true.
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft’s Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies.
See SECURITY.md for information on reporting vulnerabilities.