Azure Container Apps
In Azure, telemetry from every container ends up in Application Insights by way of the managed OpenTelemetry agent that runs on the Container App Environment. Apps speak plain OTLP/gRPC to the agent's localhost sidecar; the platform forwards. The application code does not talk to App Insights directly — we don't use @azure/monitor-opentelemetry server-side.
The agent injects OTEL_EXPORTER_OTLP_ENDPOINT automatically; apps don't pin the endpoint in Terraform. The agent is configured on the Container App Environment, owned by the platform team outside this repo. See the component map for where the agent sits in the larger topology.
Per-app environment variables
| Variable | Set by | Purpose |
|---|---|---|
OTEL_SERVICE_NAME | App | Resource attribute, drives cloud_RoleName in App Insights |
OTEL_RESOURCE_ATTRIBUTES | App (service.namespace=feyenoord) | Resource attributes, parsed by envDetector |
OTEL_TRACES_SAMPLER | Only api-gateway (Traefik, Go OTel SDK) | Sampler strategy — see note below |
OTEL_TRACES_SAMPLER_ARG | App, per env (tfvars) | Root sampling ratio |
OTEL_EXPORTER_OTLP_ENDPOINT | Platform agent | Injected automatically — do not set in Terraform |
OTEL_EXPORTER_OTLP_PROTOCOL | App, when applicable | grpc |
OTEL_TRACES_SAMPLER is api-gateway only
Node services using @repo/observability hard-wire ParentBased(TraceIdRatio) in node/sdk.ts and never read OTEL_TRACES_SAMPLER. The Apollo Router reads its sampler from router.yaml. Only Traefik (api-gateway) reads OTEL_TRACES_SAMPLER from its embedded Go OTel SDK. Setting it on the other apps would imply a knob that doesn't exist.
match-gateway deliberately omits OTEL_TRACES_SAMPLER_ARG. The SDK default is 1.0 — full trace coverage required for the live Stats Perform event feed.
Sampling per environment
| Environment | Ratio | Rationale |
|---|---|---|
| TST | 1.0 | Catch everything during integration testing |
| ACC | 1.0 | Catch everything during acceptance testing |
| PRD | 0.1 | App Insights cost control; native app's 5% decision propagates regardless |
Because every component runs parentbased_traceidratio, traces the native app marks sampled=01 always span the whole chain regardless of the per-service ratio. The ratio only governs server-originated work (cron jobs, queue consumers, internal HTTP without inbound traceparent).
Without parent-based sampling everywhere, each hop would flip its own coin and the chance of a complete distributed trace would collapse multiplicatively (5% × 10% × 10% × 10% ≈ 0.005%). With it, the native app's decision is the only one that matters for client-originated traffic.
Exception capture in App Insights
Even for the 95% of native-originated requests with sampled=00, exceptions still arrive in the exceptions table because recordException() always emits a log record alongside the span event, and log records are not sampled. See the exception pipeline for the code-level flow.
In practice:
cloud_RoleNamefilters work because the sharedResourceshipsservice.name.- The
exceptionsblade shows every error, sampled or not. - The
requestsblade shows the sampled subset; linked exceptions are still discoverable viacloud_RoleName+outerMessage. - Pino logs land in the
tracestable with the samecloud_RoleName.
What the apps DON'T do
- No
@azure/monitor-opentelemetry. The platform agent translates OTel → App Insights. The Azure-specific package would double-instrument and overwrite the resource. - No App Insights connection string server-side. The web container app sets
NEXT_PUBLIC_APPLICATIONINSIGHTS_CONNECTION_STRINGfor the browser bundle only. - No metrics export. The Container Apps agent doesn't accept OTel metrics today.
Where things live
| Concern | Location |
|---|---|
| Container App Environment + agent | Platform team — referenced as data.azurerm_container_app_environment.main |
| Application Insights resource | Referenced as data.azurerm_application_insights.main |
| Per-app OTel env vars | azure/infra/apps/<app>/main.tf |
| Per-env sampler ratio | azure/infra/apps/<app>/tfvars/{tst,acc,prd}.tfvars |
| Apollo Router OTel config | apps/graphql-router/router.yaml |
| Traefik OTel config | apps/api-gateway/traefik/entrypoint.sh |
| Node / Next.js OTel SDK | packages/infrastructure/observability |