Zero-Code Instrumentation of .NET using OpenTelemetry

Instrument .NET applications using zero-code OpenTelemetry to automatically capture telemetry data from .NET Framework and .NET Core applications without code changes.

Overview

Zero-code instrumentation for .NET with OpenTelemetry provides automatic telemetry collection from .NET Framework and .NET applications without requiring any code modifications. The OpenTelemetry .NET Automatic Instrumentation works across all officially supported operating systems and versions of .NET, including .NET Framework 4.6.2+, .NET Core, and modern .NET. It automatically instruments popular libraries and frameworks, capturing traces, metrics, and logs from HTTP servers, database operations, message queues, and more.

How It Works

The .NET automatic instrumentation works by:

  1. Profiler API: Uses the .NET CLR Profiling API to inject instrumentation code at runtime
  2. Assembly Rewriting: Modifies assemblies as they’re loaded to add telemetry collection
  3. Automatic Discovery: Detects supported libraries and frameworks automatically
  4. Export via OTLP: Sends telemetry to configured backends like Edge Delta

This approach requires no source code changes and works with both .NET Framework and modern .NET applications.

Prerequisites

Ensure that you have:

  • .NET Runtime:
    • .NET Framework 4.6.2 or later
    • .NET 6.0 or later (all supported versions)
  • Operating System: Windows, Linux, or macOS
  • Architecture: x86, AMD64 (x86-64), or ARM64
  • Permissions:
    • Administrator rights (Windows)
    • Sufficient permissions to set environment variables
  • Edge Delta: An account and pipeline with an OTLP input node

Installation

The installation process differs by operating system.

Linux and macOS Installation

Step 1: Download the Installation Script

curl -sSfL https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.sh -O

Step 2: Run the Installer

sh ./otel-dotnet-auto-install.sh

The script installs the instrumentation to $HOME/.otel-dotnet-auto/.

Step 3: Make the Instrument Script Executable

chmod +x $HOME/.otel-dotnet-auto/instrument.sh

Step 4: Source the Environment

For each shell session where you want instrumentation:

. $HOME/.otel-dotnet-auto/instrument.sh

macOS Additional Requirement:

macOS requires coreutils:

brew install coreutils

Windows Installation (PowerShell)

Prerequisites: PowerShell 5.1 or later

Step 1: Download and Import the Module

$module_url = "https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/OpenTelemetry.DotNet.Auto.psm1"
$download_path = Join-Path $env:temp "OpenTelemetry.DotNet.Auto.psm1"

Invoke-WebRequest -Uri $module_url -OutFile $download_path -UseBasicParsing
Import-Module $download_path

Step 2: Install the Core Components

Install-OpenTelemetryCore

This installs the instrumentation to $env:ProgramFiles\OpenTelemetry .NET AutoInstrumentation\.

Step 3: Register for Current Session

Register-OpenTelemetryForCurrentSession -OTelServiceName "MyServiceDisplayName"

This sets the required environment variables for the current PowerShell session.

Configuration

Configure the instrumentation entirely through environment variables.

Required Environment Variables

VariableDescriptionExample
OTEL_SERVICE_NAMEService name for identificationmy-dotnet-service
OTEL_EXPORTER_OTLP_ENDPOINTOTLP endpoint for all signalshttp://localhost:4318

Common Configuration Variables

VariableDescriptionDefault
OTEL_TRACES_EXPORTERTrace exporter typeotlp
OTEL_METRICS_EXPORTERMetrics exporter typeotlp
OTEL_LOGS_EXPORTERLogs exporter typeotlp
OTEL_EXPORTER_OTLP_PROTOCOLOTLP protocolhttp/protobuf
OTEL_LOG_LEVELLogging verbosityinfo
OTEL_RESOURCE_ATTRIBUTESAdditional resource attributesSee below

Endpoint Configuration for Edge Delta

Kubernetes Environment:

export OTEL_EXPORTER_OTLP_ENDPOINT="http://ed-data-supply-svc:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"

Non-Kubernetes (Windows):

$env:OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
$env:OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"

Non-Kubernetes (Linux/macOS):

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"

Using gRPC (port 4317):

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"

Signal-Specific Endpoints:

If you need different endpoints for each signal:

export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4318/v1/traces"
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="http://localhost:4318/v1/metrics"
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="http://localhost:4318/v1/logs"

See Ingest Data from an OTLP Source for detailed Edge Delta configuration.

Resource Attributes

Add metadata to all telemetry:

export OTEL_RESOURCE_ATTRIBUTES="service.version=1.0.0,deployment.environment=production,service.namespace=my-team"

