Local Development
Locally, every service exports OTLP/gRPC to a .NET Aspire Dashboard container. The dashboard is included in the backend and monitoring Docker Compose profiles, so it starts automatically with pnpm dev:backend. See the component map for the full topology.
Prerequisites
This page assumes you have the local stack running. See Local Development — Getting Started for the pnpm dev:* scripts and Local Development — Docker Compose for the underlying service topology.
Configuration
Each service has its OTel env in docker-compose.yml:
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://aspire-dashboard:18889
OTEL_EXPORTER_OTLP_PROTOCOL: grpc
OTEL_SERVICE_NAME: <service-name>
OTEL_RESOURCE_ATTRIBUTES: service.namespace=feyenoord
OTEL_TRACES_SAMPLER_ARG: "1.0" # 100% locally — opt out per service if noisyAspire runs unsecured (DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true) so the local OTLP endpoint accepts plaintext with no auth. Don't expose it beyond the local Docker network.
Using the dashboard
Open http://localhost:18888. Three tabs matter:
| Tab | What you'll find |
|---|---|
| Structured | Pino logs from every service, plus exception log records emitted by recordException |
| Traces | Distributed traces grouped by trace ID, with each span's service, duration, and status |
| Metrics | Empty by design — metrics are not exported |
Filter by service with the Resource dropdown. Free-text search over message and attributes is in the Filter box (exception.type=BaseError, etc.). For exception records, the trace link in each row jumps to the matching distributed trace.
Troubleshooting
Resource shows unknown_service:node — the service was started without the shared Resource from @repo/observability. Make sure the app imports its local instrumentation.ts before application modules and was rebuilt after pulling.
No traces from a service — most likely OTEL_TRACES_SAMPLER_ARG=0 is set in docker-compose.yml for that container. Logs and exceptions still flow (they're not sampled), but traces are dropped at the root. Set OTEL_TRACES_SAMPLER_ARG: "1.0" to see traces locally.
No data at all — verify the container can reach Aspire:
docker exec feyenoord-api-1 node -e "
const s = new (require('net').Socket)();
s.on('connect', () => { console.log('OK'); s.end() });
s.on('error', e => console.log('ERR', e.message));
s.connect(18889, 'aspire-dashboard');
"If this prints OK, the network is fine — check docker logs feyenoord-aspire-dashboard-1 for rejected exports.
Aspire data resets on restart — by design, the dashboard stores telemetry in memory.