APM Tips

Application Insights

Telemetry initializers in Application Insights, and their OpenTelemetry equivalents

Updated · 6 min read · By APM Tips editorial team

A telemetry initializer is the classic SDK 2.x hook that runs on every telemetry item to add or correct properties, such as a custom dimension or the cloud role name; a telemetry processor runs after it and can drop items. SDK 3.x and the OpenTelemetry distros remove both. Per-item values move to span and log record processors (BaseProcessor<Activity> in .NET), values that describe the whole service move to resource attributes such as service.name, and a fixed key on every item can go in GlobalProperties.

Almost every Application Insights setup ends up with an initializer: to name the service on the Application Map, to stamp a tenant ID on every request, or to mark 404s on one route as successful. That makes initializers one of the most common things to migrate, and the one with the least obvious replacement.

Initializers and processors in the classic SDK

The two hooks overlap, and Microsoft’s own summary of the difference is worth keeping:

  • Initializers always run before processors, so a processor sees an initializer’s changes.
  • All registered initializers are called for every telemetry item. For processors, the SDK only guarantees the first one runs; each decides whether to call the next.
  • Initializers may be called more than once for the same item. By convention they don’t overwrite a property that’s already set.
  • Processors can replace or discard an item. Initializers can’t.

So: initializers to enrich, processors to filter. A filtering processor skews counts in a way sampling doesn’t; the sampling guide explains why.

Classic SDK 2.x

The .NET Classic API SDK 2.x is deprecated and retires on March 31, 2027; the Node.js 2.x SDK is already retired. This is the API most existing initializers are written against.

Write an initializer

This initializer adds a custom dimension and sets the cloud role name, without overwriting values another component set first:

using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;

public class ServiceInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        if (string.IsNullOrEmpty(telemetry.Context.Cloud.RoleName))
        {
            telemetry.Context.Cloud.RoleName = "orders-api";
        }

        if (telemetry is ISupportProperties item && !item.Properties.ContainsKey("tenant"))
        {
            item.Properties["tenant"] = TenantContext.Current?.Id ?? "none";
        }
    }
}

Register it

In ASP.NET Core and Worker Service, add it to dependency injection; ApplicationInsights.config and TelemetryConfiguration.Active aren’t valid there:

builder.Services.AddApplicationInsightsTelemetry();
builder.Services.AddSingleton<ITelemetryInitializer, ServiceInitializer>();

In classic ASP.NET, list it under <TelemetryInitializers> in ApplicationInsights.config with its full type and assembly name, or add it to TelemetryConfiguration.Active.TelemetryInitializers in Application_Start. The ApplicationInsights.config guide covers the file.

Where context properties end up

Initializers mostly write to telemetry.Context. Each context property becomes a column in the classic schema:

Set in codeColumn (Application Insights)Column (workspace)
Context.Cloud.RoleName / RoleInstancecloud_RoleName / cloud_RoleInstanceAppRoleName / AppRoleInstance
Context.Component.Versionapplication_VersionAppVersion
Context.Operation.Nameoperation_NameOperationName
Context.User.Id, AuthenticatedUserIduser_Id, user_AuthenticatedIdUserId, UserAuthenticatedId
Context.Session.Idsession_IdSessionId
Context.Location.Ipclient_IPClientIP
Properties[...], GlobalProperties[...]customDimensionsProperties

The built-in web initializers fill most of these for you: OperationNameTelemetryInitializer sets the operation name from the MVC controller and action (how requests are named), UserTelemetryInitializer and SessionTelemetryInitializer read the ai_user and ai_session cookies (users and sessions in API apps), and ClientIpHeaderTelemetryInitializer reads X-Forwarded-For (client IP and geolocation).

Pitfalls

  • It runs on every item. Requests, dependencies, traces, exceptions, events: keep the code allocation-free and fast, and type-check before casting.
  • When it throws, the property silently goes missing. The diagnostics module reports initializer exceptions as trace telemetry in the resource itself, which is where to look when a dimension disappears.
  • Don’t store per-request state in shared fields. An initializer is a singleton; read request data from the current context, not from instance fields.
  • Node.js used different hooks. The retired Node.js 2.x SDK sets defaultClient.commonProperties for a property on every event, context.tags["ai.cloud.role"] for the role name, and addTelemetryProcessor, which drops an item when it returns false.

Today: SDK 3.x and the Microsoft OpenTelemetry Distro

ITelemetryInitializer, ITelemetryProcessor and all the built-in initializers are gone in SDK 3.x; Microsoft’s guidance is to migrate custom ones to OpenTelemetry processors. The built-ins are handled internally: operation names come from OpenTelemetry instrumentation, role name and instance from resource detectors. Start every migration with one question: is this value per service or per item?

Per service: resource attributes

The role name, role instance and version describe the process, so they belong in the OpenTelemetry resource. The distros map service.namespace plus service.name to the cloud role name (falling back to service.name) and service.instance.id to the role instance. With no code at all:

export OTEL_SERVICE_NAME="orders-api"
export OTEL_RESOURCE_ATTRIBUTES="service.namespace=shop,service.instance.id=orders-01"

OTEL_SERVICE_NAME wins over a service.name inside OTEL_RESOURCE_ATTRIBUTES. In SDK 3.x for ASP.NET Core and Worker Service, set the same values on the tracer and logger providers’ resource:

builder.Services.AddApplicationInsightsTelemetry();
builder.Services.ConfigureOpenTelemetryTracerProvider((sp, tracing) =>
    tracing.ConfigureResource(r => r.AddService(
        serviceName: "orders-api",
        serviceInstanceId: Environment.MachineName,
        serviceVersion: "1.4.2")));

