This guide shows you how to send custom and runtime metrics from your .NET application to SigNoz using OpenTelemetry automatic instrumentation.
Prerequisites
- Supported .NET version or .NET Framework 4.6.2+.
- A SigNoz Cloud account or self-hosted SigNoz instance
Send metrics to SigNoz
Step 1. Download the OpenTelemetry .NET automatic instrumentation
curl -sSfL https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.sh -O
sh ./otel-dotnet-auto-install.shStep 2. Set environment variables
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_METRIC_EXPORT_INTERVAL="60000"
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE="delta"
export OTEL_TRACES_EXPORTER="none"
export OTEL_LOGS_EXPORTER="none"
export OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES="MyApp.Metrics"Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key.<service-name>: A descriptive name for your service (e.g.,payment-service).
Step 3. Add custom metrics to your application
Use new Meter() to create metrics. The automatic instrumentation picks up meters registered via OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES.
using System.Diagnostics.Metrics;
public class OrderService
{
private static readonly Meter meter = new Meter("MyApp.Metrics", "1.0.0");
private static readonly Counter<long> ordersCounter = meter.CreateCounter<long>(
"orders.processed",
description: "Total number of orders processed");
public void ProcessOrder(string orderId)
{
// Your business logic here
ordersCounter.Add(1);
}
}Step 4. Run your application
. $HOME/.otel-dotnet-auto/instrument.sh
dotnet runStep 1. Add the instrumentation to your Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /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
ENTRYPOINT ["/bin/bash", "-c", ". $HOME/.otel-dotnet-auto/instrument.sh && exec dotnet MyApp.dll"]Step 2. Add environment variables to your deployment
env:
- name: OTEL_SERVICE_NAME
value: '<service-name>'
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: 'https://ingest.<region>.signoz.cloud:443'
- name: OTEL_EXPORTER_OTLP_HEADERS
value: 'signoz-ingestion-key=<your-ingestion-key>'
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: 'http/protobuf'
- name: OTEL_METRICS_EXPORTER
value: 'otlp'
- name: OTEL_METRIC_EXPORT_INTERVAL
value: '60000'
- name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE
value: 'delta'
- name: OTEL_TRACES_EXPORTER
value: 'none'
- name: OTEL_LOGS_EXPORTER
value: 'none'
- name: OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES
value: 'MyApp.Metrics'Replace <region>, <your-ingestion-key>, and <service-name> with your values.
Step 3. Add custom metrics
Follow Step 3 from the VM tab to add custom metrics code.
Step 1. Download the OpenTelemetry .NET automatic instrumentation
$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
Import-Module $download_path
Install-OpenTelemetryCoreStep 2. Set environment variables
$env:OTEL_SERVICE_NAME = "<service-name>"
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://ingest.<region>.signoz.cloud:443"
$env:OTEL_EXPORTER_OTLP_HEADERS = "signoz-ingestion-key=<your-ingestion-key>"
$env:OTEL_EXPORTER_OTLP_PROTOCOL = "http/protobuf"
$env:OTEL_METRICS_EXPORTER = "otlp"
$env:OTEL_METRIC_EXPORT_INTERVAL = "60000"
$env:OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = "delta"
$env:OTEL_TRACES_EXPORTER = "none"
$env:OTEL_LOGS_EXPORTER = "none"
$env:OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES = "MyApp.Metrics"Replace <region>, <your-ingestion-key>, and <service-name> with your values.
Step 3. Add custom metrics
Follow Step 3 from the VM tab to add custom metrics code.
Step 4. Run your application
Register-OpenTelemetryForCurrentSession
dotnet runStep 1. Add the instrumentation to your Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /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
ENV OTEL_SERVICE_NAME="<service-name>"
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
ENV OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
ENV OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
ENV OTEL_METRICS_EXPORTER="otlp"
ENV OTEL_METRIC_EXPORT_INTERVAL="60000"
ENV OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE="delta"
ENV OTEL_TRACES_EXPORTER="none"
ENV OTEL_LOGS_EXPORTER="none"
ENV OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES="MyApp.Metrics"
ENTRYPOINT ["/bin/bash", "-c", ". $HOME/.otel-dotnet-auto/instrument.sh && exec dotnet MyApp.dll"]Replace <region>, <your-ingestion-key>, and <service-name> with your values.
Step 2. Add custom metrics
Follow Step 3 from the VM tab to add custom metrics code.
Step 3. Build and run
docker build -t my-dotnet-app .
docker run -p 8080:8080 my-dotnet-appOr pass environment variables at runtime:
docker run -p 8080:8080 \
-e OTEL_SERVICE_NAME="my-service" \
-e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443" \
-e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<key>" \
-e OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
-e OTEL_METRICS_EXPORTER="otlp" \
-e OTEL_METRIC_EXPORT_INTERVAL="60000" \
-e OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE="delta" \
-e OTEL_TRACES_EXPORTER="none" \
-e OTEL_LOGS_EXPORTER="none" \
-e OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES="MyApp.Metrics" \
my-dotnet-appValidate
Once your application is running with automatic instrumentation:
- Trigger some application activity to generate metrics.
- In SigNoz, go to Metrics > Metrics Explorer.
- Search for your custom metric (e.g.,
orders.processed). - Runtime metrics like
process.runtime.dotnet.gc.collections.countare collected automatically.
.NET Runtime Metrics
The automatic instrumentation collects .NET runtime and process metrics automatically. No additional configuration is needed.
Exported Metrics
Garbage Collection Metrics:
process.runtime.dotnet.gc.collections.count- Number of garbage collections by generationprocess.runtime.dotnet.gc.objects.size- Bytes in use by GC heap objectsprocess.runtime.dotnet.gc.allocations.size- Total bytes allocated on managed heapprocess.runtime.dotnet.gc.duration- Total time paused in GC
JIT Compilation Metrics:
process.runtime.dotnet.jit.il_compiled.size- IL bytes compiled by JITprocess.runtime.dotnet.jit.methods_compiled.count- Number of methods JIT compiledprocess.runtime.dotnet.jit.compilation_time- Time spent in JIT compilation
Threading Metrics:
process.runtime.dotnet.thread_pool.threads.count- Number of thread pool threadsprocess.runtime.dotnet.thread_pool.completed_items.count- Work items completedprocess.runtime.dotnet.thread_pool.queue.length- Thread pool queue lengthprocess.runtime.dotnet.monitor.lock_contention.count- Monitor lock contentionsprocess.runtime.dotnet.timer.count- Active timer count
Assembly Metrics:
process.runtime.dotnet.assemblies.count- Number of loaded assemblies
Process Metrics:
process.cpu.time- CPU time by state (user/system)process.cpu.count- Number of available processorsprocess.memory.usage- Physical memory in useprocess.memory.virtual- Committed virtual memoryprocess.thread.count- Process thread count
Custom Metrics Examples
OpenTelemetry provides four metric instrument types. Each example shows how to create and use the instrument.
Counter
A value that only increases (e.g., total requests, orders processed).
using System.Diagnostics.Metrics;
var meter = new Meter("MyApp.Metrics", "1.0.0");
var requestCounter = meter.CreateCounter<long>(
"http.requests.total",
description: "Total number of HTTP requests");
// Increment the counter
requestCounter.Add(1);UpDownCounter
A value that can increase or decrease (e.g., queue size, active connections).
using System.Diagnostics.Metrics;
var meter = new Meter("MyApp.Metrics", "1.0.0");
var activeRequests = meter.CreateUpDownCounter<int>(
"http.requests.active",
description: "Number of active requests");
// Increment when request starts
activeRequests.Add(1);
// Decrement when request ends
activeRequests.Add(-1);Histogram
A distribution of values (e.g., request duration, response size).
using System.Diagnostics.Metrics;
var meter = new Meter("MyApp.Metrics", "1.0.0");
var requestDuration = meter.CreateHistogram<double>(
"http.request.duration",
unit: "ms",
description: "HTTP request duration");
// Record a duration value
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// ... process request ...
requestDuration.Record(stopwatch.ElapsedMilliseconds);ObservableGauge
A point-in-time value via callback (e.g., temperature, memory usage).
using System.Diagnostics.Metrics;
var meter = new Meter("MyApp.Metrics", "1.0.0");
meter.CreateObservableGauge(
"app.memory.usage",
() => GC.GetTotalMemory(false),
unit: "By",
description: "Current memory usage");Troubleshooting
Metrics not appearing?
Check environment variables:
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_METRICS_EXPORTER
echo $OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCESVerify instrumentation is active: Look for OpenTelemetry startup messages in the application logs.
Check meter name registration:
Ensure OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES matches your meter name (e.g., MyApp.Metrics). You can use wildcards like MyApp.*.
Metrics delayed or not updating?
OTEL_METRIC_EXPORT_INTERVAL controls how often metrics are exported in milliseconds. The default is 60000 (60 seconds), so metrics may take up to a minute to appear.
To reduce the interval for testing:
export OTEL_METRIC_EXPORT_INTERVAL=10000 # 10 secondsAuthentication errors
If you see "Unauthorized" or "403 Forbidden":
- Verify your ingestion key is correct in
OTEL_EXPORTER_OTLP_HEADERS - Ensure the header format is exactly:
signoz-ingestion-key=<your-key>(no extra spaces)
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.
Why use it?
- Cleaning up data - Filter out noisy metrics you don't care about, or remove sensitive info before it leaves your servers.
- Keeping your app lightweight - Let the Collector handle batching, retries, and compression instead of your application code.
- Adding context automatically - The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
- Future flexibility - Want to send data to multiple backends later? The Collector makes that easy without changing your app.
See Switch from direct export to Collector for step-by-step instructions to convert your setup.
For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.
Next Steps
- Create Dashboards to visualize your metrics.
- Set up Alerts on your metrics.
- Send Traces from your .NET application to correlate metrics with traces.