This guide shows you how to send custom and runtime metrics from your .NET application to SigNoz using OpenTelemetry automatic instrumentation.
Prerequisites
- .NET 6.0 or later
- 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.sh
Step 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_METRICS_EXPORTER="otlp"
export OTEL_METRIC_EXPORT_INTERVAL="60000"
export OTEL_TRACES_EXPORTER="none"
export OTEL_LOGS_EXPORTER="none"
export OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES="MyApp.Metrics"
Replace the following:
<region>: Your SigNoz Cloud region (us,eu, orin). See endpoints.<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);
}
}
This example shows a Counter, which only increases. OpenTelemetry supports other metric types like UpDownCounter, Histogram, and ObservableGauge. See Custom Metrics Examples for complete examples of each type.
Step 4. Run your application
. $HOME/.otel-dotnet-auto/instrument.sh
dotnet run
Step 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 /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_METRICS_EXPORTER
value: 'otlp'
- name: OTEL_METRIC_EXPORT_INTERVAL
value: '60000'
- 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-OpenTelemetryCore
Step 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_METRICS_EXPORTER = "otlp"
$env:OTEL_METRIC_EXPORT_INTERVAL = "60000"
$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 run
Step 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 /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_METRICS_EXPORTER="otlp"
ENV OTEL_METRIC_EXPORT_INTERVAL="60000"
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-app
Or pass environment variables at runtime:
docker run -p 8080:8080 \
-e OTEL_SERVICE_NAME="my-service" \
-e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.us.signoz.cloud:443" \
-e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<key>" \
-e OTEL_METRICS_EXPORTER="otlp" \
-e OTEL_METRIC_EXPORT_INTERVAL="60000" \
-e OTEL_TRACES_EXPORTER="none" \
-e OTEL_LOGS_EXPORTER="none" \
-e OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES="MyApp.Metrics" \
my-dotnet-app
The automatic instrumentation generates HTTP metrics automatically:
- Server metrics (
http.server.request.duration) for ASP.NET Core apps - Client metrics (
http.client.request.duration) for apps usingHttpClient
See the .NET HTTP metrics semantic conventions for details.
Validate
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.
Want more metrics? .NET runtime metrics (GC, threads, JIT) are collected automatically by the automatic instrumentation. See .NET Runtime Metrics for the full list.
.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_SOURCES
Verify 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 seconds
Authentication 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.