Core Concepts
Metrics Collection
Pyranid exposes a MetricsCollector hook for low-cardinality database metrics. It is separate from StatementLogger: use the logger for diagnostics and SQL detail, and use metrics collectors for counters, gauges, and histograms.
Metrics collection is disabled by default. Collector failures are swallowed by Pyranid so metrics cannot change database behavior.
Core Configuration
Use MetricsCollector::inMemoryInstance() for in-process counters in tests or ad-hoc inspection:
MetricsCollector metricsCollector = MetricsCollector.inMemoryInstance();
Database database = Database.withDataSource(dataSource)
.metricsCollector(metricsCollector)
.build();
MetricsCollector.Snapshot snapshot = metricsCollector.snapshot().orElseThrow();
MetricsCollector metricsCollector = MetricsCollector.inMemoryInstance();
Database database = Database.withDataSource(dataSource)
.metricsCollector(metricsCollector)
.build();
MetricsCollector.Snapshot snapshot = metricsCollector.snapshot().orElseThrow();
Passing null or omitting Database.Builder::metricsCollector(...) disables metrics collection:
Database database = Database.withDataSource(dataSource)
.metricsCollector(null)
.build();
Database database = Database.withDataSource(dataSource)
.metricsCollector(null)
.build();
The collector is fixed at Database.Builder::build() time. To reconfigure metrics, build another Database.
OpenTelemetry
Use the optional pyranid-otel artifact to export through OpenTelemetry:
<dependency>
<groupId>com.pyranid</groupId>
<artifactId>pyranid-otel</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.pyranid</groupId>
<artifactId>pyranid-otel</artifactId>
<version>1.0.0</version>
</dependency>
OpenTelemetryMetricsCollector metricsCollector = OpenTelemetryMetricsCollector
.withOpenTelemetry(openTelemetry)
.poolName("primary")
.namespace("orders")
.recordCollectionName(false)
.build();
Database database = Database.withDataSource(dataSource)
.databaseType(DatabaseType.POSTGRESQL)
.metricsCollector(metricsCollector)
.build();
OpenTelemetryMetricsCollector metricsCollector = OpenTelemetryMetricsCollector
.withOpenTelemetry(openTelemetry)
.poolName("primary")
.namespace("orders")
.recordCollectionName(false)
.build();
Database database = Database.withDataSource(dataSource)
.databaseType(DatabaseType.POSTGRESQL)
.metricsCollector(metricsCollector)
.build();
poolName(null) or leaving it unset skips pool-name-gated connection metrics. namespace(null) omits db.namespace; Pyranid does not look up a namespace from JDBC metadata. recordCollectionName(false) is the default because collection/table names can be too high-cardinality for some deployments.
If first-use transaction metrics need a precise db.system.name, configure Database.Builder::databaseType(...). Otherwise lazy detection may report other_sql for transaction-only events before a statement has detected the database type.
OpenTelemetry Metrics
| Metric | Stability | Type | Unit | Notes |
|---|---|---|---|---|
db.client.operation.duration | Stable | Histogram | s | Statement operation duration |
db.client.response.returned_rows | Development | Histogram | {row} | Emitted when Pyranid observes returned rows |
db.client.connection.wait_time | Development | Histogram | s | Emitted only when poolName is configured |
db.client.connection.use_time | Development | Histogram | s | Connection hold time; not exact physical transaction lifetime |
Connection pool internals such as idle count, max count, pending requests, timeouts, and create time are intentionally not emitted by Pyranid. Use your pool or proxy exporter for those.
Pyranid Metrics
| Metric | Type | Unit |
|---|---|---|
pyranid.statement.preparation.duration | Histogram | s |
pyranid.statement.execution.duration | Histogram | s |
pyranid.statement.mapping.duration | Histogram | s |
pyranid.statement.errors | Counter | {statement} |
pyranid.statement.batch.size | Histogram | {statement} |
pyranid.statement.rows_affected | Histogram | {row} |
pyranid.transaction.closure.duration | Histogram | s |
pyranid.transaction.commit.duration | Histogram | s |
pyranid.transaction.rollback.duration | Histogram | s |
pyranid.transaction.physical.begin_failures | Counter | {transaction} |
pyranid.transaction.count | Counter | {transaction} |
pyranid.transaction.active | Up-down counter | {transaction} |
pyranid.savepoint.operations | Counter | {operation} |
pyranid.fetchstream.duration | Histogram | s |
pyranid.fetchstream.rows_consumed | Histogram | {row} |
pyranid.post_transaction.operations | Counter | {operation} |
Attribute Policy
OpenTelemetryMetricsCollector keeps default attributes bounded. It emits db.system.name, db.operation.name, configured db.namespace, SQLSTATE class as db.response.status_code on failures, and error.type using the immediate throwable class.
Statement::getId() from Query::id(...) is not emitted by the OTel collector. It remains available to custom collectors and StatementLogger.
Snapshot Semantics
MetricsCollector.Snapshot is counter-only and intended for tests or local inspection. MetricsCollector::reset() is best-effort; concurrent updates may race with reset operations. OpenTelemetryMetricsCollector::snapshot() returns Optional::empty() because OTel consumers read metrics through the SDK/exporter pipeline.

