Observability and Diagnostics
Use one versioned diagnostics vocabulary across embedded databases, the standalone REST API, the combined REST/gRPC daemon, sharded clients, and CSharpDB Studio—without putting SQL text, values, credentials, connection strings, or paths into ordinary telemetry.
None, histories are bounded in memory, metric labels use a closed allowlist, and ordinary snapshots omit sensitive fields instead of asking a UI to hide them.
Choose the Integration Boundary
| Mode | What CSharpDB supplies | Operator action |
|---|---|---|
| Embedded | BCL DiagnosticListener, ActivitySource, Meter, bounded runtime models, and optional ILogger bridge. | Pass one validated CSharpDbObservabilityOptions to DatabaseOptions. Attach your own listeners/exporters, or reuse the ASP.NET Core host adapter. |
| Standalone API | Configuration binding, ILogger bridge, OTLP/console exporters, Prometheus, minimal health routes, and REST diagnostics. | Configure CSharpDB:Observability. The API owns exporter and route lifetime. |
| Daemon | The API features plus REST/gRPC diagnostics and the standard gRPC Health service in one warm host. | Use this preferred remote host when HTTP and gRPC clients should observe the same database runtime. |
| Admin | A bounded Observability workspace over ICSharpDbObservabilityClient. | Connect by direct, HTTP, gRPC, or sharded transport. The workspace does not inspect engine internals. |
| Sharded | One aggregate envelope plus explicit per-shard availability and identity. | Use safe shard aliases. Do not sum counters across different instance ids or counter epochs. |
CSharpDB.Observability is deliberately BCL-only. It does not install an OpenTelemetry SDK, exporter, ASP.NET Core route, logging provider, or background worker. Those dependencies belong at the application or API/daemon host boundary.
Run the Supported Sample
The observability host sample wires a direct database into ASP.NET Core, emits CSharpDB events through ILogger, writes local OpenTelemetry output, exposes a loopback Prometheus scrape, and maps separate liveness and readiness probes.
dotnet run --project samples/observability-host/ObservabilityHostSample.csprojExercise the workload and inspect its surfaces:
curl http://localhost:5099/work
curl http://localhost:5099/health/live
curl http://localhost:5099/health/ready
curl http://localhost:5099/metricsSee the sample overview and complete source.
Configuration
API and daemon hosts bind and validate one CSharpDB:Observability subtree before database warmup. Use a safe configured alias, not a database filename or tenant-provided label.
{
"CSharpDB": {
"Observability": {
"Enabled": true,
"DatabaseAlias": "primary",
"Logging": {
"Enabled": true,
"Queries": true,
"SlowQueries": true,
"SlowQueryThreshold": "00:00:00.500",
"SlowQueryThresholdOverrides": {
"Query": "00:00:01"
},
"SqlText": "None"
},
"History": {
"Enabled": true,
"ActiveQueryCapacity": 1000,
"RecentQueryCapacity": 500,
"RecentOperationCapacity": 100,
"Retention": "00:15:00"
},
"LongRunningQueryThreshold": "00:00:05",
"SessionAbandonmentThreshold": "00:30:00",
"OpenTelemetry": {
"Enabled": true,
"SamplingRatio": 0.1,
"Resource": {
"ServiceName": "orders-api",
"ServiceNamespace": "CSharpDB",
"ServiceVersion": "1.0.0",
"DeploymentEnvironment": "production"
},
"Otlp": { "Enabled": true },
"Console": { "Enabled": false }
},
"Prometheus": {
"Enabled": true,
"Path": "/metrics",
"AllowInsecureRemoteAccess": false
},
"Health": {
"Enabled": true,
"LivenessPath": "/health/live",
"ReadinessPath": "/health/ready",
"ReadinessTimeout": "00:00:02"
}
}
}
}History.Enabled defaults to true. Set it to false for metrics-only or tracing-only operation without retaining recent query or maintenance history; configured telemetry signals continue to emit independently.
Keep collector destinations and credentials outside the JSON file:
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.example.internal:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer%20...
OTEL_EXPORTER_OTLP_TIMEOUT=10000OpenTelemetry or Prometheus cannot be enabled while the master Enabled switch is false. Health is independent: API and daemon health routes remain enabled by default even when general observability is disabled. Endpoint paths must be distinct canonical absolute paths and cannot collide with REST, gRPC, OpenAPI, Scalar, or each other. See the configuration reference for defaults and limits.
Structured ILogger Events
The host bridge subscribes to the CSharpDB diagnostic listener before database warmup. Operational events use logger category CSharpDB.Operational; query completion, slow, failure, cancellation, and long-running events use CSharpDB.Query. Logging-provider failures cannot change database or request results.
| Event id | Stable event name | Meaning |
|---|---|---|
| 1000 | CSharpDB.Host.Starting | Host observability has started. |
| 1001 | CSharpDB.Database.Opened | Database became available. |
| 1002 | CSharpDB.Database.Closed | Database closed. |
| 1003 | CSharpDB.Host.RawSqlCaptureEnabled | One startup warning for the sensitive raw-SQL opt-in. |
| 2000 | CSharpDB.Query.Completed | Successful query terminal event. |
| 2001 | CSharpDB.Query.Slow | Configured slow threshold was reached. |
| 2002 | CSharpDB.Query.Failed | Query failed with a safe code/type projection. |
| 2003 | CSharpDB.Query.Canceled | Query observed cancellation. |
| 2004 | CSharpDB.Query.LongRunning | In-flight query crossed the long-running threshold. |
| 3000 | CSharpDB.Transaction.Completed | Transaction reached one terminal outcome. |
| 4000 | CSharpDB.Checkpoint.Completed | Checkpoint completed. |
| 4001 | CSharpDB.Recovery.Completed | WAL recovery completed. |
| 5000 | CSharpDB.Backup.Completed | Backup completed. |
| 5001 | CSharpDB.Restore.Completed | Restore completed. |
| 5002 | CSharpDB.Maintenance.Completed | Other maintenance completed. |
| 6000 | CSharpDB.Health.Transition | Cached lifecycle/readiness state changed. |
| 7000 | CSharpDB.Api.RequestRejected | Host rejected a request safely. |
| 7001 | CSharpDB.Api.UnhandledError | Host projected an unexpected error safely. |
Stable fields include opaque operation and parent ids, operation class/role/outcome, configured alias, transport, optional safe session/trace/fingerprint correlation, durations, row counts, and reviewed error.code/error.type. Payloads never carry raw exceptions or exception messages.
Runtime Diagnostics
Clients discover diagnostics as the additive ICSharpDbObservabilityClient capability; ICSharpDbClient is unchanged. Direct, HTTP, gRPC, and sharded implementations use the same immutable snapshot models and preserve schema version, capture time, server instance id, counter epoch, scope, availability, truncation, capacity, retention, and dropped-count metadata.
| REST route | gRPC operation | Result |
|---|---|---|
GET /api/diagnostics/runtime | GetRuntimeDiagnostics | Queries, connections, storage, WAL, maintenance, and health summary. |
GET /api/diagnostics/queries/active?maximumRecords=100 | GetActiveQueries | Capped active-query collection. |
GET /api/diagnostics/queries/recent?maximumRecords=100 | GetRecentQueries | Capped recent terminal-query collection. |
GET /api/diagnostics/queries/{operationId}/plan | GetQueryPlanDiagnostics | Bounded retained plan summary; never replays SQL. |
GET /api/diagnostics/sessions?maximumRecords=100 | GetSessions | Capped database and host-request session state. |
GET /api/diagnostics/queries/{operationId}/detail | GetQueryDetail | Separate captured-text request subject to capture and host authorization. |
Diagnostics calls suppress their own observation so polling does not recursively fill history. Available, Disabled, Unsupported, Denied, and Unavailable are different states; unavailable values are omitted rather than represented by a misleading zero. Cancellation is forwarded but cooperative, and no diagnostics route terminates a query or session.
OpenTelemetry Tracing
The activity source is CSharpDB, instrumentation version 1.0.0. Hosted sampling is parent-based over OpenTelemetry:SamplingRatio from 0 through 1. REST and gRPC server activities parent the logical database activity, and the engine adopts that context rather than creating a duplicate root.
Every activity uses ActivityKind.Internal and one low-cardinality name:
csharpdb.query csharpdb.script csharpdb.procedure
csharpdb.transaction csharpdb.database csharpdb.recovery
csharpdb.checkpoint csharpdb.backup csharpdb.restore
csharpdb.reindex csharpdb.vacuum csharpdb.maintenance
csharpdb.pipeline csharpdb.operationHosted resources publish service.name, optional service.namespace and service.version, an opaque process-lifetime service.instance.id, and deployment.environment.name. Start attributes are db.system.name=csharpdb, db.namespace, db.operation.name, csharpdb.schema.version, csharpdb.operation.id, csharpdb.operation.class, csharpdb.operation.role, csharpdb.transport, and csharpdb.database.alias. csharpdb.operation.parent_id, csharpdb.session.id, csharpdb.query.fingerprint, and csharpdb.maintenance.kind appear only when applicable.
Completion adds csharpdb.operation.outcome. Query completion may add csharpdb.query.rows_produced, csharpdb.query.rows_affected, csharpdb.query.queue_duration_ms, csharpdb.query.time_to_first_result_ms, and csharpdb.query.slow. Maintenance completion may add csharpdb.maintenance.completed_units, csharpdb.maintenance.total_units, csharpdb.maintenance.warning_count, and csharpdb.maintenance.error_count. Failed, canceled, and rejected work uses error status with only reviewed error.type and csharpdb.error.code.
Traces never attach statement text, even when logging capture is enabled. Startup WAL recovery and automatic foreground, background, and shutdown checkpoints create explicit-root physical spans from storage-captured start and completion times when tracing is enabled. Manual checkpoint, backup, and the checkpoint sub-step inside startup recovery reuse their logical parent and suppress a second physical checkpoint span. Path-only static restore, reindex, vacuum, and foreign-key operations lack a runtime owner; use database/client-owned operations when telemetry correlation is required.
Metrics and Prometheus
The meter is CSharpDB, instrumentation version 1.0.0. Counter means cumulative, up/down and gauge mean current value, and histogram records a distribution. Units use UCUM notation.
| Instruments (kind; unit) | Allowed dimensions |
|---|---|
csharpdb.requests (counter; {request}), csharpdb.statements (counter; {statement}), csharpdb.query.duration (histogram; s), csharpdb.rows.produced/csharpdb.rows.affected (counter; {row}), csharpdb.queries.slow (counter; {query}) | operation class, outcome, transport, alias |
csharpdb.queries.active (observable up/down; {query}) | alias |
csharpdb.transactions (counter; {transaction}), csharpdb.transaction.duration (histogram; s) | operation class, outcome, transport, alias |
csharpdb.transactions.active (observable up/down; {transaction}) | alias |
csharpdb.maintenance.operations (counter; {operation}), csharpdb.maintenance.duration (histogram; s) | operation class, outcome, transport, alias |
csharpdb.maintenance.active (observable up/down; {operation}) | operation class, alias |
csharpdb.checkpoints (counter; {checkpoint}), csharpdb.checkpoint.duration (histogram; s) | outcome, alias |
csharpdb.checkpoints.active (observable up/down; {checkpoint}), csharpdb.checkpoint.age (observable gauge; s) | alias |
csharpdb.wal.recoveries (counter; {recovery}), csharpdb.wal.recovery.duration (histogram; s) | outcome, alias |
csharpdb.wal.recoveries.active (observable up/down; {recovery}) | alias |
csharpdb.wal.commit.batch.size (histogram; {commit}) | alias |
csharpdb.storage.logical_bytes, csharpdb.storage.allocated_bytes (observable gauge; By), csharpdb.storage.page_count, csharpdb.storage.dirty_pages (observable gauge; {page}) | alias |
csharpdb.storage.page.reads, csharpdb.storage.page.writes, csharpdb.storage.cache.hits, csharpdb.storage.cache.misses (observable counter; {page}) | alias |
csharpdb.storage.bytes.read, csharpdb.storage.bytes.written (observable counter; By) | alias |
csharpdb.storage.readers.active (observable up/down; {reader}), csharpdb.storage.writers.active (observable up/down; {writer}) | alias |
csharpdb.storage.commits (observable counter; {commit}), csharpdb.storage.conflicts (observable counter; {conflict}) | alias |
csharpdb.wal.logical_bytes, csharpdb.wal.allocated_bytes, csharpdb.wal.committed_bytes, csharpdb.wal.retained_bytes (observable gauge; By), csharpdb.wal.frame_count (observable gauge; {frame}) | alias |
csharpdb.wal.commit_batches (observable counter; {batch}), csharpdb.wal.bytes.written (observable counter; By), csharpdb.wal.commits.flushed (observable counter; {commit}), csharpdb.wal.flushes (observable counter; {flush}), csharpdb.wal.group_commit.batches (observable counter; {batch}), csharpdb.wal.group_commit.commits (observable counter; {commit}) | alias |
csharpdb.wal.commits.pending (observable up/down; {commit}) | alias |
csharpdb.sessions.active (observable up/down; {session}), csharpdb.readers.active (observable up/down; {reader}), csharpdb.pool.waiters (observable up/down; {request}), csharpdb.connections.available (observable gauge; {connection}) | transport, alias |
csharpdb.pool.wait.duration (histogram; s) | outcome, transport, alias |
csharpdb.health.status (observable gauge; {status}) | health check, status, alias |
The exact metric tag-key allowlist is csharpdb.operation.class, csharpdb.operation.outcome, csharpdb.transport, csharpdb.database.alias, csharpdb.health.check, and csharpdb.status. SQL, fingerprints, operation/session/trace ids, object names, paths, exception data, and arbitrary user strings are prohibited dimensions.
Prometheus can be enabled without tracing. It uses the exact configured path on the normal Kestrel listener, emits cumulative counters, and disables exemplars so trace ids do not enter the pull surface. A custom path does not leave /metrics mapped.
Liveness and Readiness
GET /health/live answers whether the host process is serving. GET /health/ready answers whether the cached database-host state is ready for work. Both return only {"status":"healthy"} or {"status":"unhealthy"} with 200 or 503; they never query the database on each probe.
- Liveness remains healthy through database initialization failure and recovery retries.
- Readiness is unhealthy during startup, replacement/restore reopen verification, retryable recovery, and graceful shutdown.
- Readiness work respects
ReadinessTimeoutand never mutates data. - The daemon also exposes standard gRPC Health
Check/Watchfor the empty service name andcsharpdb.database. - Admin retains
/healthzas a shallow desktop-launch compatibility probe; use the separate readiness route for orchestration.
The detailed /api/diagnostics/health snapshot is not an anonymous orchestration probe and follows diagnostics access policy.
Admin Observability Workspace
Open Observability from Studio navigation or the command palette. The same component consumes the optional diagnostics client in direct, HTTP, gRPC, and sharded modes. It shows bounded overview samples, active/recent queries, sessions, storage/WAL, maintenance, health, and aggregate/per-shard availability.
Polling is serialized and runs only while the tab is active. Pause, manual refresh, snapshot age, stale state, truncation, dropped counts, and unavailable/denied/unsupported states remain visible. Samples reset when the database scope, server instance id, counter epoch, or a monotonic counter changes unexpectedly.
Raw SQL and server paths are absent from ordinary views. Query text requires a separate authorized reveal and is cleared when the tab hides, the database or scope changes, or the component is disposed. Deep physical Storage inspection is a separate explicit operation because it has different cost and privacy boundaries. See the Admin UI guide.
Privacy and Security
Query-text modes
| Mode | Behavior |
|---|---|
None (default) | No statement text is captured. Fingerprints and bounded plan summaries remain non-SQL identifiers/shape metadata. |
Normalized | Tokenizer-normalized SQL is an explicit opt-in. Treat it as potentially sensitive object/shape metadata; it is not a runnable Admin explain draft. |
Raw | May expose literals and identifiers. Hosts emit event 1003 at startup. Query detail still requires separate host authorization. |
Parameter values, row values, credentials, connection strings, file paths, raw exceptions, and exception messages are never built-in structured fields. Ordinary runtime, plan, session, storage, and WAL snapshots omit raw SQL and paths.
Remote access
- In API-key mode, diagnostics and Prometheus require the configured key. A missing or wrong key returns
401. - With security mode
None, diagnostics and Prometheus accept only the actual loopback peer. Forwarded-address headers do not grant access. AllowInsecureRemoteDiagnosticsandPrometheus:AllowInsecureRemoteAccessare explicit remote exceptions. Do not use them as substitutes for TLS, private networking, and authentication.- Sensitive query detail additionally requires
AllowSensitiveQueryDetailAccess=true. Policy denial returns403. - Keep OTLP headers and collector credentials in a protected environment or secret provider.
Retention, Capacity, and Overhead
| Bound | Default | Maximum |
|---|---|---|
| Active queries | 1,000 | 10,000 |
| Recent queries | 500 | 10,000 |
| Recent operations | 100 | 10,000 |
| History retention | 15 minutes | 7 days |
| Configured database/shard aliases | deployment-defined | 64 |
| Live runtime metric families | listener-driven | 64 |
Histories are process-local, bounded, and reset on restart. Capacity limits memory; retention removes old records. Drops and truncation are reported explicitly. Keep production capacities close to the operator question you need to answer rather than treating the host as a telemetry warehouse.
Disabled observability avoids runtime history and hosted signal setup. Enabled metrics-only mode does not install tracing. Tracing cost follows sampling and listener interest. Exporter failure is isolated from database results, but export delivery is not guaranteed during a collector outage; rely on bounded local diagnostics and the exporter/collector's own retry and queue telemetry.
Schema Compatibility and Deprecation
- Runtime snapshot schema:
1.1; built-in serializers and transports continue to accept supported1.0payloads. - Metric schema:
1.0; activity/meter instrumentation version:1.0.0. - Adding a new instrument or optional snapshot field is additive. Changing an existing metric name, kind, unit, meaning, or allowed dimensions requires an explicit schema-version and compatibility decision.
- Deprecations must be announced in release notes before removal. Consumers should ignore unknown additive fields and use explicit availability metadata rather than inferring support from missing or zero values.
- Never calculate a counter delta across a changed server instance id or counter epoch.
Troubleshooting
No traces or OTLP data
Verify master observability and OpenTelemetry:Enabled, a nonzero sampling ratio, and either Console:Enabled or Otlp:Enabled. Check the standard OTLP endpoint/protocol/header variables and collector reachability. An unavailable collector does not prevent host startup or database work, but it may delay or drop export.
/metrics returns 404, 401, or 403
404 means Prometheus is disabled or you requested the old path after configuring a custom one. 401 means API-key authentication failed. 403 means a non-loopback peer was rejected in security mode None. Prefer API-key mode plus TLS/private networking rather than insecure remote access.
Readiness remains unhealthy
Check the connection string, database-directory permissions, startup/recovery logs, active restore or exclusive maintenance, and the configured readiness timeout. Liveness can stay healthy while readiness correctly remains unhealthy.
Admin says unsupported, denied, unavailable, or stale
Unsupported means the selected client/host lacks the optional diagnostics capability. Denied means remote authorization rejected it. Unavailable means a supported producer could not provide that section. Stale data reports the last successful capture; refresh the active tab and verify connectivity. A database switch or reconnect intentionally clears old samples and revealed detail.
Raw SQL is missing
This is the safe default. Enable Logging:SqlText only after a data-handling review, then separately authorize query detail on the host. Normal polling and traces never return raw SQL.