Resource Detectors

Control which resource detectors are enabled:

export OTEL_DOTNET_AUTO_RESOURCE_DETECTOR_ENABLED="true"

Available detectors (automatically enabled):

  • Azure App Service
  • Container (Docker/Kubernetes)
  • Host
  • Operating System
  • Process
  • Process Runtime

Controlling Instrumentation

Disable All Instrumentation:

export OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED="false"

Disable Specific Signals:

export OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED="false"
export OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED="false"
export OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED="false"

Disable Specific Instrumentations:

# Disable HTTP client instrumentation
export OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_ENABLED="false"

# Disable SQL Client instrumentation
export OTEL_DOTNET_AUTO_TRACES_SQLCLIENT_INSTRUMENTATION_ENABLED="false"

Running Your Application

Linux and macOS

Standard Application:

# Source the environment (once per session)
. $HOME/.otel-dotnet-auto/instrument.sh

# Set configuration
export OTEL_SERVICE_NAME="my-dotnet-service"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

# Run your application
./MyApp

Using dotnet run:

. $HOME/.otel-dotnet-auto/instrument.sh
export OTEL_SERVICE_NAME="my-web-api"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

dotnet run

Windows - Standard Applications

PowerShell:

# Import module
Import-Module "C:\Program Files\OpenTelemetry .NET AutoInstrumentation\OpenTelemetry.DotNet.Auto.psm1"

# Register for current session
Register-OpenTelemetryForCurrentSession -OTelServiceName "MyService"

# Set additional configuration
$env:OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

# Run your application
.\MyApp.exe

Windows - IIS Applications

For ASP.NET and ASP.NET Core applications hosted in IIS:

Step 1: Install Core Components

Import-Module "OpenTelemetry.DotNet.Auto.psm1"
Install-OpenTelemetryCore

Step 2: Register for IIS

Register-OpenTelemetryForIIS

This configures all IIS application pools to use the instrumentation.

Step 3: Configure via Web.config

For .NET Framework applications, add to Web.config:

<configuration>
  <appSettings>
    <add key="OTEL_SERVICE_NAME" value="my-web-app" />
    <add key="OTEL_EXPORTER_OTLP_ENDPOINT" value="http://localhost:4318" />
    <add key="OTEL_EXPORTER_OTLP_PROTOCOL" value="http/protobuf" />
    <add key="OTEL_TRACES_EXPORTER" value="otlp" />
    <add key="OTEL_METRICS_EXPORTER" value="otlp" />
  </appSettings>
</configuration>

For ASP.NET Core applications in IIS, use environment variables in web.config:

<configuration>
  <system.webServer>
    <aspNetCore processPath="dotnet" arguments=".\MyApp.dll">
      <environmentVariables>
        <environmentVariable name="OTEL_SERVICE_NAME" value="my-web-app" />
        <environmentVariable name="OTEL_EXPORTER_OTLP_ENDPOINT" value="http://localhost:4318" />
        <environmentVariable name="OTEL_EXPORTER_OTLP_PROTOCOL" value="http/protobuf" />
      </environmentVariables>
    </aspNetCore>
  </system.webServer>
</configuration>

Step 4: Restart IIS

iisreset

Auto-Generated Service Names:

IIS applications get automatic service names in the format: SiteName\VirtualDirectoryPath

For example:

  • Default Web SiteDefault Web Site
  • Default Web Site\apiDefault Web Site\api

Override with OTEL_SERVICE_NAME in configuration.

Windows Services

For Windows Services:

Step 1: Install and Register

Import-Module "OpenTelemetry.DotNet.Auto.psm1"
Install-OpenTelemetryCore
Register-OpenTelemetryForWindowsService -WindowsServiceName "MyServiceName" -OTelServiceName "MyDisplayName"

Step 2: Configure via Registry

Environment variables are stored in the Windows Registry at:

HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName>

Add a REG_MULTI_SZ value named Environment with entries like:

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

Each line is a separate entry in the multi-string value.

Step 3: Restart the Service

Restart-Service -Name "MyServiceName"

Docker Deployment

Dockerfile for .NET Application:

FROM mcr.microsoft.com/dotnet/aspnet:8.0

WORKDIR /app

# Install OpenTelemetry automatic instrumentation
RUN apt-get update && \
    apt-get install -y curl && \
    curl -sSfL https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.sh -O && \
    sh ./otel-dotnet-auto-install.sh && \
    rm ./otel-dotnet-auto-install.sh

# Copy application
COPY publish/ .

