This guide walks you through the steps and resources you'll need to get OpenTelemetry set up with Azure Functions, specifically for TypeScript projects using ECMAScript Modules (ESM).
We're using the latest instrumentation libraries and the shiny new OpenTelemetry JS SDK 2.x.
I put this guide together because even though Azure's official OpenTelemetry for functions is in preview, there wasn't current guidance available, especially around the newer SDK configurations and ECMAScript Modules compilation. Of the few examples I found many don't cover the practices required for the SDK 2.x migration.
Reference: Microsoft OpenTelemetry resources for Azure JavaScript Functions: Samples Typescript & ESM and How-to Guide (CommonJS) Legacy SDK.
Understanding the architecture is key to a smooth setup. To simplify the process, consider your function app as having two distinct components:
- Azure Functions Host: This is the runtime that handles triggers (e.g., an HTTP request), manages the function lifecycle, and calls your code. It incorporates its own telemetry system, typically defaulting to the Application Insights pipeline.
- Worker process: This is where your application and function code resides, running in a separate process. The OpenTelemetry SDK you configure will be preloaded to operate within this process.
Enable OpenTelemetry in host.json
To enable OpenTelemetry output from your Functions host, regardless of the language, you must update the host.json file in your project root. Add the "telemetryMode": "OpenTelemetry" element to the root collection:
{
"version": "2.0",
"telemetryMode": "OpenTelemetry",
...
}
Install Dependencies
As of February 2026, the following npm packages are required to get started:
"dependencies": {
"@azure/functions-opentelemetry-instrumentation": "^0.3.0",
"@opentelemetry/auto-instrumentations-node": "^0.68.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/sdk-node": "^0.211.0",
...
}
Configure the Tracer (tracing.ts)
In the specialized Azure Functions environment, the instrumentation code must execute before any other library loads. Therefore, the recommended practice is to dedicate a separate file for the setup code, often named tracing.js, tracing.mjs, or tracing.ts (for TypeScript).
For instance, here is an Example tracing.ts (for ESM compilation):
The OpenTelemetry community has standardized on NodeSDK in version 2.x as the primary method for initializing the SDK, offering a more "unified" experience.
// Handle the CJS/ESM interop for the Azure package
import FunctionInstrumentation from "@azure/functions-opentelemetry-instrumentation";
const { AzureFunctionsInstrumentation } = FunctionInstrumentation;
import { NodeSDK } from "@opentelemetry/sdk-node";
import { BatchSpanProcessor, SimpleSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-node";
import { getNodeAutoInstrumentations, getResourceDetectors } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const otlpExporter = new OTLPTraceExporter();
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
resourceDetectors: getResourceDetectors(), // 1. Detect worker resources (like process ID, host name, etc.)
spanProcessors: [
new BatchSpanProcessor(otlpExporter),
new SimpleSpanProcessor(new ConsoleSpanExporter()), //For debug locally
],
instrumentations: [
new AzureFunctionsInstrumentation(),
getNodeAutoInstrumentations({
"@opentelemetry/instrumentation-fs": {
enabled: false,
},
"@opentelemetry/instrumentation-http": {
enabled: true,
// AzureFunctionsInstrumentation handles this
disableIncomingRequestInstrumentation: true,
// Keep outgoing request instrumentation for HTTP clients
disableOutgoingRequestInstrumentation: false,
},
}),
],
});
sdk.start();
To ensure clean, end-to-end telemetry originating from your code rather than the host's observation, you should disable the Worker OpenTelemetry capabilities in your application setup if you are using a custom Log provider. This is done by setting your app capabilities: { WorkerOpenTelemetryEnabled: false }. This action switches the host into a "background" mode for that worker's telemetry.
Execution and Pre-loading
Do not directly import the SDK into your function file. To guarantee instrumentation runs first, use a pre-load mechanism:
- Node.js Runtime Pre-loading: Use the
NODE_OPTIONSenvironment variable with the--require(CJS) or--import(ESM) flag to instruct the Node.js runtime to pre-load the module first. - ESM Loader Hook: For projects using JavaScript as ECMAScript Modules (ESM) or code compiled to ESM, a loader hook is essential for proper instrumentation patching. Pass the ESM instrumentation hook directly to the node binary:
--experimental-loader=@opentelemetry/instrumentation/hook.mjs
(Reference OpenTelemetry documentation on ESM support: https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/esm-support.md)
Programmatic Wrapping: In single-entry functions, you can explicitly pass the @azure/functions module into the instrumentation library. This allows the instrumentation to wrap the app object programmatically if the standard monkey patching may be initialized "too late."
Configure application settings
Azure Functions supports exporting OpenTelemetry data to any OTLP-compliant endpoint. The specific environment variables you need to configure.
Configure the following application settings aka local.settings.json:
NODE_OPTIONS: "--experimental-loader=@opentelemetry/instrumentation/hook.mjs --import=./dist/tracer.js"
OTEL_EXPORTER_OTLP_ENDPOINT: "https://otlp.nr-data.net:4318"
OTEL_EXPORTER_OTLP_HEADERS: "api-key=YOUR_INGEST_KEY"
OTEL_SERVICE_NAME: "service.name"
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: “delta”
Important Note: If you intend to send OpenTelemetry data only to the New Relic OTLP endpoint, review your application settings and consider removing the default APPLICATIONINSIGHTS_CONNECTION_STRING environment variable. endpoint or you may incur additional costs through application insights.
Once everything is in place you should start to see your telemetry data on the backend. Here is a high-level breakdown of how the OpenTelemetry instrumentation is distributed between the Azure Functions Host and your Node.js Worker process.
Sampling your data
Keep in mind that sampling is not enabled by default. For Application Insights live metrics, the default is often around 5 requests per second. However, when configuring OpenTelemetry exporters directly, the sampling behavior is different and needs explicit configuration to suit your needs.
With “telemetryMode” set to “opentelemetry” in an Azure Function, sampling at the host level is controlled by the sampling decision made in your worker process. The Functions Host does not apply its own independent sampling rules; instead, it respects the sampling decision propagated from your OpenTelemetry SDK.
For further sampling configuration, refer to the application settings, specifically local.settings.json. You can find the accepted values for OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG in the general SDK configuration documentation.
Recommend: Disabling Azure Functions Console Logging
The Azure Functions runtime's default console logging can intercept application logs, preventing them from being decorated with trace context before flowing to tools like New Relic. Reviewing host.json Logging Configuration
Similar configuration for console logging should be disabled:
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
},
},
"console": {
"isEnabled": true,
"DisableColors": true
},
"logLevel": {
"default": "Information",
"Function": "Information",
"Host.Aggregator": "Information",
"Host.Results": "Information"
}
The Logging Conflict: When "console": { "isEnabled": true } is set in host.json, the Functions runtime:
- Redirects
Console.WriteLine/Writecalls through its own logging pipeline. - Routes console output through
ILoggerfor processing, formatting, and filtering. - Applies a
logLevelfilter before any output reaches the actual console.
To ensure your application logs are decorated with trace context and successfully flow into New Relic, this console logging redirection must be disabled.
이 블로그에 표현된 견해는 저자의 견해이며 반드시 New Relic의 견해를 반영하는 것은 아닙니다. 저자가 제공하는 모든 솔루션은 환경에 따라 다르며 New Relic에서 제공하는 상용 솔루션이나 지원의 일부가 아닙니다. 이 블로그 게시물과 관련된 질문 및 지원이 필요한 경우 Explorers Hub(discuss.newrelic.com)에서만 참여하십시오. 이 블로그에는 타사 사이트의 콘텐츠에 대한 링크가 포함될 수 있습니다. 이러한 링크를 제공함으로써 New Relic은 해당 사이트에서 사용할 수 있는 정보, 보기 또는 제품을 채택, 보증, 승인 또는 보증하지 않습니다.