CxO.Observability 0.4.0

dotnet add package CxO.Observability --version 0.4.0
                    
NuGet\Install-Package CxO.Observability -Version 0.4.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="CxO.Observability" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CxO.Observability" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="CxO.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 CxO.Observability --version 0.4.0
                    
#r "nuget: CxO.Observability, 0.4.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 CxO.Observability@0.4.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=CxO.Observability&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=CxO.Observability&version=0.4.0
                    
Install as a Cake Tool

CxO.Observability

NuGet Target frameworks

Shared observability primitives for CxO services. One NuGet package that wires up:

  • OpenTelemetry metrics — ASP.NET Core, HttpClient, Runtime, Process instrumentation + Prometheus exporter on /metrics
  • OpenTelemetry tracing — ASP.NET Core, HttpClient instrumentation + OTLP exporter, with well-known ActivitySources (CxO.Orders, CxO.Workflows, CxO.Dependencies)
  • Standard health endpoints/health, /ready, /live with tag-based filtering
  • Dependency telemetry — HTTP (via DelegatingHandler) and WCF/SOAP (via three interception strategies) with a closed-vocabulary metric schema: cxo_dependency_request_total, cxo_dependency_request_duration_seconds, cxo_dependency_timeout_total, cxo_dependency_health_status
  • Structured request logging — Serilog request middleware + a LogContext enricher that stamps ClientIp / UserId / OrderId / WorkflowId / CorrelationId on every request log and on the current Activity

Targets net8.0 and net10.0. Distributed via GitHub Packages (ghcr.io/codaxy) and nuget.org.

Install

dotnet add package CxO.Observability

Or as a PackageReference:

<PackageReference Include="CxO.Observability" Version="0.2.*" />

Quick start — three-line integration

In your service's Program.cs:

using CxO.Observability;

var builder = WebApplication.CreateBuilder(args);

// 1. Register the observability stack (metrics + tracing + dependency telemetry + health checks)
builder.AddCxoObservability("cxo-your-service-name");

// ... your other service registrations ...

var app = builder.Build();

// 2. Activate request logging + LogContext middleware (AFTER auth)
app.UseAuthentication();
app.UseAuthorization();
app.UseCxoObservability();

// 3. Expose Prometheus + health endpoints
app.MapCxoObservabilityEndpoints();

app.MapControllers();
app.Run();

That's it. After this your service emits the platform-standard metric/log/trace fingerprint and can be picked up by the central Prometheus + Grafana + Tempo stack.

What you get, concretely

On GET /metrics

Prometheus-format metrics including:

Family Source Examples
ASP.NET Core OpenTelemetry.Instrumentation.AspNetCore http_server_request_duration_seconds, http_server_active_requests, kestrel_connection_duration_seconds
HTTP clients OpenTelemetry.Instrumentation.Http http_client_request_duration_seconds, http_client_active_requests
Runtime OpenTelemetry.Instrumentation.Runtime process_runtime_dotnet_gc_heap_size_bytes, process_runtime_dotnet_thread_pool_*
Process OpenTelemetry.Instrumentation.Process process_cpu_time_seconds_total, process_memory_usage_bytes, process_open_handles
CxO dependency telemetry this package cxo_dependency_request_total, cxo_dependency_request_duration_seconds, cxo_dependency_timeout_total, cxo_dependency_health_status

On GET /health, /ready, /live

Aggregated health check responses. Tag a registration ready for readiness-probe inclusion, live for liveness, or leave untagged — it'll appear in /health but not /ready or /live:

builder.Services.AddHealthChecks()
    .AddNpgSql(pgConnectionString, tags: ["ready"])     // readiness: DB must be reachable
    .AddKafka(kafkaOpts, tags: ["ready"])
    .AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"]);

OTLP traces

