Skip to main content

sqlx_tracing/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::sync::Arc;
4
5mod connection;
6mod pool;
7pub mod prelude;
8pub(crate) mod span;
9mod transaction;
10
11#[cfg(feature = "postgres")]
12pub mod postgres;
13
14#[cfg(feature = "sqlite")]
15pub mod sqlite;
16
17#[cfg(feature = "mysql")]
18pub mod mysql;
19
20/// Attributes describing the database connection and context.
21/// Used for span enrichment and attribute propagation.
22#[derive(Debug, Default)]
23struct Attributes {
24    name: Option<String>,
25    host: Option<String>,
26    port: Option<u16>,
27    database: Option<String>,
28}
29
30/// Builder for constructing a [`Pool`] with custom attributes.
31///
32/// Allows setting database name, host, port, and other identifying information
33/// for tracing purposes.
34#[derive(Debug)]
35pub struct PoolBuilder<DB: sqlx::Database> {
36    pool: sqlx::Pool<DB>,
37    attributes: Attributes,
38}
39
40// URL-based attribute extraction — works for TCP-backed drivers (postgres, mysql).
41// Sqlite has its own impl below because `to_url_lossy()` panics on sqlite options.
42#[cfg(feature = "postgres")]
43impl From<sqlx::Pool<sqlx::Postgres>> for PoolBuilder<sqlx::Postgres> {
44    /// Create a new builder from an existing SQLx pool.
45    fn from(pool: sqlx::Pool<sqlx::Postgres>) -> Self {
46        use sqlx::ConnectOptions;
47
48        let url = pool.connect_options().to_url_lossy();
49        let attributes = Attributes {
50            name: None,
51            host: url.host_str().map(String::from),
52            port: url.port(),
53            database: url
54                .path_segments()
55                .and_then(|mut segments| segments.next().map(String::from)),
56        };
57        Self { pool, attributes }
58    }
59}
60
61#[cfg(feature = "sqlite")]
62impl From<sqlx::Pool<sqlx::Sqlite>> for PoolBuilder<sqlx::Sqlite> {
63    /// Create a new builder from an existing SQLx pool.
64    fn from(pool: sqlx::Pool<sqlx::Sqlite>) -> Self {
65        let attributes = Attributes {
66            name: None,
67            host: pool
68                .connect_options()
69                .get_filename()
70                .to_str()
71                .map(String::from),
72            port: None,
73            database: None,
74        };
75        Self { pool, attributes }
76    }
77}
78
79#[cfg(feature = "mysql")]
80impl From<sqlx::Pool<sqlx::MySql>> for PoolBuilder<sqlx::MySql> {
81    /// Create a new builder from an existing SQLx pool.
82    fn from(pool: sqlx::Pool<sqlx::MySql>) -> Self {
83        use sqlx::ConnectOptions;
84
85        let url = pool.connect_options().to_url_lossy();
86        let attributes = Attributes {
87            name: None,
88            host: url.host_str().map(String::from),
89            port: url.port(),
90            database: url
91                .path_segments()
92                .and_then(|mut segments| segments.next().map(String::from)),
93        };
94        Self { pool, attributes }
95    }
96}
97
98impl<DB: sqlx::Database> PoolBuilder<DB> {
99    /// Set a custom name for the pool (for peer.service attribute).
100    pub fn with_name(mut self, name: impl Into<String>) -> Self {
101        self.attributes.name = Some(name.into());
102        self
103    }
104
105    /// Set the database name attribute.
106    pub fn with_database(mut self, database: impl Into<String>) -> Self {
107        self.attributes.database = Some(database.into());
108        self
109    }
110
111    /// Set the host attribute.
112    pub fn with_host(mut self, host: impl Into<String>) -> Self {
113        self.attributes.host = Some(host.into());
114        self
115    }
116
117    /// Set the port attribute.
118    pub fn with_port(mut self, port: u16) -> Self {
119        self.attributes.port = Some(port);
120        self
121    }
122
123    /// Build the [`Pool`] with the configured attributes.
124    pub fn build(self) -> Pool<DB> {
125        Pool {
126            inner: self.pool,
127            attributes: Arc::new(self.attributes),
128        }
129    }
130}
131
132/// An asynchronous pool of SQLx database connections with tracing instrumentation.
133///
134/// Wraps a SQLx [`Pool`] and propagates tracing attributes to all acquired connections.
135#[derive(Debug)]
136pub struct Pool<DB>
137where
138    DB: sqlx::Database,
139{
140    inner: sqlx::Pool<DB>,
141    attributes: Arc<Attributes>,
142}
143
144impl<DB> Clone for Pool<DB>
145where
146    DB: sqlx::Database,
147{
148    fn clone(&self) -> Self {
149        Self {
150            inner: self.inner.clone(),
151            attributes: self.attributes.clone(),
152        }
153    }
154}
155
156impl<DB> From<sqlx::Pool<DB>> for Pool<DB>
157where
158    DB: sqlx::Database,
159    PoolBuilder<DB>: From<sqlx::Pool<DB>>,
160{
161    /// Convert a SQLx [`Pool`] into a tracing-instrumented [`Pool`].
162    fn from(inner: sqlx::Pool<DB>) -> Self {
163        PoolBuilder::from(inner).build()
164    }
165}
166
167impl<DB> Pool<DB>
168where
169    DB: sqlx::Database,
170{
171    /// Retrieves a connection and immediately begins a new transaction.
172    ///
173    /// The returned [`Transaction`] is instrumented for tracing.
174    pub async fn begin<'c>(&'c self) -> Result<Transaction<'c, DB>, sqlx::Error> {
175        self.inner.begin().await.map(|inner| Transaction {
176            inner,
177            attributes: self.attributes.clone(),
178        })
179    }
180
181    /// Attempts to retrieve a connection and immediately begins a new transaction if successful.
182    ///
183    /// The returned [`Transaction`] is instrumented for tracing.
184    pub async fn try_begin<'c>(&'c self) -> Result<Option<Transaction<'c, DB>>, sqlx::Error> {
185        self.inner.try_begin().await.map(|t| {
186            t.map(|inner| Transaction {
187                inner,
188                attributes: self.attributes.clone(),
189            })
190        })
191    }
192
193    /// Acquires a pooled connection, instrumented for tracing.
194    pub async fn acquire(&self) -> Result<PoolConnection<DB>, sqlx::Error> {
195        self.inner.acquire().await.map(|inner| PoolConnection {
196            attributes: self.attributes.clone(),
197            inner,
198        })
199    }
200}
201
202/// Wrapper for a mutable SQLx connection reference with tracing attributes.
203///
204/// Used internally for transaction and pool connection executors.
205pub struct Connection<'c, DB>
206where
207    DB: sqlx::Database,
208{
209    inner: &'c mut DB::Connection,
210    attributes: Arc<Attributes>,
211}
212
213impl<'c, DB: sqlx::Database> std::fmt::Debug for Connection<'c, DB> {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        f.debug_struct("Connection").finish_non_exhaustive()
216    }
217}
218
219/// A pooled SQLx connection instrumented for tracing.
220///
221/// Implements [`sqlx::Executor`] and propagates tracing attributes.
222#[derive(Debug)]
223pub struct PoolConnection<DB>
224where
225    DB: sqlx::Database,
226{
227    inner: sqlx::pool::PoolConnection<DB>,
228    attributes: Arc<Attributes>,
229}
230
231/// An in-progress database transaction or savepoint, instrumented for tracing.
232///
233/// Wraps a SQLx [`Transaction`] and propagates tracing attributes.
234#[derive(Debug)]
235pub struct Transaction<'c, DB>
236where
237    DB: sqlx::Database,
238{
239    inner: sqlx::Transaction<'c, DB>,
240    attributes: Arc<Attributes>,
241}