Skip to main content

Observability

This page is Endatix API OpenTelemetry (OTEL_* on the API host). Hub uses the same OTLP names plus Azure Monitor — Hub observability.

Endatix emits logs, metrics and traces through one OpenTelemetry SDK over OTLP. There is no separate logging stack and no vendor SDK in the box.

Telemetry is off by default. With no OTLP endpoint, nothing is exported and no exporter is allocated. One environment variable turns all three signals on.

OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317

Logs also always go to stdout, whether or not telemetry is configured — that is what docker logs and kubectl logs read.

What you get

SignalSourceNotable series / spans
MetricsASP.NET Core, HttpClient, .NET runtimehttp.server.request.duration, dotnet.gc.collections, thread pool
TracesInbound HTTP, outbound HttpClient (including webhook delivery)one span per request; /health, /alive, /ready excluded
LogsEvery ILogger recordcarries the TraceId of the request it happened in

All three share one resource, so a log record and its span agree on service.name.

:::note Runtime metric names On .NET 9 and later the runtime instrumentation emits dotnet.* metrics (dotnet.gc.collections, dotnet.thread_pool.thread.count). The older process.runtime.dotnet.* names are not emitted. If a dashboard shows nothing, check which names it queries. :::

Environment variables

Standard OTEL_* variables are authoritative. Anything under Endatix:Telemetry is a fallback, never an override.

Collector endpoint. Setting this enables telemetry.

grpc or http/protobuf.

Per-signal override. Any one of traces, metrics, or logs endpoint also enables telemetry. Signal-specific variables take precedence over the global one, per the OpenTelemetry specification.

Per-signal metrics endpoint.

Per-signal logs endpoint.

service.name on everything exported.

service.version.

Sampling strategy, e.g. parentbased_traceidratio. Pair with OTEL_TRACES_SAMPLER_ARG (0.1 for 10%).

:::warning A malformed endpoint fails startup An unparseable endpoint or an unknown protocol throws at startup, naming the offending value. The alternative is a host that starts and silently exports nothing. :::

Configuration fallback

Every variable above has an appsettings.json equivalent. Environment variables win where both are present.

{
"Endatix": {
"Telemetry": {
"Otlp": { "Endpoint": "http://collector:4317", "Protocol": "grpc" },
"ServiceName": "endatix-api",
"ResourceAttributes": { "deployment.environment": "staging" }
}
}
}

Log levels

Endatix uses the standard Logging section. Levels default to Warning, so exporting without raising the Endatix level ships almost nothing.

The recommended production shape lifts Endatix records for OTLP only, leaving the console terse:

{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning",
"Endatix": "Warning"
},
"OpenTelemetry": { "LogLevel": { "Endatix": "Information" } },
"Console": { "FormatterName": "json" }
}
}

Logging:OpenTelemetry:LogLevel:* is provider-scoped: it changes what the OTLP exporter receives without touching the console.

Every provider.

The log file only (see below).

File logging

.NET ships no file logging provider, so Endatix includes an optional one. It is disabled by default.

{
"Endatix": {
"Logging": {
"File": {
"Enabled": false,
"Path": "logs/endatix-.log",
"Formatter": "Json",
"RollingInterval": "Day",
"FileSizeLimitBytes": 10485760,
"RollOnFileSizeLimit": true,
"RetainedFileCountLimit": 7
}
}
}
}

Off by design — see the container note below.

Endatix:Logging:File:PathDefault logs/endatix-.log

Relative paths resolve against the content root, not the working directory. The rotation suffix is inserted before the extension.

Json or Text. JSON keeps structured properties queryable.

Infinite, Year, Month, Day, Hour, Minute.

With this off the sink stops writing at the limit rather than rolling.

Older files are deleted as new ones roll.

Enabling file logging never silences the console. Both receive every record.

:::danger Enabling this in a container needs a writable volume The Helm chart runs with readOnlyRootFilesystem: true and only /tmp writable. Enabling file logging without mounting a writable volume at the configured path will fail startup with a message naming the directory. Use the chart's extraVolumes / extraVolumeMounts passthrough. :::

