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.
- 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.
.
├── 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
flowchart LR
A[ObservableApi.Api] -->|OTLP gRPC| O[OTel Collector]
O -->|logs| L[Loki]
O -->|metrics endpoint| P[Prometheus scrape]
G[Grafana] -->|query| L
- API emits logs/traces/metrics.
- Service defaults wire OpenTelemetry automatically.
- AppHost injects
OTEL_EXPORTER_OTLP_ENDPOINT. - Collector forwards logs to Loki.
- Grafana reads Loki data source.
- .NET 10 SDK
- Docker Desktop running
dotnet restore .\ObservableApi.sln
dotnet run --project .\src\ObservableApi.AppHost\ObservableApi.AppHost.csprojWhen AppHost starts, open the Aspire dashboard URL shown in the terminal.
- Open API from dashboard (or call directly once endpoint is shown).
- Call:
curl http://localhost:<api-port>/ping
curl http://localhost:<api-port>/weatherforecast- Open Grafana at
http://localhost:3000(admin/admin). - In Explore, query logs from
observable-api.
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| 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 |
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.
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"API logging is configured with Serilog:
- Console sink (local terminal readability)
- Grafana Loki sink (central log query)
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>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 configConfigure 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 setshttp://localhost:3100in orchestrated mode).labels: Tag logs for filtering in Grafana (e.g., filter byservice="observable-api").- Override levels: Keep Microsoft logs at Warning to reduce noise.
- FromLogContext enricher: Adds scope properties to logs for request tracing.
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.
dotnet build .\ObservableApi.slndotnet test .\ObservableApi.slnsrc\ObservableApi.Api.Tests includes endpoint integration tests for /ping and /weatherforecast.
GitHub Actions workflow: .github/workflows/ci.yml
Runs restore, build, and tests on push/PR.
- Add a new endpoint mapper in
src\ObservableApi.Api\Endpoints\. - Map it in
Program.cs. - Add integration tests in
src\ObservableApi.Api.Tests. - Update README endpoint table if externally relevant.
- Create an options class under
src\ObservableApi.Api\Options\. - Bind + validate in
Program.cs. - Add defaults in
appsettings.jsonand optional overrides in environment-specific settings.
- Update
src\ObservableApi.ServiceDefaults\Extensions.csfor shared telemetry behavior. - Keep defaults safe and explicit for local developer workflows.
- Document any new required environment variables.
| 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 |
See CONTRIBUTING.md for setup, test expectations, and PR guidance.
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.