1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! Lightweight [`SQLx`](https://docs.rs/sqlx) wrapper that emits OpenTelemetry-native spans
//! and metrics following the [database client semantic conventions][semconv].
//!
//! `sqlx-otel` talks to the [`opentelemetry`] API directly – there is no `tracing` bridge
//! indirection. The wrapper is **zero-cost when no `TracerProvider` or `MeterProvider` is
//! installed** because the global `opentelemetry` API resolves to no-op instruments in that
//! configuration; the only overhead is one `Arc` clone per acquired connection or transaction.
//!
//! [semconv]: https://opentelemetry.io/docs/specs/semconv/database/
//!
//! # Quick start
//!
//! Wrap an existing `sqlx::Pool` with [`PoolBuilder`] and use the result anywhere a `sqlx::Pool`
//! is accepted – every operation through the wrapper is instrumented.
//!
//! ```no_run
//! # #[cfg(feature = "sqlite")]
//! # async fn _doc() -> Result<(), sqlx::Error> {
//! use sqlx_otel::PoolBuilder;
//!
//! // Wrap an existing sqlx pool. Connection-level attributes (host, port, db namespace) are
//! // auto-extracted from the underlying connect options.
//! let raw = sqlx::SqlitePool::connect(":memory:").await?;
//! let pool = PoolBuilder::from(raw).build();
//!
//! // Use it exactly like a sqlx pool – `&pool` is an `sqlx::Executor`.
//! let row: (i64,) = sqlx::query_as("SELECT 1").fetch_one(&pool).await?;
//! assert_eq!(row.0, 1);
//!
//! // Transactions work via `&mut tx` (note: not `&mut *tx`; the wrapper does not deref).
//! let mut tx = pool.begin().await?;
//! sqlx::query("CREATE TABLE users (name TEXT)")
//! .execute(&mut tx)
//! .await?;
//! tx.commit().await?;
//! # Ok(())
//! # }
//! ```
//!
//! Every operation through the pool emits a [`SpanKind::Client`][SpanKind] span, records the
//! operation duration, and tracks pool-level metrics. No code changes are required beyond the
//! one-line wrap.
//!
//! [SpanKind]: https://docs.rs/opentelemetry/latest/opentelemetry/trace/enum.SpanKind.html
//!
//! # Setting up an OpenTelemetry SDK
//!
//! `sqlx-otel` produces telemetry but **does not install a provider** – that is the application's
//! responsibility. Pair this crate with the [`opentelemetry_sdk`] (or any other compliant SDK) and
//! set up exporters for your traces and metrics. Until a provider is installed via
//! [`opentelemetry::global::set_tracer_provider`] / [`set_meter_provider`][m], the instrumentation
//! is a no-op.
//!
//! [`opentelemetry_sdk`]: https://docs.rs/opentelemetry_sdk
//! [m]: https://docs.rs/opentelemetry/latest/opentelemetry/global/fn.set_meter_provider.html
//!
//! # What gets emitted
//!
//! ## Spans
//!
//! Every [`sqlx::Executor`] method (`execute`, `fetch`, `fetch_all`, `fetch_one`, `fetch_optional`,
//! `fetch_many`, `execute_many`, `prepare`, `prepare_with`, `describe`) produces a
//! `SpanKind::Client` span. The span name follows the [semantic-convention hierarchy][naming]:
//! `db.query.summary` (when set) → `"{operation} {collection}"` → `"{operation}"` → `"{db.system.name}"`.
//!
//! [naming]: https://opentelemetry.io/docs/specs/semconv/database/database-spans/#name
//!
//! | Attribute | Source | Condition |
//! |-----------------------------|----------------------------------------------------------|-----------------------------|
//! | `db.system.name` | Backend (`"postgresql"`, `"sqlite"`, `"mysql"`) | Always |
//! | `db.namespace` | Database name extracted from connect options | When available |
//! | `server.address` | Hostname extracted from connect options | When available |
//! | `server.port` | Port extracted from connect options | When available |
//! | `network.peer.address` | Resolved IP address | When set via builder |
//! | `network.peer.port` | Resolved port | When set via builder |
//! | `db.query.text` | The SQL query string with inter-token whitespace collapsed | Unless [`QueryTextMode::Off`] |
//! | `db.operation.name` | Database operation (e.g. `SELECT`) | When [annotated] |
//! | `db.collection.name` | Target table or collection | When [annotated] |
//! | `db.query.summary` | Low-cardinality query summary | When [annotated] |
//! | `db.stored_procedure.name` | Stored procedure name | When [annotated] |
//! | `db.response.returned_rows` | Row count | On `fetch*` methods |
//! | `db.response.affected_rows` | Rows affected (`QueryResult::rows_affected()`) | On `execute` ([note](#a-note-on-dbresponseaffected_rows)) |
//! | `db.response.status_code` | SQLSTATE / driver error code | On database errors |
//! | `error.type` | Error variant name | On any error |
//!
//! [annotated]: #per-query-annotations
//!
//! On error, the span status is set to `Error` and an `exception` event is added carrying
//! `exception.type` and `exception.message`.
//!
//! ## Operation metrics
//!
//! | Instrument | Type | Unit | Description |
//! |------------------------------------|-----------|------|---------------------------------------|
//! | `db.client.operation.duration` | Histogram | `s` | Duration of each database operation |
//! | `db.client.response.returned_rows` | Histogram | | Number of rows returned per operation |
//!
//! These carry the connection-level attributes (`db.system.name`, `db.namespace`, `server.address`,
//! `server.port`).
//!
//! ## Connection-pool metrics
//!
//! | Instrument | Type | Unit | Description |
//! |-----------------------------------------|-----------------|------|------------------------------------------------------|
//! | `db.client.connection.wait_time` | Histogram | `s` | Time spent waiting for a connection in `acquire()` |
//! | `db.client.connection.use_time` | Histogram | `s` | Time a connection was held before being returned |
//! | `db.client.connection.timeouts` | Counter | | Number of acquire attempts that timed out |
//! | `db.client.connection.pending_requests` | `UpDownCounter` | | Number of callers currently waiting in `acquire()` |
//! | `db.client.connection.count` | Gauge | | Current connections by state (`idle` / `used`) |
//! | `db.client.connection.max` | Gauge | | Maximum number of connections allowed |
//! | `db.client.connection.idle.max` | Gauge | | Maximum idle connections (equals `max` in `SQLx`) |
//! | `db.client.connection.idle.min` | Gauge | | Configured minimum connections |
//!
//! The first four are recorded inline on every `acquire()` and connection drop – no sampling
//! gaps. `connection.count` is polled by a background task and requires both
//! [`PoolBuilder::with_pool_name`] and a runtime feature (`runtime-tokio` or
//! `runtime-async-std`); without either, the gauge is silent. The remaining three are static
//! gauges recorded once at [`PoolBuilder::build`].
//!
//! ## A note on `db.response.affected_rows`
//!
//! `db.response.affected_rows` is **not part of the OpenTelemetry semantic conventions** at
//! the time of writing; it is a custom attribute we find useful. It carries the
//! database-confirmed count from `QueryResult::rows_affected()` on every `execute()` call,
//! using the same connection-level attributes as the standard `db.response.returned_rows`.
//! It is not recorded for `execute_many`, which is [considered deprecated upstream][exec-many].
//!
//! [exec-many]: https://github.com/launchbadge/sqlx/issues/3108
//!
//! # Per-query annotations
//!
//! `sqlx-otel` does **not** parse SQL. The four per-query semantic-convention attributes –
//! `db.operation.name`, `db.collection.name`, `db.query.summary`, and
//! `db.stored_procedure.name` – are the caller's responsibility, supplied through the
//! annotation API. There are two equivalent surfaces depending on whether you prefer the
//! annotation to live next to the executor or next to the query:
//!
//! **Executor-side** ([`Pool::with_annotations`], [`PoolConnection::with_annotations`],
//! [`Transaction::with_annotations`]) returns a borrowed wrapper that is itself an
//! `sqlx::Executor`. Use it when the same query text is reused with one set of annotations,
//! or when you want to annotate `prepare` / `describe` flows.
//!
//! ```no_run
//! # #[cfg(feature = "sqlite")]
//! # async fn _doc() -> Result<(), sqlx::Error> {
//! # use sqlx_otel::PoolBuilder;
//! use sqlx::Executor as _; // brings `fetch_all` / `execute` into scope.
//! use sqlx_otel::QueryAnnotations;
//! # let pool = PoolBuilder::from(sqlx::SqlitePool::connect(":memory:").await?).build();
//!
//! pool.with_annotations(
//! QueryAnnotations::new()
//! .operation("SELECT")
//! .collection("users"),
//! )
//! .fetch_all("SELECT * FROM users")
//! .await?;
//!
//! // Shorthand for the common two-attribute case.
//! pool.with_operation("INSERT", "orders")
//! .execute("INSERT INTO orders (id) VALUES (1)")
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! **Query-side** ([`QueryAnnotateExt`]) attaches the annotation directly to the
//! `sqlx::query` / `sqlx::query_as` / `sqlx::query_scalar` builder. Use it when annotations
//! belong with the query text – the typical case. Works with `Query::map` /
//! `Query::try_map` and with the compile-time validated macro forms.
//!
//! ```no_run
//! # #[cfg(feature = "sqlite")]
//! # async fn _doc() -> Result<(), sqlx::Error> {
//! # use sqlx_otel::PoolBuilder;
//! use sqlx_otel::QueryAnnotateExt;
//! # let pool = PoolBuilder::from(sqlx::SqlitePool::connect(":memory:").await?).build();
//!
//! sqlx::query("INSERT INTO orders (user_id) VALUES (?)")
//! .bind(7_i64)
//! .with_operation("INSERT", "orders")
//! .execute(&pool)
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! See [`QueryAnnotations`] for the full set of fields and [`QueryAnnotateExt`] for the
//! query-side surface (including the three valid annotation positions on `Query::map`
//! chains).
//!
//! # Error handling
//!
//! All wrapper methods return `sqlx::Error` unchanged – there is no error wrapping or
//! retranslation. When an error surfaces, the active span's status is set to `Error`, an
//! `exception` event is added with `exception.type` and `exception.message`, and –
//! whenever the error is a [`sqlx::Error::Database`] with a SQLSTATE/driver code – the
//! `db.response.status_code` attribute is recorded. The instrumentation is otherwise
//! transparent: the caller sees exactly the same `Result` it would see without the wrapper.
//!
//! # Feature flags
//!
//! `sqlx-otel` ships with **no default features** – pick at least one backend.
//!
//! | Feature | Effect |
//! |---------------------|---------------------------------------------------------------------|
//! | `sqlite` | Enable the `sqlx::Sqlite` backend. |
//! | `postgres` | Enable the `sqlx::Postgres` backend. |
//! | `mysql` | Enable the `sqlx::MySql` backend. |
//! | `runtime-tokio` | Spawn the `db.client.connection.count` polling task with `tokio`. |
//! | `runtime-async-std` | Same as above, but with `async-std`. Ignored if `tokio` is enabled. |
//!
//! ```toml
//! [dependencies]
//! sqlx-otel = { version = "0.1", features = ["postgres", "runtime-tokio"] }
//! ```
//!
//! All operation- and pool-level metrics other than `db.client.connection.count` work
//! without a runtime feature.
//!
//! # MSRV
//!
//! Minimum supported Rust version: **1.85.0**.
pub
pub use ;
pub use QueryTextMode;
pub use PoolConnection;
pub use Database;
pub use ;
pub use ;
pub use Transaction;