Skip to content

About

ObservableApi is an onboarding-first observability reference for .NET developers. It shows how to run a minimal API with .NET Aspire orchestration and send telemetry to Loki/Grafana through an OpenTelemetry Collector.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

ObservableApi

ObservableApi is an onboarding-first observability reference for .NET developers.
It shows how to run a minimal API with .NET Aspire orchestration and send telemetry to Loki/Grafana through an OpenTelemetry Collector.

Who this is for

  • New developers: get a working, observable app running quickly.
  • Experienced developers: use it as a baseline for service defaults, telemetry wiring, and local diagnostics patterns.

What is in this repository

.
├── ObservableApi.sln
├── src\
│   ├── ObservableApi.Api\              # Minimal API (/ping, /weatherforecast)
│   ├── ObservableApi.AppHost\          # Aspire orchestrator
│   └── ObservableApi.ServiceDefaults\  # Shared OpenTelemetry + health + discovery defaults
├── docker\
│   ├── docker-compose.yml         # Optional standalone local stack
│   ├── grafana\
│   ├── loki\
│   └── otel-collector\
└── .github\                       # CI and contribution templates

Architecture at a glance

flowchart LR
    A[ObservableApi.Api] -->|OTLP gRPC| O[OTel Collector]
    O -->|logs| L[Loki]
    O -->|metrics endpoint| P[Prometheus scrape]
    G[Grafana] -->|query| L
Loading

Key behavior

  1. API emits logs/traces/metrics.
  2. Service defaults wire OpenTelemetry automatically.
  3. AppHost injects OTEL_EXPORTER_OTLP_ENDPOINT.
  4. Collector forwards logs to Loki.
  5. Grafana reads Loki data source.

Quick start (recommended: Aspire)

Prerequisites

  • .NET 10 SDK
  • Docker Desktop running

Run

dotnet restore .\ObservableApi.sln
dotnet run --project .\src\ObservableApi.AppHost\ObservableApi.AppHost.csproj

When AppHost starts, open the Aspire dashboard URL shown in the terminal.

Validate quickly

  1. Open API from dashboard (or call directly once endpoint is shown).
  2. Call:
curl http://localhost:<api-port>/ping
curl http://localhost:<api-port>/weatherforecast
  1. Open Grafana at http://localhost:3000 (admin / admin).
  2. In Explore, query logs from observable-api.

Optional standalone stack (without Aspire)

Use this when you want observability services running independently.

docker compose -f .\docker\docker-compose.yml up -d
$env:OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
dotnet run --project .\src\ObservableApi.Api\ObservableApi.Api.csproj

API endpoints

Endpoint Purpose
GET /ping Lightweight connectivity check
GET /weatherforecast Sample payload endpoint with configurable generation settings
GET /health Development readiness check from service defaults
GET /alive Development liveness check from service defaults

Configuration model

Weather forecast options

src\ObservableApi.Api\appsettings.json:

"WeatherForecast": {
  "DaysToGenerate": 5,
  "MinTemperatureC": -20,
  "MaxTemperatureC": 55
}

These values are bound to strongly-typed options (WeatherForecastOptions) and validated at startup.

Telemetry endpoint override

AppHost sets OTEL_EXPORTER_OTLP_ENDPOINT automatically for orchestrated runs.

For API-only runs, set it manually:

$env:OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"

Logging

API logging is configured with Serilog:

  • Console sink (local terminal readability)
  • Grafana Loki sink (central log query)

Package references

Add these to your API project:

<ItemGroup>
  <PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
  <PackageReference Include="Serilog.Settings.Configuration" Version="10.0.1" />
  <PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
  <PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="9.0.0" />
</ItemGroup>

Program.cs setup

Wire Serilog into the host:

using Serilog;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseSerilog((context, services, loggerConfiguration) => loggerConfiguration
    .ReadFrom.Configuration(context.Configuration)
    .ReadFrom.Services(services)
    .Enrich.FromLogContext(), writeToProviders: true);

builder.AddServiceDefaults();
// ... rest of config

appsettings.json configuration

Configure Serilog sinks in appsettings.json:

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Grafana.Loki" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.AspNetCore": "Warning"
      }
    },
    "Enrich": [ "FromLogContext" ],
    "WriteTo": [
      {
        "Name": "Console"
      },
      {
        "Name": "GrafanaLoki",
        "Args": {
          "uri": "http://localhost:3100",
          "labels": [
            {
              "Key": "service",
              "Value": "observable-api"
            },
            {
              "Key": "environment",
              "Value": "local"
            }
          ]
        }
      }
    ]
  }
}

Key points:

  • uri: Loki endpoint (AppHost sets http://localhost:3100 in orchestrated mode).
  • labels: Tag logs for filtering in Grafana (e.g., filter by service="observable-api").
  • Override levels: Keep Microsoft logs at Warning to reduce noise.
  • FromLogContext enricher: Adds scope properties to logs for request tracing.

Using the logger

Logs are emitted automatically through ASP.NET Core middleware. To log from code:

var logger = loggerFactory.CreateLogger("MyEndpoint");
logger.LogInformation("Processing request with {UserId}", userId);

All logs flow to both Console and Loki automatically.


Development workflow

Build

dotnet build .\ObservableApi.sln

Test

dotnet test .\ObservableApi.sln

src\ObservableApi.Api.Tests includes endpoint integration tests for /ping and /weatherforecast.

CI

GitHub Actions workflow: .github/workflows/ci.yml
Runs restore, build, and tests on push/PR.


How to extend this project

Add a new endpoint

  1. Add a new endpoint mapper in src\ObservableApi.Api\Endpoints\.
  2. Map it in Program.cs.
  3. Add integration tests in src\ObservableApi.Api.Tests.
  4. Update README endpoint table if externally relevant.

Add service-level options

  1. Create an options class under src\ObservableApi.Api\Options\.
  2. Bind + validate in Program.cs.
  3. Add defaults in appsettings.json and optional overrides in environment-specific settings.

Extend observability

  1. Update src\ObservableApi.ServiceDefaults\Extensions.cs for shared telemetry behavior.
  2. Keep defaults safe and explicit for local developer workflows.
  3. Document any new required environment variables.

Troubleshooting

Symptom Likely cause Fix
AppHost starts but containers fail Docker not running or missing image pull Start Docker Desktop and rerun
No logs in Grafana Loki/collector/API endpoint mismatch Verify collector and Loki endpoints in AppHost and Docker config
API starts but tests fail locally Wrong environment config Ensure tests run with default project layout and dotnet test from repo root
Dashboard/health endpoint confusion Non-development environment assumptions Use Development environment for local runs

Contributing

See CONTRIBUTING.md for setup, test expectations, and PR guidance.


Production caveats

This repository is a developer reference, not a production template.
Before production use, add stronger auth/secrets management, persistence/retention strategy, and tighter platform hardening.

About

ObservableApi is an onboarding-first observability reference for .NET developers. It shows how to run a minimal API with .NET Aspire orchestration and send telemetry to Loki/Grafana through an OpenTelemetry Collector.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages