Ofbirds.Observability 1.1.0

dotnet add package Ofbirds.Observability --version 1.1.0
                    
NuGet\Install-Package Ofbirds.Observability -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ofbirds.Observability" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ofbirds.Observability" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Ofbirds.Observability" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ofbirds.Observability --version 1.1.0
                    
#r "nuget: Ofbirds.Observability, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ofbirds.Observability@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ofbirds.Observability&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Ofbirds.Observability&version=1.1.0
                    
Install as a Cake Tool

Ofbirds.Observability

One-call logging + OpenTelemetry for ASP.NET Core services, so every service emits the same shape of telemetry. Defaults are the standard; the opt-outs exist for the rare case where a piece is impossible or genuinely unwanted.

var builder = WebApplication.CreateBuilder(args);
builder.AddOfbirdsObservability("my-app");   // FIRST — it sets the static Log.Logger

var app = builder.Build();
app.UseOfbirdsObservability();               // request logging + health endpoints

What you get

Logging (Serilog) — console, rolling compact-JSON file (logs/log-.json, 31 days), and Seq when SEQ_URL is set. Enrichers: log context, machine name, process id, thread id, Application, Version. Minimum level Information, with Microsoft.AspNetCore and System.Net.Http.HttpClient at Warning. Both the static Log.Logger and the DI logger are wired, so startup-time Log.Warning(...) calls before the host is built still reach the sinks.

Tracing (OpenTelemetry) — ASP.NET Core + HttpClient instrumentation plus your app's own ActivitySource, exported over OTLP/HTTP-protobuf.

Metrics — ASP.NET Core, HttpClient, runtime, and your app's Meter. Off unless OTEL_EXPORTER_OTLP_METRICS_ENDPOINT is set, because a log/trace-only backend rejects them.

Health endpoints/api/health (all checks) and /api/alive (checks tagged live), both mapped AllowAnonymous so an app with a global "require authenticated user" fallback policy does not answer its own probes with 401 and read as permanently down.

Quiet probe traffic — request logs for the health paths drop below the minimum level, so Kubernetes probes don't fill the log store with thousands of daily non-events. Failures still log: 5xx and unhandled exceptions stay at Error. Adjust with QuietRequestPaths.

CorrelationObservabilityScope puts identifiers on every log event inside the scope and, when a span is active, as tags on it:

using var _ = ObservabilityScope.Begin(("ScanId", scanId), ("AccountId", accountId));

Custom spans and metrics — take OfbirdsTelemetry from DI (or OfbirdsTelemetry.Instance where DI is not available); its Source and Meter are the ones the pipeline listens to:

using var activity = telemetry.Source.StartActivity("scan.account");
var counter = telemetry.Meter.CreateCounter<long>("documents.routed");

Configuration

Everything is environment-driven — no endpoint is ever baked into the package.

Variable Effect
SEQ_URL Enables the Seq sink; also the fallback source for the OTLP trace endpoint (<host>:5341/ingest/otlp).
SEQ_API_KEY Optional Seq ingestion key.
OTEL_EXPORTER_OTLP_ENDPOINT OTLP base for traces; wins over the SEQ_URL fallback.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT Signal-specific traces endpoint; wins over everything.
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT Metrics endpoint. Unset = no metrics exported.
APP_VERSION Release tag on every log and span. Falls back to the entry assembly's informational version, then dev.
LOG_LEVEL The floor every sink sees: Verbose/Trace, Debug, Information/Info, Warning/Warn, Error, Fatal/Critical (case-insensitive). Default Information. Wins over the app's own MinimumLevel, so an app can be turned up on a running pod without a code change. An unrecognised value falls back rather than silencing the app or drowning it.

Signal-specific variables accept either the full URL (…/v1/traces, per the OTel spec) or a base URL — the signal path is appended only when missing.

Unset everything and the service still runs, logging to console and file.

The SEQ_URL trace fallback assumes the Seq host also exposes OTLP on 5341, since it rewrites the port (http://seq.example.com:8080http://seq.example.com:5341/ingest/otlp/v1/traces). If Seq sits behind a reverse proxy, or its 5341 is not published, set OTEL_EXPORTER_OTLP_ENDPOINT explicitly — a wrong OTLP endpoint fails silently, it does not throw.

Shutdown: the logger is disposed with the host, which is what flushes Seq. Keep the usual try { app.Run(); } catch (ex) { Log.Fatal(ex, "..."); } finally { Log.CloseAndFlush(); } wrapper, but be aware that a fatal logged after host disposal only reaches the console — the network sinks are already closed. Log.CloseAndFlush() after host disposal is safe.

Turning an app up

The floor is a LoggingLevelSwitch, not a fixed level, so it can move while the process runs:

options.LevelSwitch.MinimumLevel = LogEventLevel.Debug;   // e.g. from an admin endpoint

That matters because raising the level to diagnose something must not require the restart that destroys the state being diagnosed. Until this existed the floor was hardcoded at Information, which meant every LogDebug and LogTrace an app wrote was discarded before reaching a sink — so writing them was pointless, and the only way to see inside a running app was to add LogInformation calls and redeploy.

Framework noise stays capped by the standard overrides (Microsoft.AspNetCore at Warning), so LOG_LEVEL=Debug surfaces the app's own detail rather than ASP.NET's.

Opt-outs

builder.AddOfbirdsObservability("my-app", o =>
{
    o.DisableFileSink = true;          // containers without a writable scratch volume
    o.DisableHealthEndpoints = true;   // host already owns /api/health
    o.DisableRequestLogging = true;
    o.LevelOverrides["Npgsql"] = LogEventLevel.Warning;
    o.ConfigureTracing = t => t.AddSource("HotChocolate");   // app-specific instrumentation
});

Two routes on the same path throw AmbiguousMatchException at request time — an app that already maps /api/health should register its probe as an IHealthCheck rather than keeping a parallel endpoint.

Conventions this package assumes

  • Three levels in practice. Information for lifecycle and business events, Warning for handled problems including failed input validation, Error for failures that need someone. Critical only when the process cannot continue.
  • Named placeholders, always: logger.LogInformation("Scan {ScanId} finished in {Elapsed}ms", id, ms). Never string interpolation — it destroys the structure Seq queries.
  • Exception first: logger.LogError(ex, "template", args).
  • Never log whole entities. Log identifiers and the few fields that matter; entity dumps are a volume and privacy problem in a shared log store.
  • Log where the failure is absorbed. A swallowed exception logs; a rethrown one does not (whoever handles it will log it) — no double reporting.
  • Correlate at the boundary. Open an ObservabilityScope where a unit of work starts so every downstream line carries its identifiers.

MIT licensed.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 397 8/9/2026
1.0.0 87 8/9/2026