Microsoft’s migration guidance shows the identical call on ConfigureOpenTelemetryLoggerProvider, so logs carry the same role. One behaviour change to know: other attributes you put on the resource go to an _APPRESOURCEPREVIEW_ metric, not onto every item. For a key on every item, use one of the next two options.

Per item: a span processor

A processor that runs when each span ends replaces the enriching initializer. With the Microsoft OpenTelemetry Distro, register it before the distro:

using System.Diagnostics;
using Microsoft.OpenTelemetry;
using OpenTelemetry;
using OpenTelemetry.Trace;

builder.Services.ConfigureOpenTelemetryTracerProvider((sp, tracing) =>
    tracing.AddProcessor(new TenantProcessor()));
builder.Services.AddOpenTelemetry().UseMicrosoftOpenTelemetry(o => o.Exporters = ExportTarget.AzureMonitor);

public class TenantProcessor : BaseProcessor<Activity>
{
    public override void OnEnd(Activity activity)
    {
        activity.SetTag("tenant", TenantContext.Current?.Id ?? "none");
    }
}

Span attributes become customDimensions on the request, dependency, trace or exception they belong to. The same processor class works with SDK 3.x: register it with ConfigureOpenTelemetryTracerProvider in ASP.NET Core, using the instance form AddProcessor(new TenantProcessor()), because the generic AddProcessor<T>() can’t be used there, or with TelemetryConfiguration.ConfigureOpenTelemetryBuilder in non-DI apps.

Logs, exceptions and custom events travel through the log pipeline, so they need a BaseProcessor<LogRecord> registered on the logger provider if they should carry the same key. For a constant key on everything your own TelemetryClient sends, SDK 3.x keeps client.Context.GlobalProperties["key"] = "value".

Per item: dropping instead of enriching

The 2.x filtering processor also has an OpenTelemetry form. Microsoft’s SDK 3.x examples drop a span by clearing its Recorded flag when it ends, which keeps it out of the export:

public class HealthCheckFilter : BaseProcessor<Activity>
{
    public override void OnEnd(Activity activity)
    {
        if (activity.DisplayName?.Contains("/health") == true)
        {
            activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
        }
    }
}

For incoming requests, the ASP.NET Core instrumentation’s own Filter option is simpler: it decides per request whether to trace it at all. Either way, the item is gone without a trace, so keep filters for traffic you never want to count.

Node.js, Python and Java

The Node.js distro takes processors in its options (spanProcessors and logRecordProcessors), each an object with onStart, onEnd, forceFlush and shutdown; Python takes span_processors=[...] with a subclass of SpanProcessor. Java is different: 2.x initializers and processors don’t run with the 3.x agent, and their jobs move to agent configuration (custom dimensions, sampling overrides) or the agent’s telemetry processors, in preview.

Migration map

Classic 2.xSDK 3.xMicrosoft OpenTelemetry Distro
Initializer setting role name/instanceResource (AddService) or Context.Cloud on a manual clientOTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES or ConfigureResource
Initializer adding a propertyActivity and log processors; GlobalPropertiesBaseProcessor<Activity>, log record processor
Initializer marking 4xx as successProcessor setting ActivityStatusCode.OkSame
Processor dropping itemsProcessor clearing the Recorded flagSame, or instrumentation Filter options
commonProperties (Node.js)applicationinsights@^3spanProcessors, logRecordProcessors

Troubleshooting

SymptomLikely cause
Dimension present on requests but not on logsOnly a span processor was registered; add a log record processor
Role name ignored in SDK 3.xContext.Cloud.RoleName set after the host built the SDK; use the resource
Custom resource attribute not in customDimensionsResource attributes go to _APPRESOURCEPREVIEW_; use a processor
2.x initializer compiles against 3.xIt doesn’t: the interface was removed; mixing 2.x and 3.x packages isn’t supported
Dimension missing intermittently in 2.xThe initializer threw; look for its exception in the resource’s traces

Queries for this topic

From the KQL library: explained queries for the Application Insights tables, ready to copy.

Frequently asked questions

What is the difference between a telemetry initializer and a telemetry processor?

In the classic SDK, initializers enrich: every registered initializer runs on every item, before any processor. Processors form a chain and can modify, replace or drop an item by not calling the next one. Microsoft recommends initializers for adding properties and processors for filtering.

How do I add a custom dimension to every item with SDK 3.x?

Set it in TelemetryClient.Context.GlobalProperties for values your own TelemetryClient sends, or register an OpenTelemetry activity processor (and a log processor) that calls SetTag on every span. Values set through resource attributes go to an _APPRESOURCEPREVIEW_ metric instead of each item.

Do my 2.x telemetry initializers still run with the Java 3.x agent?

No. Microsoft states that 2.x TelemetryInitializers and TelemetryProcessors don't run with the 3.x Java agent. Use custom dimensions, inherited attributes or sampling overrides in the agent configuration, or telemetry processors (preview).

Sources, checked

  1. Monitor .NET and Node.js applications with Application Insights (Classic API 2.x)learn.microsoft.com · checked
  2. Add and modify OpenTelemetry in Application Insightslearn.microsoft.com · checked
  3. Configure OpenTelemetry in Application Insights (cloud role name and instance)learn.microsoft.com · checked
  4. Migrate Application Insights Classic API SDKs to OpenTelemetrylearn.microsoft.com · checked
  5. Migration guidance: Application Insights 2.x to 3.xgithub.com · checked
  6. Application Insights .NET SDK concepts (activity and log processors)github.com · checked
  7. Customizing the Microsoft OpenTelemetry Distro (.NET)github.com · checked
  8. General SDK configuration (OpenTelemetry)opentelemetry.io · checked