Version: 0.71.0This documentation is for Spitfire 0.71.0.
Connecting a .NET service with OTel
OpenTelemetry .NET Automatic Instrumentation collects traces, metrics and logs without touching the code. Values and general rules: Connecting services with OTel: overview.
Version table
| Runtime | Version | Note |
|---|---|---|
| .NET 8, 9, 10 | 1.18.0 | Linux, Windows, Docker |
| .NET 6, 7 | 1.9.0 | .NET 6 and 7 support was removed in 1.10.0; stay on 1.9.0 |
| .NET Framework 4.6.2 – 4.8.1 | 1.18.0 | Windows only (IIS or Windows service) |
| .NET Framework 4.6.1 and earlier (4.5 included) | not supported | Upgrade to 4.6.2+ (e.g. 4.8) or take server metrics only: Collector |
Find your version: for .NET dotnet --list-runtimes; for .NET Framework the targetFramework in the
application's web.config or .csproj file.
The commands below say v1.18.0; if your band is .NET 6/7, write v1.9.0 everywhere.
Verified: 2026-11-02. Source: supported runtimes, changelog (.NET 6/7 removed in 1.10.0), 1.9.0 documentation, IIS, Windows service.
What you need
- Spitfire address
https://<spitfire>and OTLP receiver tokensfotlp_… - Tower address
http://<tower>:4318and Collector tokensfcol_… - Protocol
http/protobuf - Service name
<service-name>, version<service-version>, environment<environment> - With a private CA, the CA file
/etc/spitfire-otel/ca.pem(Windows:C:\spitfire-otel\ca.pem) - With a proxy, the Tower and Spitfire in
NO_PROXY - Sampling
parentbased_traceidratio,0.1
Linux (systemd)
- Download and run the install script (the machine needs
curlandunzip):
curl -sSfL -O https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v1.18.0/otel-dotnet-auto-install.sh
sudo OTEL_DOTNET_AUTO_HOME=/opt/otel-dotnet-auto SKIP_RELEASE_VERIFICATION=true sh ./otel-dotnet-auto-install.sh
sudo mkdir -p /opt/spitfire-otel /etc/spitfire-otelIf the machine has the GitHub CLI (gh), delete SKIP_RELEASE_VERIFICATION=true; the
script then verifies the downloaded package's signature. 1.9.0 does not use this variable.
- Create the file
/etc/spitfire-otel/<service-name>.envand paste this:
IS_OTEL_ENABLED=true
OTEL_SERVICE_NAME=<service-name>
OTEL_RESOURCE_ATTRIBUTES=service.version=<service-version>,deployment.environment=<environment>
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://<spitfire>/otlp/v1/traces
OTEL_EXPORTER_OTLP_TRACES_HEADERS=authorization=sfotlp_…
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://<spitfire>/otlp/v1/logs
OTEL_EXPORTER_OTLP_LOGS_HEADERS=authorization=sfotlp_…
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://<tower>:4318/v1/metrics
OTEL_EXPORTER_OTLP_METRICS_HEADERS=authorization=sfcol_…
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1
NO_PROXY=<tower>,<spitfire>
#OTEL_EXPORTER_OTLP_CERTIFICATE=/etc/spitfire-otel/ca.pem- Restrict its permission:
sudo chmod 600 /etc/spitfire-otel/<service-name>.env - Create the file
/opt/spitfire-otel/otel-runand paste this:
#!/bin/sh
# IS_OTEL_ENABLED=true: the profiler and startup hook are loaded; any other value: neither is loaded.
if [ "$IS_OTEL_ENABLED" = "true" ]; then
export OTEL_DOTNET_AUTO_HOME=/opt/otel-dotnet-auto
. /opt/otel-dotnet-auto/instrument.sh
else
export OTEL_SDK_DISABLED=true
fi
exec "$@"- Make it executable:
sudo chmod 755 /opt/spitfire-otel/otel-run - Find the service's current start command:
systemctl cat <unit> | grep ^ExecStart - Open
sudo systemctl edit <unit>and paste this (on the last line, replace the command afterExecStart=with the command from step 6):
[Service]
EnvironmentFile=/etc/spitfire-otel/<service-name>.env
ExecStart=
ExecStart=/opt/spitfire-otel/otel-run /usr/bin/dotnet /srv/app/App.dll- Restart:
sudo systemctl daemon-reload && sudo systemctl restart <unit> - See that it is loaded: log files named with the service's process number appear under
ls /var/log/opentelemetry/dotnet.
If the service starts with DOTNET_EnableDiagnostics=0, the profiler is not loaded on
.NET 8+; remove that line.
Docker
- Put the
otel-runfile (Linux step 4) next to theDockerfile. - Add this to the
Dockerfile(the image needscurlandunzip);/opt/spitfire-otel/otel-rungoes at the start ofENTRYPOINT, the rest is your command:
ADD --chmod=755 https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v1.18.0/otel-dotnet-auto-install.sh /tmp/otel-dotnet-auto-install.sh
RUN OTEL_DOTNET_AUTO_HOME=/opt/otel-dotnet-auto SKIP_RELEASE_VERIFICATION=true sh /tmp/otel-dotnet-auto-install.sh && rm /tmp/otel-dotnet-auto-install.sh
COPY --chmod=755 otel-run /opt/spitfire-otel/otel-run
ENTRYPOINT ["/opt/spitfire-otel/otel-run", "dotnet", "/app/App.dll"]- Save the environment file (Linux step 2) on the Docker machine as
/etc/spitfire-otel/<service-name>.env. - Run:
docker run --env-file /etc/spitfire-otel/<service-name>.env …or, in compose, under the serviceenv_file: /etc/spitfire-otel/<service-name>.env. - When the environment file changes, recreate the container (
restartis not enough):docker compose up -d --force-recreate <service>
Kubernetes
- Prepare the image as in the Docker section.
- Save the environment file (Linux step 2) as
<service-name>.envand make it a Secret:
kubectl create secret generic <service-name>-otel --from-env-file=<service-name>.env \
--dry-run=client -o yaml | kubectl apply -f -- In the Deployment, add this under the container:
envFrom:
- secretRef:
name: <service-name>-otel- Restart:
kubectl rollout restart deployment/<deployment>
Windows: installing (once)
- Open PowerShell as administrator and run:
New-Item -ItemType Directory -Force C:\spitfire-otel | Out-Null
Invoke-WebRequest -Uri https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v1.18.0/OpenTelemetry.DotNet.Auto.psm1 -OutFile C:\spitfire-otel\OpenTelemetry.DotNet.Auto.psm1
Import-Module C:\spitfire-otel\OpenTelemetry.DotNet.Auto.psm1
Install-OpenTelemetryCore -SkipReleaseVerificationIf the machine has the GitHub CLI (gh.exe), delete -SkipReleaseVerification; the
package's signature is then verified. 1.9.0 has no such parameter; delete it.
- If the application runs in IIS go to "Windows: IIS", if it is a Windows service go to "Windows: service".
Windows: IIS
- Run once in an administrator PowerShell (IIS restarts):
Import-Module C:\spitfire-otel\OpenTelemetry.DotNet.Auto.psm1
Register-OpenTelemetryForIIS- For the application's application pool, write the pool's name (IIS Manager → Application
Pools) in place of
<app-pool>and run:
Import-Module WebAdministration
$Pool = "<app-pool>"
$IS_OTEL_ENABLED = "true"
$Vars = [ordered]@{
IS_OTEL_ENABLED = $IS_OTEL_ENABLED
OTEL_SERVICE_NAME = "<service-name>"
OTEL_RESOURCE_ATTRIBUTES = "service.version=<service-version>,deployment.environment=<environment>"
OTEL_EXPORTER_OTLP_PROTOCOL = "http/protobuf"
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = "https://<spitfire>/otlp/v1/traces"
OTEL_EXPORTER_OTLP_TRACES_HEADERS = "authorization=sfotlp_…"
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = "https://<spitfire>/otlp/v1/logs"
OTEL_EXPORTER_OTLP_LOGS_HEADERS = "authorization=sfotlp_…"
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = "http://<tower>:4318/v1/metrics"
OTEL_EXPORTER_OTLP_METRICS_HEADERS = "authorization=sfcol_…"
OTEL_TRACES_EXPORTER = "otlp"
OTEL_METRICS_EXPORTER = "otlp"
OTEL_LOGS_EXPORTER = "otlp"
OTEL_TRACES_SAMPLER = "parentbased_traceidratio"
OTEL_TRACES_SAMPLER_ARG = "0.1"
NO_PROXY = "<tower>,<spitfire>"
}
# Private CA: $Vars.OTEL_EXPORTER_OTLP_CERTIFICATE = "C:\spitfire-otel\ca.pem"
if ($IS_OTEL_ENABLED -ne "true") {
$Vars.COR_ENABLE_PROFILING = "0"
$Vars.CORECLR_ENABLE_PROFILING = "0"
$Vars.OTEL_SDK_DISABLED = "true"
$Vars.OTEL_TRACES_EXPORTER = "none"
$Vars.OTEL_METRICS_EXPORTER = "none"
$Vars.OTEL_LOGS_EXPORTER = "none"
}
$Filter = "system.applicationHost/applicationPools/add[@name='$Pool']/environmentVariables"
foreach ($Name in @($Vars.Keys) + @("COR_ENABLE_PROFILING", "CORECLR_ENABLE_PROFILING", "OTEL_SDK_DISABLED")) {
Remove-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter $Filter -Name "." -AtElement @{ name = $Name } -ErrorAction SilentlyContinue
}
foreach ($Name in $Vars.Keys) {
Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter $Filter -Name "." -Value @{ name = $Name; value = $Vars[$Name] }
}
Restart-WebAppPool -Name $Pool- Open a few pages of the site;
w3wplog files appear underC:\ProgramData\OpenTelemetry .NET AutoInstrumentation\logs.
Register-OpenTelemetryForIIS affects every application pool on the machine. For the
pools you do not want collected, run step 2 with $IS_OTEL_ENABLED = "false".
Windows: service
- In an administrator PowerShell, write the service's name (Name in the
Get-Servicelist) in place of<windows-service>and run:
Import-Module C:\spitfire-otel\OpenTelemetry.DotNet.Auto.psm1
$Service = "<windows-service>"
$IS_OTEL_ENABLED = "true"
$Otel = @(
"IS_OTEL_ENABLED=$IS_OTEL_ENABLED",
"OTEL_SERVICE_NAME=<service-name>",
"OTEL_RESOURCE_ATTRIBUTES=service.version=<service-version>,deployment.environment=<environment>",
"OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://<spitfire>/otlp/v1/traces",
"OTEL_EXPORTER_OTLP_TRACES_HEADERS=authorization=sfotlp_…",
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://<spitfire>/otlp/v1/logs",
"OTEL_EXPORTER_OTLP_LOGS_HEADERS=authorization=sfotlp_…",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://<tower>:4318/v1/metrics",
"OTEL_EXPORTER_OTLP_METRICS_HEADERS=authorization=sfcol_…",
"OTEL_TRACES_EXPORTER=otlp",
"OTEL_METRICS_EXPORTER=otlp",
"OTEL_LOGS_EXPORTER=otlp",
"OTEL_TRACES_SAMPLER=parentbased_traceidratio",
"OTEL_TRACES_SAMPLER_ARG=0.1",
"NO_PROXY=<tower>,<spitfire>"
)
# Private CA: $Otel += "OTEL_EXPORTER_OTLP_CERTIFICATE=C:\spitfire-otel\ca.pem"
if ($IS_OTEL_ENABLED -ne "true") { $Otel += "OTEL_SDK_DISABLED=true" }
$Key = "HKLM:\SYSTEM\CurrentControlSet\Services\$Service"
$Keep = @((Get-ItemProperty $Key).Environment | Where-Object { $_ -and $_ -notmatch '^(IS_OTEL_ENABLED|OTEL_(?!DOTNET_AUTO_)|NO_PROXY)=' })
Set-ItemProperty $Key -Name Environment -Type MultiString -Value ($Keep + $Otel)
if ($IS_OTEL_ENABLED -eq "true") {
Register-OpenTelemetryForWindowsService -WindowsServiceName $Service -OTelServiceName "<service-name>"
} else {
Unregister-OpenTelemetryForWindowsService -WindowsServiceName $Service
}- Log files named after the service's process appear under
C:\ProgramData\OpenTelemetry .NET AutoInstrumentation\logs.
Turning off and removing
- Turning off: set
IS_OTEL_ENABLED=falsein the environment file (in the script on Windows) and restart the service (Linux step 8, Docker step 5, Kubernetes step 4, or run the Windows script again with"false"). - Removing: on Linux
sudo systemctl revert <unit>andsudo rm -r /opt/otel-dotnet-auto /opt/spitfire-otel /etc/spitfire-otel; in Docker, delete the lines added to theDockerfile; on WindowsUnregister-OpenTelemetryForIISorUnregister-OpenTelemetryForWindowsService -WindowsServiceName <windows-service>, thenUninstall-OpenTelemetryCore, and deleteC:\spitfire-otel.
Common problems
| Symptom | Cause | Fix |
|---|---|---|
| The log folder is empty | The profiler was not loaded | Is IS_OTEL_ENABLED true; did Register-OpenTelemetryForIIS run for IIS; is DOTNET_EnableDiagnostics=0 set |
| A .NET 6/7 service does not start with 1.18.0 or sends no data | 1.18.0 does not support .NET 6/7 | Reinstall with 1.9.0 |
| No data from a .NET Framework 4.5 application | Below 4.6.2 is not supported | Move the application to 4.6.2+ |
| "CLR profiler was not correctly loaded" on Linux ARM64 | The ARM64 profiler needs glibc 2.33+; Debian 11 (e.g. the .NET 6 6.0 images) has 2.31 |
Use an Ubuntu 22.04+ base (e.g. the 6.0-jammy image) or an x64 server |
gh not found error |
1.18.0's signature check needs the GitHub CLI | Install gh or use SKIP_RELEASE_VERIFICATION=true / -SkipReleaseVerification |
| No traces or metrics arrive | Address, token or network | Follow the Verifying steps |