# Set environment variables for instrumentation
ENV OTEL_SERVICE_NAME="my-dotnet-service"
ENV OTEL_EXPORTER_OTLP_ENDPOINT="http://ed-data-supply-svc:4318"
ENV OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
ENV OTEL_TRACES_EXPORTER="otlp"
ENV OTEL_METRICS_EXPORTER="otlp"
ENV OTEL_LOG_LEVEL="info"

# Source instrumentation environment and run app
CMD . /root/.otel-dotnet-auto/instrument.sh && dotnet MyApp.dll

docker-compose.yml:

version: '3.8'

services:
  dotnet-app:
    build: .
    environment:
      - OTEL_SERVICE_NAME=my-dotnet-service
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://ed-data-supply-svc:4318
      - OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
      - OTEL_TRACES_EXPORTER=otlp
      - OTEL_METRICS_EXPORTER=otlp
      - OTEL_LOG_LEVEL=info
      - OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
    ports:
      - "8080:8080"

Kubernetes Deployment

Deployment Manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dotnet-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dotnet-app
  template:
    metadata:
      labels:
        app: dotnet-app
    spec:
      initContainers:
      # Install instrumentation in init container
      - name: otel-instrumentation-installer
        image: busybox:latest
        command:
        - sh
        - -c
        - |
          wget -O /otel-install/otel-dotnet-auto-install.sh https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.sh
          chmod +x /otel-install/otel-dotnet-auto-install.sh
          sh /otel-install/otel-dotnet-auto-install.sh
          cp -r /root/.otel-dotnet-auto /otel-install/          
        volumeMounts:
        - name: otel-install
          mountPath: /otel-install

      containers:
      - name: app
        image: myapp:latest
        env:
        # OpenTelemetry Configuration
        - name: OTEL_SERVICE_NAME
          value: "my-dotnet-service"
        - name: OTEL_EXPORTER_OTLP_ENDPOINT
          value: "http://ed-data-supply-svc.default.svc.cluster.local:4318"
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: "http/protobuf"
        - name: OTEL_TRACES_EXPORTER
          value: "otlp"
        - name: OTEL_METRICS_EXPORTER
          value: "otlp"
        - name: OTEL_LOG_LEVEL
          value: "info"
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "service.version=1.0.0,deployment.environment=production,k8s.namespace.name=default"

        # Instrumentation paths
        - name: CORECLR_ENABLE_PROFILING
          value: "1"
        - name: CORECLR_PROFILER
          value: "{918728DD-259F-4A6A-AC2B-B85E1B658318}"
        - name: CORECLR_PROFILER_PATH
          value: "/otel-dotnet-auto/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so"
        - name: DOTNET_ADDITIONAL_DEPS
          value: "/otel-dotnet-auto/AdditionalDeps"
        - name: DOTNET_SHARED_STORE
          value: "/otel-dotnet-auto/store"
        - name: DOTNET_STARTUP_HOOKS
          value: "/otel-dotnet-auto/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll"
        - name: OTEL_DOTNET_AUTO_HOME
          value: "/otel-dotnet-auto"

        volumeMounts:
        - name: otel-install
          mountPath: /otel-dotnet-auto
          subPath: .otel-dotnet-auto

      volumes:
      - name: otel-install
        emptyDir: {}

Alternative: Use OpenTelemetry Operator

For easier deployment, use the OpenTelemetry Operator which automatically injects instrumentation into pods via annotations.

Supported Instrumentation

The automatic instrumentation supports a wide variety of libraries:

Traces (24 Libraries)

Web Frameworks:

  • ASP.NET (.NET Framework 4.6.2+)
  • ASP.NET Core (.NET 6+)

HTTP Clients:

  • HttpClient
  • WCF (Windows Communication Foundation)

Databases:

  • Microsoft.Data.SqlClient
  • System.Data.SqlClient
  • Npgsql (PostgreSQL)
  • MySql.Data
  • MySqlConnector
  • MongoDB.Driver
  • Oracle.ManagedDataAccess
  • Elasticsearch.Net
  • Elastic.Transport

Message Queues:

  • RabbitMQ.Client
  • MassTransit
  • NServiceBus
  • Kafka (Confluent.Kafka)

Cloud SDKs:

  • Azure SDK

Other:

  • GraphQL
  • Quartz.NET (job scheduling)
  • StackExchange.Redis
  • gRPC.Net.Client

Metrics (8 Libraries)

  • ASP.NET (.NET Framework)
  • ASP.NET Core
  • HttpClient
  • .NET Runtime metrics
  • Npgsql
  • NServiceBus
  • System.Data.SqlClient
  • Process metrics