Self-hosted collector

A minimal OpenTelemetry Collector that accepts OTLP and forwards metrics to Prometheus, logs to Loki and traces to Tempo:

otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc: { endpoint: 0.0.0.0:4317 }
http: { endpoint: 0.0.0.0:4318 }

processors:
batch: {}

exporters:
prometheus:
endpoint: 0.0.0.0:8889
otlphttp/loki:
endpoint: http://loki:3100/otlp
otlp/tempo:
endpoint: tempo:4317
tls: { insecure: true }

service:
pipelines:
metrics: { receivers: [otlp], processors: [batch], exporters: [prometheus] }
logs: { receivers: [otlp], processors: [batch], exporters: [otlphttp/loki] }
traces: { receivers: [otlp], processors: [batch], exporters: [otlp/tempo] }

Point Endatix at it with OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317.

Local development

docker compose up starts an Aspire Dashboard alongside the API and Hub, with both already exporting to it. Image: mcr.microsoft.com/dotnet/aspire-dashboard:13.4.2. No extra configuration.

Both ports bind to 127.0.0.1 deliberately: the dashboard is unauthenticated and renders every request, log line and form payload the platform handles. Never pair it with a 0.0.0.0 publish, and never set DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS in a deployed manifest.

To export to it from an app running outside Docker:

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:18889 \
OTEL_EXPORTER_OTLP_PROTOCOL=grpc \
OTEL_SERVICE_NAME=endatix-api \
dotnet run

Hub outside Docker uses the same endpoint with OTEL_SERVICE_NAME=endatix-hubHub observability.

Migrating from the Serilog section

Endatix no longer reads a Serilog configuration section. A host that still has one starts normally and logs a warning naming the section — the section is ignored, which means the levels in it no longer apply.

Levels

BeforeAfter
Serilog:MinimumLevel:DefaultLogging:LogLevel:Default
Serilog:MinimumLevel:Override:MicrosoftLogging:LogLevel:Microsoft
Serilog:MinimumLevel:Override:EndatixLogging:LogLevel:Endatix

Serilog level names map to .NET names: VerboseTrace, DebugDebug, InformationInformation, WarningWarning, ErrorError, FatalCritical.

Sinks

BeforeAfter
Serilog:WriteToConsoleAlways on. Shape with Logging:Console:FormatterName (json or simple)
Serilog:WriteToFileEndatix:Logging:File:Enabled: true — one key to flip, no package to install
Serilog:WriteToApplicationInsightsSee Bring your own telemetry

Serilog:WriteTo:File:Args maps key-for-key onto Endatix:Logging:File: pathPath, rollingIntervalRollingInterval, fileSizeLimitBytesFileSizeLimitBytes, rollOnFileSizeLimitRollOnFileSizeLimit, retainedFileCountLimitRetainedFileCountLimit.

:::caution Check your file path while migrating If your old configuration used an absolute path such as /logs/log-.txt, confirm the process can actually write there. Relative paths now resolve against the content root; absolute paths are left exactly as given. :::

Code

BeforeAfter
endatix.Logging.ConfigureSerilog(cfg => …)endatix.Logging.Configure(logging => …)
endatix.Logging.ConfigureBootstrapLogger(…)Removed — startup logging is configured from Logging

The replacement takes an ILoggingBuilder, so it works with any provider rather than only Serilog:

builder.Host.ConfigureEndatixWithDefaults(endatix =>
{
endatix.Logging.Configure(logging => logging.AddAzureWebAppDiagnostics());
});

:::warning builder.Logging.AddX() in Program.cs does not survive Endatix registers logging inside IHostBuilder.ConfigureServices, which runs at Build() and calls ClearProviders() first — so anything added directly to builder.Logging beforehand is discarded. endatix.Logging.Configure(...) exists because it runs after that point. :::

Next steps