DEV Community

Victor
Victor

Posted on

Observability in Local Development with OpenTelemetry, OTLP, and Aspire Dashboard

Introduction

Modern applications are increasingly distributed, combining APIs, front-end SPAs, databases, and cloud storage. While observability is critical in production, developers often overlook its importance during local development. Without proper visibility, debugging performance bottlenecks, tracing requests across services, or identifying storage issues becomes guesswork.

There are many ways to observe local systems — logs, metrics dashboards, or tracing tools — but this time I focus on using the standalone Aspire Dashboard with OpenTelemetry (OTLP).

Local observability helps developers catch issues earlier, experiment with instrumentation safely, and build confidence in their system’s resilience.

Prerequisites

  • Podman (or Docker) for containerized Aspire Dashboard

Here’s how I set things up in my own project, based on my experience.:

1 - Run Aspire Dashboard

Pull and run the Aspire Dashboard container using Podman.

podman run --rm -it -p 18888:18888 -p 4317:18889 --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:latest
Enter fullscreen mode Exit fullscreen mode

Expose the necessary ports for local access.

Aspire dashboard

2 - Configure OpenTelemetry in .NET API

Add OpenTelemetry.Extensions.Hosting and OpenTelemetry.Exporter.OTLP packages.

<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.14.0-beta.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" />
Enter fullscreen mode Exit fullscreen mode

Configure logs, tracing and metrics in Program.cs.

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
});

// Configure OpenTelemetry
var otel = builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation();
        //metrics.AddMeter("Microsoft.AspNetCore.Hosting");
        //metrics.AddMeter("Microsoft.AspNetCore.Server.Kestrel");
    })
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation(options =>
        {
            options.EnrichWithIDbCommand = (activity, command) =>
            {
                var stateDisplayName = $"{command.CommandType} operation";
                activity.DisplayName = stateDisplayName;
                activity.SetTag("db.name", stateDisplayName);
            };
        }));

var OtlpEndpoint = builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"];
if (OtlpEndpoint != null)
{
    otel.UseOtlpExporter();
}
Enter fullscreen mode Exit fullscreen mode

Appsetting.json file

 "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317",
 "OTEL_SERVICE_NAME": "StorageManagementAPI",
 "OTEL_TRACES_SAMPLER": "always_on"
Enter fullscreen mode Exit fullscreen mode

3 - Run the system locally.
3.1 - Logs

Structured logs

3.2 - Traces

Traces screen

A particular trace

A traces with mssql

3.2 - Metrics

Metric sreen

Conclusion

This approach is lightweight, easy to set up, and highly effective for debugging complex interactions across APIs, SPAs, databases, ...

Whether you’ve already tested it or you’re just thinking about it, local observability is worth exploring.

Top comments (0)