Skip to main content

diesel/connection/
instrumentation.rs

1use alloc::boxed::Box;
2use core::fmt::{Debug, Display};
3use core::num::NonZeroU32;
4use core::ops::DerefMut;
5use downcast_rs::Downcast;
6
7#[cfg(feature = "std")]
8static GLOBAL_INSTRUMENTATION: std::sync::RwLock<fn() -> Option<Box<dyn Instrumentation>>> =
9    std::sync::RwLock::new(|| None);
10
11/// A helper trait for opaque query representations
12/// which allows to get a `Display` and `Debug`
13/// representation of the underlying type without
14/// exposing type specific details
15pub trait DebugQuery: Debug + Display {}
16
17impl<T, DB> DebugQuery for crate::query_builder::DebugQuery<'_, T, DB> where Self: Debug + Display {}
18
19/// A helper type that allows printing out str slices
20///
21/// This type is necessary because it's not possible
22/// to cast from a reference of a unsized type like `&str`
23/// to a reference of a trait object even if that
24/// type implements all necessary traits
25#[doc = " A helper type that allows printing out str slices"]
#[doc = ""]
#[doc = " This type is necessary because it\'s not possible"]
#[doc = " to cast from a reference of a unsized type like `&str`"]
#[doc = " to a reference of a trait object even if that"]
#[doc = " type implements all necessary traits"]
#[non_exhaustive]
pub struct StrQueryHelper<'query> {
    s: &'query str,
}#[diesel_derives::__diesel_public_if(
26    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
27)]
28pub(crate) struct StrQueryHelper<'query> {
29    s: &'query str,
30}
31
32impl<'query> StrQueryHelper<'query> {
33    /// Construct a new `StrQueryHelper`
34    #[doc = " Construct a new `StrQueryHelper`"]
pub fn new(s: &'query str) -> Self { Self { s } }#[diesel_derives::__diesel_public_if(
35        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
36    )]
37    #[cfg(any(
38        feature = "postgres",
39        feature = "__sqlite-shared",
40        feature = "mysql",
41        feature = "mariadb",
42        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
43    ))]
44    pub(crate) fn new(s: &'query str) -> Self {
45        Self { s }
46    }
47}
48
49impl Debug for StrQueryHelper<'_> {
50    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51        Debug::fmt(self.s, f)
52    }
53}
54
55impl Display for StrQueryHelper<'_> {
56    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57        Display::fmt(&self.s, f)
58    }
59}
60
61impl DebugQuery for StrQueryHelper<'_> {}
62
63/// This enum describes possible connection events
64/// that can be handled by an [`Instrumentation`] implementation
65///
66/// Some fields might contain sensitive information, like login
67/// details for the database.
68///
69/// Diesel does not guarantee that future versions will
70/// emit the same events in the same order or timing.
71/// In addition the output of the [`Debug`] and [`Display`]
72/// implementation of the enum itself and any of its fields
73/// is not guarantee to be stable.
74//
75// This type is carefully designed
76// to avoid any potential overhead by
77// taking references for all things
78// and by not performing any additional
79// work until required.
80// In addition it's carefully designed
81// not to be dependent on the actual backend
82// type, as that makes it easier to reuse
83// `Instrumentation` implementations in
84// a different context
85#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for InstrumentationEvent<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            InstrumentationEvent::StartEstablishConnection { url: __self_0 }
                =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "StartEstablishConnection", "url", &__self_0),
            InstrumentationEvent::FinishEstablishConnection {
                url: __self_0, error: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "FinishEstablishConnection", "url", __self_0, "error",
                    &__self_1),
            InstrumentationEvent::StartQuery { query: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "StartQuery", "query", &__self_0),
            InstrumentationEvent::CacheQuery { sql: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "CacheQuery", "sql", &__self_0),
            InstrumentationEvent::FinishQuery {
                query: __self_0, error: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "FinishQuery", "query", __self_0, "error", &__self_1),
            InstrumentationEvent::BeginTransaction { depth: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "BeginTransaction", "depth", &__self_0),
            InstrumentationEvent::CommitTransaction { depth: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "CommitTransaction", "depth", &__self_0),
            InstrumentationEvent::RollbackTransaction { depth: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "RollbackTransaction", "depth", &__self_0),
        }
    }
}Debug)]
86#[non_exhaustive]
87pub enum InstrumentationEvent<'a> {
88    /// An event emitted by before starting
89    /// establishing a new connection
90    #[non_exhaustive]
91    StartEstablishConnection {
92        /// The database url the connection
93        /// tries to connect to
94        ///
95        /// This might contain sensitive information
96        /// like the database password
97        url: &'a str,
98    },
99    /// An event emitted after establishing a
100    /// new connection
101    #[non_exhaustive]
102    FinishEstablishConnection {
103        /// The database url the connection
104        /// tries is connected to
105        ///
106        /// This might contain sensitive information
107        /// like the database password
108        url: &'a str,
109        /// An optional error if the connection failed
110        error: Option<&'a crate::result::ConnectionError>,
111    },
112    /// An event that is emitted before executing
113    /// a query
114    #[non_exhaustive]
115    StartQuery {
116        /// A opaque representation of the query
117        ///
118        /// This type implements [`Debug`] and [`Display`],
119        /// but should be considered otherwise as opaque.
120        ///
121        /// The exact output of the [`Debug`] and [`Display`]
122        /// implementation is not considered as part of the
123        /// stable API.
124        query: &'a dyn DebugQuery,
125    },
126    /// An event that is emitted when a query
127    /// is cached in the connection internal
128    /// prepared statement cache
129    #[non_exhaustive]
130    CacheQuery {
131        /// SQL string of the cached query
132        sql: &'a str,
133    },
134    /// An event that is emitted after executing
135    /// a query
136    #[non_exhaustive]
137    FinishQuery {
138        /// A opaque representation of the query
139        ///
140        /// This type implements [`Debug`] and [`Display`],
141        /// but should be considered otherwise as opaque.
142        ///
143        /// The exact output of the [`Debug`] and [`Display`]
144        /// implementation is not considered as part of the
145        /// stable API.
146        query: &'a dyn DebugQuery,
147        /// An optional error if the connection failed
148        error: Option<&'a crate::result::Error>,
149    },
150    /// An event that is emitted while
151    /// starting a new transaction
152    #[non_exhaustive]
153    BeginTransaction {
154        /// Transaction level of the newly started
155        /// transaction
156        depth: NonZeroU32,
157    },
158    /// An event that is emitted while
159    /// committing a transaction
160    #[non_exhaustive]
161    CommitTransaction {
162        /// Transaction level of the to be committed
163        /// transaction
164        depth: NonZeroU32,
165    },
166    /// An event that is emitted while
167    /// rolling back a transaction
168    #[non_exhaustive]
169    RollbackTransaction {
170        /// Transaction level of the to be rolled
171        /// back transaction
172        depth: NonZeroU32,
173    },
174}
175
176// these constructors exist to
177// keep `#[non_exhaustive]` on all the variants
178// and to gate the constructors on the unstable feature
179#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
180impl<'a> InstrumentationEvent<'a> {
181    /// Create a new `InstrumentationEvent::StartEstablishConnection` event
182    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
183    pub fn start_establish_connection(url: &'a str) -> Self {
184        Self::StartEstablishConnection { url }
185    }
186
187    /// Create a new `InstrumentationEvent::FinishEstablishConnection` event
188    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
189    pub fn finish_establish_connection(
190        url: &'a str,
191        error: Option<&'a crate::result::ConnectionError>,
192    ) -> Self {
193        Self::FinishEstablishConnection { url, error }
194    }
195
196    /// Create a new `InstrumentationEvent::StartQuery` event
197    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
198    pub fn start_query(query: &'a dyn DebugQuery) -> Self {
199        Self::StartQuery { query }
200    }
201
202    /// Create a new `InstrumentationEvent::CacheQuery` event
203    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
204    pub fn cache_query(sql: &'a str) -> Self {
205        Self::CacheQuery { sql }
206    }
207
208    /// Create a new `InstrumentationEvent::FinishQuery` event
209    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
210    pub fn finish_query(
211        query: &'a dyn DebugQuery,
212        error: Option<&'a crate::result::Error>,
213    ) -> Self {
214        Self::FinishQuery { query, error }
215    }
216
217    /// Create a new `InstrumentationEvent::BeginTransaction` event
218    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
219    pub fn begin_transaction(depth: NonZeroU32) -> Self {
220        Self::BeginTransaction { depth }
221    }
222
223    /// Create a new `InstrumentationEvent::RollbackTransaction` event
224    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
225    pub fn rollback_transaction(depth: NonZeroU32) -> Self {
226        Self::RollbackTransaction { depth }
227    }
228
229    /// Create a new `InstrumentationEvent::CommitTransaction` event
230    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
231    pub fn commit_transaction(depth: NonZeroU32) -> Self {
232        Self::CommitTransaction { depth }
233    }
234}
235
236/// A type that provides an connection `Instrumentation`
237///
238/// This trait is the basic building block for logging or
239/// otherwise instrumenting diesel connection types. It
240/// acts as callback that receives information about certain
241/// important connection states
242///
243/// For simple usages this trait is implemented for closures
244/// accepting a [`InstrumentationEvent`] as argument.
245///
246/// More complex usages and integrations with frameworks like
247/// `tracing` and `log` are supposed to be part of their own
248/// crates.
249pub trait Instrumentation: Downcast + Send + 'static {
250    /// The function that is invoked for each event
251    fn on_connection_event(&mut self, event: InstrumentationEvent<'_>);
252}
253impl dyn Instrumentation<> {
    /// Returns true if the trait object wraps an object of type `__T`.
    #[inline]
    pub fn is<__T: Instrumentation<>>(&self) -> bool {
        ::downcast_rs::Downcast::as_any(self).is::<__T>()
    }
    /// Returns a boxed object from a boxed trait object if the underlying object is of type
    /// `__T`. Returns the original boxed trait if it isn't.
    #[inline]
    pub fn downcast<__T: Instrumentation<>>(self:
            ::downcast_rs::__alloc::boxed::Box<Self>)
        ->
            ::downcast_rs::__std::result::Result<::downcast_rs::__alloc::boxed::Box<__T>,
            ::downcast_rs::__alloc::boxed::Box<Self>> {
        if self.is::<__T>() {
            Ok(::downcast_rs::Downcast::into_any(self).downcast::<__T>().unwrap())
        } else { Err(self) }
    }
    /// Returns an `Rc`-ed object from an `Rc`-ed trait object if the underlying object is of
    /// type `__T`. Returns the original `Rc`-ed trait if it isn't.
    #[inline]
    pub fn downcast_rc<__T: Instrumentation<>>(self:
            ::downcast_rs::__alloc::rc::Rc<Self>)
        ->
            ::downcast_rs::__std::result::Result<::downcast_rs::__alloc::rc::Rc<__T>,
            ::downcast_rs::__alloc::rc::Rc<Self>> {
        if self.is::<__T>() {
            Ok(::downcast_rs::Downcast::into_any_rc(self).downcast::<__T>().unwrap())
        } else { Err(self) }
    }
    /// Returns a reference to the object within the trait object if it is of type `__T`, or
    /// `None` if it isn't.
    #[inline]
    pub fn downcast_ref<__T: Instrumentation<>>(&self)
        -> ::downcast_rs::__std::option::Option<&__T> {
        ::downcast_rs::Downcast::as_any(self).downcast_ref::<__T>()
    }
    /// Returns a mutable reference to the object within the trait object if it is of type
    /// `__T`, or `None` if it isn't.
    #[inline]
    pub fn downcast_mut<__T: Instrumentation<>>(&mut self)
        -> ::downcast_rs::__std::option::Option<&mut __T> {
        ::downcast_rs::Downcast::as_any_mut(self).downcast_mut::<__T>()
    }
}downcast_rs::impl_downcast!(Instrumentation);
254
255/// Get an instance of the default [`Instrumentation`]
256///
257/// This function is mostly useful for crates implementing
258/// their own connection types
259pub fn get_default_instrumentation() -> Option<Box<dyn Instrumentation>> {
260    #[cfg(feature = "std")]
261    match GLOBAL_INSTRUMENTATION.read() {
262        Ok(f) => (*f)(),
263        Err(_) => None,
264    }
265    #[cfg(not(feature = "std"))]
266    None
267}
268
269/// Set a custom constructor for the default [`Instrumentation`]
270/// used by new connections
271///
272/// ```rust
273/// use diesel::connection::{set_default_instrumentation, Instrumentation, InstrumentationEvent};
274///
275/// // a simple logger that prints all events to stdout
276/// fn simple_logger() -> Option<Box<dyn Instrumentation>> {
277///     // we need the explicit argument type there due
278///     // to bugs in rustc
279///     Some(Box::new(|event: InstrumentationEvent<'_>| {
280///         println!("{event:?}")
281///     }))
282/// }
283///
284/// set_default_instrumentation(simple_logger);
285/// ```
286#[cfg(feature = "std")]
287pub fn set_default_instrumentation(
288    default: fn() -> Option<Box<dyn Instrumentation>>,
289) -> crate::QueryResult<()> {
290    match GLOBAL_INSTRUMENTATION.write() {
291        Ok(mut l) => {
292            *l = default;
293            Ok(())
294        }
295        Err(e) => Err(crate::result::Error::DatabaseError(
296            crate::result::DatabaseErrorKind::Unknown,
297            Box::new(e.to_string()),
298        )),
299    }
300}
301
302impl<F> Instrumentation for F
303where
304    F: FnMut(InstrumentationEvent<'_>) + Send + 'static,
305{
306    fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
307        (self)(event)
308    }
309}
310
311impl Instrumentation for Box<dyn Instrumentation> {
312    fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
313        self.deref_mut().on_connection_event(event)
314    }
315}
316
317impl<T> Instrumentation for Option<T>
318where
319    T: Instrumentation,
320{
321    fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
322        if let Some(i) = self {
323            i.on_connection_event(event)
324        }
325    }
326}
327
328#[doc = " An optional dyn instrumentation."]
#[doc = ""]
#[doc =
" For ease of use, this type implements [`Deref`] and [`DerefMut`] to `&dyn Instrumentation`,"]
#[doc =
" falling back to a no-op implementation if no instrumentation is set."]
#[doc = ""]
#[doc =
" The DynInstrumentation type is useful because without it we actually did tend to return"]
#[doc =
" (accidentally) `&mut Option<Box> as &mut dyn Instrumentation` from `connection.instrumentation()`,"]
#[doc =
" so downcasting would have to be done in these two steps by the user, which is counter-intuitive."]
#[non_exhaustive]
pub struct DynInstrumentation {
    #[doc = " zst"]
    no_instrumentation: NoInstrumentation,
    inner: Option<Box<dyn Instrumentation>>,
}#[diesel_derives::__diesel_public_if(
329    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
330)]
331#[cfg(any(
332    feature = "postgres",
333    feature = "__sqlite-shared",
334    feature = "mysql",
335    feature = "mariadb",
336    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
337))]
338/// An optional dyn instrumentation.
339///
340/// For ease of use, this type implements [`Deref`] and [`DerefMut`] to `&dyn Instrumentation`,
341/// falling back to a no-op implementation if no instrumentation is set.
342///
343/// The DynInstrumentation type is useful because without it we actually did tend to return
344/// (accidentally) `&mut Option<Box> as &mut dyn Instrumentation` from `connection.instrumentation()`,
345/// so downcasting would have to be done in these two steps by the user, which is counter-intuitive.
346pub(crate) struct DynInstrumentation {
347    /// zst
348    no_instrumentation: NoInstrumentation,
349    inner: Option<Box<dyn Instrumentation>>,
350}
351
352#[cfg(any(
353    feature = "postgres",
354    feature = "__sqlite-shared",
355    feature = "mysql",
356    feature = "mariadb",
357    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
358))]
359impl core::ops::Deref for DynInstrumentation {
360    type Target = dyn Instrumentation;
361
362    fn deref(&self) -> &Self::Target {
363        self.inner.as_deref().unwrap_or(&self.no_instrumentation)
364    }
365}
366
367#[cfg(any(
368    feature = "postgres",
369    feature = "__sqlite-shared",
370    feature = "mysql",
371    feature = "mariadb",
372    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
373))]
374impl DerefMut for DynInstrumentation {
375    fn deref_mut(&mut self) -> &mut Self::Target {
376        self.inner
377            .as_deref_mut()
378            .unwrap_or(&mut self.no_instrumentation)
379    }
380}
381
382#[cfg(any(
383    feature = "postgres",
384    feature = "__sqlite-shared",
385    feature = "mysql",
386    feature = "mariadb",
387    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
388))]
389impl DynInstrumentation {
390    /// Create a instance of the default instrumentation provider
391    #[doc = " Create a instance of the default instrumentation provider"]
pub fn default_instrumentation() -> Self {
    Self {
        inner: get_default_instrumentation(),
        no_instrumentation: NoInstrumentation,
    }
}#[diesel_derives::__diesel_public_if(
392        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
393    )]
394    #[cfg(any(
395        feature = "postgres",
396        feature = "__sqlite-shared",
397        feature = "mysql",
398        feature = "mariadb",
399        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
400    ))]
401    pub(crate) fn default_instrumentation() -> Self {
402        Self {
403            inner: get_default_instrumentation(),
404            no_instrumentation: NoInstrumentation,
405        }
406    }
407
408    /// Create a noop instrumentation provider instance
409    #[doc = " Create a noop instrumentation provider instance"]
pub fn none() -> Self {
    Self { inner: None, no_instrumentation: NoInstrumentation }
}#[diesel_derives::__diesel_public_if(
410        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
411    )]
412    #[cfg(any(
413        feature = "postgres",
414        feature = "__sqlite-shared",
415        feature = "mysql",
416        feature = "mariadb",
417        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
418    ))]
419    pub(crate) fn none() -> Self {
420        Self {
421            inner: None,
422            no_instrumentation: NoInstrumentation,
423        }
424    }
425
426    /// register an event with the given instrumentation implementation
427    #[doc = " register an event with the given instrumentation implementation"]
pub fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
    if let Some(inner) = self.inner.as_deref_mut() {
        inner.on_connection_event(event)
    }
}#[diesel_derives::__diesel_public_if(
428        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
429    )]
430    #[cfg(any(
431        feature = "postgres",
432        feature = "__sqlite-shared",
433        feature = "mysql",
434        feature = "mariadb",
435        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
436    ))]
437    pub(crate) fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
438        // This implementation is not necessary to be able to call this method on this object
439        // because of the already existing Deref impl.
440        // However it allows avoiding the dynamic dispatch to the stub value
441        if let Some(inner) = self.inner.as_deref_mut() {
442            inner.on_connection_event(event)
443        }
444    }
445}
446
447#[cfg(any(
448    feature = "postgres",
449    feature = "__sqlite-shared",
450    feature = "mysql",
451    feature = "mariadb",
452    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
453))]
454impl<I: Instrumentation> From<I> for DynInstrumentation {
455    fn from(instrumentation: I) -> Self {
456        Self {
457            inner: Some(unpack_instrumentation(Box::new(instrumentation))),
458            no_instrumentation: NoInstrumentation,
459        }
460    }
461}
462#[cfg(any(
463    feature = "postgres",
464    feature = "__sqlite-shared",
465    feature = "mysql",
466    feature = "mariadb",
467    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
468))]
469struct NoInstrumentation;
470
471#[cfg(any(
472    feature = "postgres",
473    feature = "__sqlite-shared",
474    feature = "mysql",
475    feature = "mariadb",
476    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
477))]
478impl Instrumentation for NoInstrumentation {
479    fn on_connection_event(&mut self, _: InstrumentationEvent<'_>) {}
480}
481
482/// Unwrap unnecessary boxing levels
483#[cfg(any(
484    feature = "postgres",
485    feature = "__sqlite-shared",
486    feature = "mysql",
487    feature = "mariadb",
488    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
489))]
490fn unpack_instrumentation(
491    mut instrumentation: Box<dyn Instrumentation>,
492) -> Box<dyn Instrumentation> {
493    loop {
494        match instrumentation.downcast::<Box<dyn Instrumentation>>() {
495            Ok(extra_boxed_instrumentation) => instrumentation = *extra_boxed_instrumentation,
496            Err(not_extra_boxed_instrumentation) => {
497                break not_extra_boxed_instrumentation;
498            }
499        }
500    }
501}