ASP.NET on IIS (.NET Framework)
Instrument ASP.NET applications on IIS with OpenTelemetry to send traces, metrics, and logs to Last9
Use the OpenTelemetry .NET Automatic Instrumentation agent to instrument ASP.NET applications running on IIS and send telemetry data to Last9. The agent uses the .NET CLR profiler to capture traces from incoming HTTP requests, outbound HTTP calls, SQL queries, and WCF services.
What Gets Auto-Instrumented
| Library | What’s captured |
|---|---|
| ASP.NET / ASP.NET MVC | Incoming HTTP request spans with method, URL, status code, duration |
| System.Net.Http / WebClient | Outbound HTTP call spans |
| ADO.NET (System.Data) | SQL query spans with db.statement |
| WCF (client) | Service call spans |
Prerequisites
- Windows Server 2016 or later with IIS 8.5+
- .NET Framework 4.6.2–4.8 installed
- Administrator access on the server
- IIS app pool name — find it with:
Get-WebApplication | Select ApplicationPool - Last9 Account with OpenTelemetry integration credentials
Installation
-
Download the auto-instrumentation package
Open an elevated PowerShell session (Windows PowerShell 5.1, not PowerShell 7):
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12$version = "v1.14.1"$otelHome = "C:\Program Files\OpenTelemetry .NET AutoInstrumentation"# Download the instrumentation packageInvoke-WebRequest `-Uri "https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/$version/opentelemetry-dotnet-instrumentation-windows.zip" `-OutFile "$env:TEMP\otel-dotnet.zip" -UseBasicParsing# Download the PowerShell management moduleInvoke-WebRequest `-Uri "https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/$version/OpenTelemetry.DotNet.Auto.psm1" `-OutFile "$env:TEMP\OpenTelemetry.DotNet.Auto.psm1" -UseBasicParsing# ExtractNew-Item -Path $otelHome -ItemType Directory -Force | Out-NullExpand-Archive -Path "$env:TEMP\otel-dotnet.zip" -DestinationPath $otelHome -ForceCopy-Item "$env:TEMP\OpenTelemetry.DotNet.Auto.psm1" "$otelHome\" -ForceVerify the installation:
Test-Path "$otelHome\win-x64\OpenTelemetry.AutoInstrumentation.Native.dll"# Should return: True -
Register the profiler for IIS
The official PowerShell module registers the CLR profiler and configures IIS globally:
$env:OTEL_DOTNET_AUTO_HOME = "C:\Program Files\OpenTelemetry .NET AutoInstrumentation"Import-Module "$env:OTEL_DOTNET_AUTO_HOME\OpenTelemetry.DotNet.Auto.psm1"Install-OpenTelemetryCoreRegister-OpenTelemetryForIISThis restarts IIS automatically. The profiler is now enabled for all app pools.
-
Configure your app pool
Set environment variables on the specific app pool to control service name, exporter, and sampling:
Import-Module WebAdministration$poolName = "YourAppPool" # Replace with your app pool nameSet-ItemProperty "IIS:\AppPools\$poolName" -Name environmentVariables -Value @(@{ name = 'OTEL_SERVICE_NAME'; value = 'your-service-name' },@{ name = 'OTEL_RESOURCE_ATTRIBUTES'; value = "deployment.environment=production,host.name=$env:COMPUTERNAME" },@{ name = 'OTEL_TRACES_EXPORTER'; value = 'otlp' },@{ name = 'OTEL_METRICS_EXPORTER'; value = 'otlp' },@{ name = 'OTEL_LOGS_EXPORTER'; value = 'otlp' },@{ name = 'OTEL_EXPORTER_OTLP_ENDPOINT'; value = '$last9_otlp_endpoint' },@{ name = 'OTEL_EXPORTER_OTLP_PROTOCOL'; value = 'http/protobuf' },@{ name = 'OTEL_EXPORTER_OTLP_HEADERS'; value = 'Authorization=$last9_otlp_auth_header' },@{ name = 'OTEL_PROPAGATORS'; value = 'tracecontext,baggage' },@{ name = 'OTEL_TRACES_SAMPLER'; value = 'always_on' })Import-Module WebAdministration$poolName = "YourAppPool" # Replace with your app pool nameSet-ItemProperty "IIS:\AppPools\$poolName" -Name environmentVariables -Value @(@{ name = 'OTEL_SERVICE_NAME'; value = 'your-service-name' },@{ name = 'OTEL_RESOURCE_ATTRIBUTES'; value = "deployment.environment=production,host.name=$env:COMPUTERNAME" },@{ name = 'OTEL_TRACES_EXPORTER'; value = 'otlp' },@{ name = 'OTEL_METRICS_EXPORTER'; value = 'otlp' },@{ name = 'OTEL_LOGS_EXPORTER'; value = 'otlp' },@{ name = 'OTEL_EXPORTER_OTLP_ENDPOINT'; value = 'http://localhost:4318' },@{ name = 'OTEL_EXPORTER_OTLP_PROTOCOL'; value = 'http/protobuf' },@{ name = 'OTEL_PROPAGATORS'; value = 'tracecontext,baggage' },@{ name = 'OTEL_TRACES_SAMPLER'; value = 'always_on' }) -
Restart the app pool
Restart-WebAppPool -Name "YourAppPool" -
Verify instrumentation
Make a request to your application, then verify the profiler loaded:
Get-Process w3wp | ForEach-Object {$modules = $_.Modules | Where-Object { $_.ModuleName -like "*OpenTelemetry*" }if ($modules) {Write-Host "PID $($_.Id): OTel profiler loaded"$modules | Select ModuleName}}You should see
OpenTelemetry.AutoInstrumentation.Nativein the module list.Check Last9 Traces and filter by your
service.nameto see incoming and outgoing HTTP spans.
Distributed Trace Propagation
The auto-instrumentation automatically reads and writes W3C traceparent headers on all HTTP requests. This means:
- Incoming requests with a
traceparentheader (from upstream services, mobile apps, or load balancers) will be continued as part of the same trace - Outgoing requests (via
HttpClient,WebClient, orHttpWebRequest) will automatically include thetraceparentheader, propagating the trace to downstream services
No configuration needed — this works by default when OTEL_PROPAGATORS=tracecontext,baggage is set.
Sampling Configuration
| Sampler | Value | Use case |
|---|---|---|
always_on | Capture all traces | Development, debugging |
always_off | Capture no traces | Disable temporarily |
traceidratio | Sample by percentage | Production (set OTEL_TRACES_SAMPLER_ARG=0.1 for 10%) |
parentbased_traceidratio | Respect parent’s sampling decision, ratio for new traces | Production with distributed tracing |
For production, use parentbased_traceidratio to respect upstream sampling decisions while controlling the sampling rate for traces originating at this service.
Multi-App Pool Configuration
When multiple applications share an app pool, they share the same profiler configuration. To give each application its own service name, configure them in separate app pools with different OTEL_SERVICE_NAME values.
To disable instrumentation for a specific app pool:
Import-Module "$env:OTEL_DOTNET_AUTO_HOME\OpenTelemetry.DotNet.Auto.psm1"Disable-OpenTelemetryForIISAppPool -AppPoolName "PoolToDisable"Troubleshooting
Profiler not loading
Check the .NET Runtime event log:
Get-WinEvent -LogName Application -MaxEvents 20 | Where-Object { $_.ProviderName -eq '.NET Runtime' -and $_.Message -like '*profiler*'} | Format-List TimeCreated, MessageCommon errors:
HRESULT: 0x80004005— Missing Visual C++ Redistributable. Install from MicrosoftCoCreateInstance failed— RunInstall-OpenTelemetryCoreandRegister-OpenTelemetryForIISagain
No spans appearing
-
Verify the app pool has the correct environment variables:
(Get-ItemProperty "IIS:\AppPools\YourAppPool").environmentVariables.Collection | Select name, value -
Check the auto-instrumentation log:
Get-ChildItem "$env:TEMP" -Filter "otel-dotnet-auto-*" |Sort LastWriteTime -Descending | Select -First 1 | Get-Content | Select -Last 30 -
Ensure the app pool is running in 64-bit mode (32-bit mode uses a different profiler path):
(Get-ItemProperty "IIS:\AppPools\YourAppPool").enable32BitAppOnWin64# Should be False for 64-bit
SQL query spans missing
ADO.NET instrumentation works automatically for System.Data.SqlClient. If using a third-party data access layer, ensure it ultimately uses ADO.NET under the hood.
Need Help?
If you encounter any issues or have questions:
- Join our Discord community for real-time support
- Contact our support team at support@last9.io