Sent to the collector endpoint resolved from OTEL_EXPORTER_OTLP_ENDPOINT env var (default http://cxo-otel-collector:4317). Lab environment already has the collector; override in other environments.

Structured request logs

Every request produces a Serilog log line enriched with ClientIp, UserId, OrderId, WorkflowId, CorrelationId, RequestPath, StatusCode. The same properties flow through LogContext to any log lines emitted within the request, and are also stamped as tags on Activity.Current so they appear on your traces too.

Probe paths (/metrics, /health, /ready, /live) log at Verbose to avoid drowning your logs in probe noise.

Custom metrics + tracing

Add service-specific meters and activity sources via the optional hooks:

builder.AddCxoObservability(
    serviceName: "cxo-service-order-management",
    configureMeters: meters => meters.AddMeter("CxO.Orders"),
    configureTracing: tracing => tracing.AddSource("CxO.Orders"));

For the CxO order/workflow/dependency convention, the built-in ActivitySources are already registered:

using var activity = CxO.Observability.Tracing.CxoActivitySources.Orders
    .StartActivity("ProvisionOrder");
activity?.SetTag(CxoActivityTags.OrderId, orderId);

Dependency telemetry — HTTP

Attach the shared DependencyTelemetryHandler to any IHttpClientFactory client and give it a static operation-name resolver (to avoid per-URL cardinality explosion in your metrics):

builder.Services
    .AddHttpClient<KeycloakClient>(c => c.BaseAddress = new Uri(cfg["Keycloak:BaseUrl"]))
    .AddDependencyTelemetry(
        dependencyName: "keycloak",
        resolveOperation: request => request.RequestUri?.LocalPath switch
        {
            "/realms/cxo/protocol/openid-connect/token" => "GetToken",
            "/admin/realms/cxo/users"                   => "ListUsers",
            _                                           => "Other"
        });

Every outbound call against KeycloakClient now emits cxo_dependency_request_total{dependency="keycloak", operation="GetToken|ListUsers|Other", status="ok|4xx|5xx|fault|timeout|error"} plus the latency histogram.

Dependency telemetry — WCF / SOAP

Three integration strategies depending on how your SOAP client is constructed. See docs/integration-guide.md in the repo for full examples; the short version:

// Option — DispatchProxy (recommended for ChannelFactory-based clients)
var channelFactory = new ChannelFactory<ILegacyService>(binding, endpoint);
var proxy = WcfTelemetryDispatchProxy.Create(
    channelFactory.CreateChannel(),
    metrics: dependencyMetrics,
    dependencyName: "legacy-billing-soap",
    resolveOperation: message => message.Headers.Action?.Split('/').Last() ?? "Unknown");

Dependency health checks

Register a health check and the package's IHealthCheckPublisher automatically mirrors its pass/fail state onto the cxo_dependency_health_status gauge so dashboards and alerts see the same truth as the /health endpoint:

builder.Services.AddHealthChecks()
    .AddDependencyCheck(
        name: "keycloak",
        probe: async (sp, ct) =>
        {
            var http = sp.GetRequiredService<IHttpClientFactory>().CreateClient();
            var resp = await http.GetAsync(cfg["Keycloak:HealthUrl"], ct);
            resp.EnsureSuccessStatusCode();
        },
        tags: ["ready"]);

The gauge emits 0 = Unhealthy, 1 = Healthy, 0.5 = Degraded, with a {dependency="keycloak"} label.

Metric names, labels, and the dependency status contract

The package intentionally uses a closed set of values for the status label:

Value Meaning
ok 2xx / IsSuccessStatusCode == true / WCF reply with no fault
4xx HTTP 4xx response
5xx HTTP 5xx response
fault WCF SOAP fault reply
timeout TaskCanceledException / SOAP timeout
error Anything else — network failure, DNS, deserialization, etc.

Stick to these values. Adding a new one requires a coordinated update of Grafana dashboards and alert rules (DependencyHighErrorRate regex matches 5xx|fault|error|timeout).

Full reference in docs/dependency-naming-conventions.md.

Configuration surface

Knob How to set Default Effect
Service name AddCxoObservability("my-service") positional arg required Becomes the service.name resource attribute on metrics and traces
OTLP endpoint OTEL_EXPORTER_OTLP_ENDPOINT env var http://cxo-otel-collector:4317 Where trace spans are sent
Additional meters configureMeters callback none Register custom AddMeter("...") calls
Additional ActivitySources configureTracing callback none Register custom AddSource("...") calls

appsettings.json requirements

The package itself reads nothing from appsettings.json — service name is a code argument, OTLP endpoint is an env var, and request-log dimensions come from HTTP headers / JWT claims at runtime. You can use CxO.Observability in a service with no appsettings.json at all.

Hard host requirement: Serilog must be registered as the logging provider, because UseCxoObservability() calls UseSerilogRequestLogging(...) internally. Minimum wiring:

using Serilog;

builder.Host.UseSerilog((ctx, _, cfg) => cfg
    .ReadFrom.Configuration(ctx.Configuration)   // reads "Serilog" section from appsettings.json
    .Enrich.FromLogContext()                      // required — lets CxoLogContextMiddleware push properties
    .WriteTo.Console());

Recommended appsettings.json Serilog block (CxO platform convention — Console + Seq):

{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.Seq"],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft.AspNetCore": "Warning",
        "Microsoft.EntityFrameworkCore": "Warning",
        "System.Net.Http.HttpClient": "Warning"
      }
    },
    "WriteTo": [
      { "Name": "Console" },
      { "Name": "Seq", "Args": { "serverUrl": "http://cxo-seq:5341" } }
    ],
    "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
    "Properties": { "Application": "cxo-your-service-name" }
  }
}

