Table of Contents

Observability (OpenTelemetry, metrics, logging)

Inquiry ships an opt-in telemetry layer built on the command-interceptor seam. When it is not registered, the pipeline carries zero telemetry overhead (the interceptor fast path skips every notification); when it is registered but nothing is listening, each signal is a cheap no-op.

Enable it with one call:

services
    .AddInquiry()
    .AddInquiryTelemetry()          // tracing + metrics + logging
    .AddInquirySqlite(connectionString);

Tracing

Every command the pipeline executes becomes a System.Diagnostics.Activity (span) on the "Inquiry" ActivitySource, following the OpenTelemetry database semantic conventions:

Span data Value
Name / kind leading SQL keyword (SELECT, INSERT, …) / Client
db.system.name sqlite, microsoft.sql_server, postgresql, mysql, oracle.db (derived from the ADO.NET command type — no per-provider wiring)
db.operation.name leading SQL keyword
db.query.text the executed SQL (see Redaction)
db.response.affected_rows row count for non-queries
error.type + span status Error exception type on failure

Subscribe an OpenTelemetry tracer to the source:

builder.Services.AddOpenTelemetry()
    .WithTracing(t => t.AddSource(InquiryTelemetry.ActivitySourceName));

Spans parent automatically to the ambient Activity (e.g. the ASP.NET Core request span). A span covers execution through result-set consumption; a streaming query whose enumeration is abandoned early is dropped rather than recorded with a fabricated duration.

Metrics

The "Inquiry" Meter publishes the OpenTelemetry-conventional db.client.operation.duration histogram (seconds), tagged with db.system.name and db.operation.name; failed commands additionally carry error.type, so throughput, latency percentiles, and error rate all derive from the one instrument:

builder.Services.AddOpenTelemetry()
    .WithMetrics(m => m.AddMeter(InquiryTelemetry.MeterName));

Logging

When an ILoggerFactory is registered, the Inquiry.Command category logs:

  • Debug — command executing (operation + SQL) and executed (elapsed ms + rows affected),
  • Error — command failed (elapsed ms, with the exception).

Messages use cached LoggerMessage delegates, so disabled levels cost a single IsEnabled check.

Redaction

Inquiry SQL is built at compile time from constant templates: values flow only through bound parameters, and parameter values are never recorded on spans, metrics, or logs. If even the SQL shape (table/column names) must not leave the process, turn the text off:

services.AddInquiryTelemetry(o => o.RecordCommandText = false);

Health checks

The core package ships an ASP.NET Core health check that opens a connection through the registered connection factory — the same open path the pipeline uses, including configured retry and failover:

builder.Services.AddHealthChecks().AddInquiry();   // name "inquiry", Unhealthy on failure
app.MapHealthChecks("/health");

AddInquiry(name, failureStatus, tags) overload parameters integrate with readiness/liveness endpoint filtering.

Custom interceptors

The telemetry layer is an ordinary IInquiryCommandInterceptor; register your own implementations alongside (or instead of) it for bespoke auditing, tagging, or APM integrations.