Logs (2 Libraries)

  • Microsoft.Extensions.Logging (.NET 9.0+)
  • log4net (2.0.13 - 4.0.0)

All instrumentations are enabled by default and can be selectively disabled via environment variables.

Sampling Configuration

Control trace data volume using sampling:

Ratio-Based Sampling

export OTEL_TRACES_SAMPLER="traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"  # Sample 10%
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"  # Sample 10% of root traces

Always Sample (Development)

export OTEL_TRACES_SAMPLER="always_on"

Verifying Instrumentation

Enable Console Exporter for Testing

Test instrumentation locally:

export OTEL_TRACES_EXPORTER="console"
export OTEL_METRICS_EXPORTER="console"
export OTEL_LOGS_EXPORTER="console"

Check Logs

Set debug logging:

export OTEL_LOG_LEVEL="debug"

Look for messages like:

[OpenTelemetry.AutoInstrumentation] Initializing...
[OpenTelemetry.AutoInstrumentation] Loaded instrumentation: AspNetCore
[OpenTelemetry.AutoInstrumentation] Loaded instrumentation: HttpClient
[OpenTelemetry.AutoInstrumentation] Loaded instrumentation: SqlClient

Verify in Edge Delta

  1. Log into Edge Delta
  2. Navigate to your OTLP input node
  3. Check for traces with your service name
  4. Verify spans from instrumented libraries

Troubleshooting

Instrumentation Not Loading

Windows - Check Installation:

Test-Path "$env:ProgramFiles\OpenTelemetry .NET AutoInstrumentation"

Linux/macOS - Check Installation:

ls -la $HOME/.otel-dotnet-auto/

Verify Environment Variables:

# Linux/macOS
env | grep OTEL

# Windows PowerShell
gci env: | Where-Object { $_.Name -like "OTEL*" }

IIS Applications Not Instrumented

Check IIS Registration:

# Should show OpenTelemetry environment variables
reg query "HKLM\SYSTEM\CurrentControlSet\Services\WAS\Environment"

Restart IIS:

iisreset

Check Application Pool Identity: Ensure the application pool has permissions to access the instrumentation files.

Review Event Viewer: Check Windows Event Viewer → Application logs for .NET runtime errors.

No Telemetry Data

Test OTLP Endpoint:

# Windows
Test-NetConnection -ComputerName localhost -Port 4318

# Linux
curl -X POST http://localhost:4318/v1/traces

Enable Debug Logging:

export OTEL_LOG_LEVEL="debug"

Check Firewall: Ensure port 4318/4317 is open for outbound connections.

Performance Issues

High CPU Usage:

  • Reduce sampling rate
  • Disable unnecessary instrumentations
  • Set appropriate log level (warn or error)

Memory Leaks:

  • Check for the latest version of the instrumentation
  • Report issues to the OpenTelemetry .NET team

Slow Application Startup:

  • This is expected as instrumentation initializes
  • Consider warm-up requests in load balancers

Uninstallation

Linux and macOS

No action needed - instrumentation only affects the current shell session.

To remove files:

rm -rf $HOME/.otel-dotnet-auto/

Windows

Import-Module "OpenTelemetry.DotNet.Auto.psm1"

# Unregister from IIS
Unregister-OpenTelemetryForIIS

# Unregister from Windows Services
Unregister-OpenTelemetryForWindowsService -WindowsServiceName "ServiceName"

# Uninstall core components
Uninstall-OpenTelemetryCore

Best Practices

  1. Set Service Names: Always explicitly set OTEL_SERVICE_NAME for clarity.

  2. Use Resource Attributes: Include version and environment information:

    OTEL_RESOURCE_ATTRIBUTES="service.version=1.2.3,deployment.environment=production"
    
  3. Configure Sampling: Use sampling in production to control costs:

    OTEL_TRACES_SAMPLER="parentbased_traceidratio"
    OTEL_TRACES_SAMPLER_ARG="0.1"
    
  4. Test Locally First: Use console exporters for local testing before deploying.

  5. Monitor Performance: Watch application performance after enabling instrumentation.

  6. Update Regularly: Keep the instrumentation package updated for bug fixes and new features.

  7. Use Configuration Files: For IIS applications, use Web.config for maintainable configuration.

  8. Secure Credentials: Never hardcode credentials in configuration files; use environment variables or secure stores.

Next Steps

For more information on OpenTelemetry .NET automatic instrumentation, visit the official documentation and GitHub repository.