Sink NuGets referenced above: Serilog.Sinks.Console, Serilog.Sinks.Seq. Swap in whatever sinks your environment uses — none are hard-required by CxO.Observability. See docs/integration-guide.md for the full discussion of env-var vs. config-file trade-offs.

Migrating from hand-rolled OTel wiring

If your Program.cs currently looks like:

builder.Services.AddOpenTelemetry()
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddPrometheusExporter());

app.MapPrometheusScrapingEndpoint();
app.MapHealthChecks("/health");

Replace with:

builder.AddCxoObservability("cxo-your-service-name");
// ...
app.UseCxoObservability();
app.MapCxoObservabilityEndpoints();

You gain: AddProcessInstrumentation (CPU!), HttpClient instrumentation, OTLP tracing, dependency telemetry registration, Serilog LogContext middleware, and /ready / /live tag-filtered endpoints. Nothing you had breaks — any meter you previously registered via configureMeters hook still works.

Drop direct OpenTelemetry.Instrumentation.* / OpenTelemetry.Exporter.* PackageReference lines from your .csproj — they come transitively now.

Versioning

Semantic versioning. Breaking changes (metric names, ActivitySource names, public API shape) bump major. Additive features bump minor. Patch for bug fixes. While on 0.x, the minor version may contain breaking changes until 1.0 — watch the release notes.

License

Proprietary — © Codaxy. Consumption restricted to CxO platform services and Codaxy-authorized integrators.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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 (1)

Showing the top 1 NuGet packages that depend on CxO.Observability:

Package Downloads
CxO.Observability.Conductor

End-to-end W3C trace-context propagation through Netflix Conductor workflows for CxO platform services. Plugs into ConductorSharp via a DelegatingHandler (outbound workflow starts + task completions) and a MediatR IPipelineBehavior (worker-side task dispatch) to keep a single OpenTelemetry traceId across every Conductor async boundary.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0 33 4/29/2026
0.3.0 58 4/29/2026
0.2.0 113 4/24/2026
0.2.0-preview.2 62 4/24/2026
0.2.0-preview.1 40 4/24/2026
0.1.1 93 4/22/2026
0.1.0 94 4/21/2026