CxO.Observability
0.4.0
dotnet add package CxO.Observability --version 0.4.0
NuGet\Install-Package CxO.Observability -Version 0.4.0
<PackageReference Include="CxO.Observability" Version="0.4.0" />
<PackageVersion Include="CxO.Observability" Version="0.4.0" />
<PackageReference Include="CxO.Observability" />
paket add CxO.Observability --version 0.4.0
#r "nuget: CxO.Observability, 0.4.0"
#:package CxO.Observability@0.4.0
#addin nuget:?package=CxO.Observability&version=0.4.0
#tool nuget:?package=CxO.Observability&version=0.4.0
CxO.Observability
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,/livewith 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
LogContextenricher that stampsClientIp/UserId/OrderId/WorkflowId/CorrelationIdon every request log and on the currentActivity
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.
Links
- Repo: github.com/codaxy/cxo-shared
- Design doc:
docs/implementation-design.md - Integration guide:
docs/integration-guide.md - Dependency naming conventions:
docs/dependency-naming-conventions.md
License
Proprietary — © Codaxy. Consumption restricted to CxO platform services and Codaxy-authorized integrators.
| Product | Versions 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. |
-
net10.0
- OpenTelemetry.Api (>= 1.11.2)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.11.2)
- OpenTelemetry.Exporter.Prometheus.AspNetCore (>= 1.11.2-beta.1)
- OpenTelemetry.Extensions.Hosting (>= 1.11.2)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.11.1)
- OpenTelemetry.Instrumentation.Http (>= 1.11.1)
- OpenTelemetry.Instrumentation.Process (>= 1.11.0-beta.1)
- OpenTelemetry.Instrumentation.Runtime (>= 1.11.1)
- Serilog.AspNetCore (>= 9.0.0)
- System.ServiceModel.Http (>= 6.2.0)
- System.ServiceModel.Primitives (>= 6.2.0)
-
net8.0
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.4)
- Microsoft.Extensions.Http (>= 9.0.4)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.4)
- OpenTelemetry.Api (>= 1.11.2)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.11.2)
- OpenTelemetry.Exporter.Prometheus.AspNetCore (>= 1.11.2-beta.1)
- OpenTelemetry.Extensions.Hosting (>= 1.11.2)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.11.1)
- OpenTelemetry.Instrumentation.Http (>= 1.11.1)
- OpenTelemetry.Instrumentation.Process (>= 1.11.0-beta.1)
- OpenTelemetry.Instrumentation.Runtime (>= 1.11.1)
- Serilog.AspNetCore (>= 9.0.0)
- System.ServiceModel.Http (>= 6.2.0)
- System.ServiceModel.Primitives (>= 6.2.0)
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 |