CtrlK
BlogDocsLog inGet started
Tessl Logo

kopai/otel-instrumentation

Instrument applications with the OpenTelemetry SDK and prove the telemetry is good by validating it against a local Kopai backend. Use when setting up observability, adding tracing/logging/metrics, deciding what to instrument or which attributes to add, retrofitting OTel into an existing codebase, threading context through call chains, configuring sampling, or when traces/logs/metrics aren't appearing after setup. Also use when users say things like "my traces aren't showing up", "I don't see any data", or "how do I add observability to my app". Do NOT use to investigate existing telemetry for a root cause (use root-cause-analysis), to build dashboards (use create-dashboard), or to instrument LLM and agent calls (use otel-genai-instrumentation).

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

lang-dotnet.mdreferences/

titleimpacttags
.NET InstrumentationHIGHlang, dotnet, csharp, traces, logs, metrics

.NET Instrumentation

Set up OpenTelemetry SDK for .NET applications with traces, logs, and metrics.

Install

dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore

Configuration

CRITICAL: The OTLP exporter defaults to gRPC. For HTTP endpoints (port 4318), you MUST set OtlpExportProtocol.HttpProtobuf and append signal paths.

using OpenTelemetry.Exporter;

// Get endpoint from environment
var endpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")
    ?? "http://localhost:4318";
var serviceName = Environment.GetEnvironmentVariable("OTEL_SERVICE_NAME")
    ?? "my-service";

Environment Variables:

VariableDescription
OTEL_EXPORTER_OTLP_ENDPOINTOTLP endpoint (e.g., http://localhost:4318)
OTEL_SERVICE_NAMEService name shown in observability backend

Traces

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService(serviceName))
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter(opts =>
        {
            opts.Endpoint = new Uri($"{endpoint}/v1/traces");
            opts.Protocol = OtlpExportProtocol.HttpProtobuf;
        }));

Logs

using OpenTelemetry.Resources;

var resourceBuilder = ResourceBuilder.CreateDefault()
    .AddService(serviceName);

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.SetResourceBuilder(resourceBuilder);
    logging.AddOtlpExporter(opts =>
    {
        opts.Endpoint = new Uri($"{endpoint}/v1/logs");
        opts.Protocol = OtlpExportProtocol.HttpProtobuf;
    });
});

Metrics

using System.Diagnostics.Metrics;

var meter = new Meter(serviceName);
var requestCounter = meter.CreateCounter<long>("requests", "1", "Request count");

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddMeter(serviceName)
        .AddOtlpExporter(opts =>
        {
            opts.Endpoint = new Uri($"{endpoint}/v1/metrics");
            opts.Protocol = OtlpExportProtocol.HttpProtobuf;
        }));

Complete Example

using System.Diagnostics.Metrics;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

var endpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")
    ?? "http://localhost:4318";
var serviceName = Environment.GetEnvironmentVariable("OTEL_SERVICE_NAME")
    ?? "my-service";

var meter = new Meter(serviceName);
var requestCounter = meter.CreateCounter<long>("requests");

var resourceBuilder = ResourceBuilder.CreateDefault().AddService(serviceName);

// Traces and Metrics
builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService(serviceName))
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter(opts =>
        {
            opts.Endpoint = new Uri($"{endpoint}/v1/traces");
            opts.Protocol = OtlpExportProtocol.HttpProtobuf;
        }))
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddMeter(serviceName)
        .AddOtlpExporter(opts =>
        {
            opts.Endpoint = new Uri($"{endpoint}/v1/metrics");
            opts.Protocol = OtlpExportProtocol.HttpProtobuf;
        }));

// Logs
builder.Logging.AddOpenTelemetry(logging =>
{
    logging.SetResourceBuilder(resourceBuilder);
    logging.AddOtlpExporter(opts =>
    {
        opts.Endpoint = new Uri($"{endpoint}/v1/logs");
        opts.Protocol = OtlpExportProtocol.HttpProtobuf;
    });
});

var app = builder.Build();
app.MapGet("/hello", (ILogger<Program> logger) =>
{
    requestCounter.Add(1);
    logger.LogInformation("Hello endpoint called");
    return Results.Json(new { message = "Hello!" });
});
app.Run();

Reference

OpenTelemetry .NET

Next

SDK setup is step 3 of six. It gets bytes flowing; it does not make the telemetry good.

  1. Decide what earns a span — instrument-spans.md
  2. Add the context that makes spans answerable — instrument-attributes.md
  3. Instrument the error paths — instrument-errors.md
  4. Drive traffic yourself — drive-traffic.md
  5. Assert on what arrived — validate-traces.md

Confirm before moving on: the SDK starts before any application code that could create a span, and shutdown flushes on SIGTERM (validate-shutdown.md). Both fail silently.

references

_sections.md

architectural-patterns.md

attributes.md

cli-reference.md

context-propagation.md

custom-instrumentation.md

drive-traffic.md

instrument-attributes.md

instrument-errors.md

instrument-spans.md

lang-cpp.md

lang-dotnet.md

lang-erlang.md

lang-fastify.md

lang-go.md

lang-java.md

lang-nextjs.md

lang-nodejs.md

lang-php.md

lang-python.md

lang-ruby.md

lang-rust.md

layered-telemetry.md

nextjs-examples.md

otel-docs.md

sampling.md

setup-backend.md

setup-environment.md

troubleshoot-missing-attrs.md

troubleshoot-missing-spans.md

troubleshoot-no-data.md

troubleshoot-wrong-port.md

validate-logs.md

validate-metrics.md

validate-shutdown.md

validate-traces.md

CHANGELOG.md

SKILL.md

tile.json