#[cfg(feature = "tracing-spans")]
mod inner {
use crate::DbBackend;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DbOperation {
Select,
Insert,
Update,
Delete,
Execute,
}
impl DbOperation {
pub fn from_sql(sql: &str) -> Self {
let first_word = sql.trim_start().split_whitespace().next().unwrap_or("");
if first_word.eq_ignore_ascii_case("SELECT") {
DbOperation::Select
} else if first_word.eq_ignore_ascii_case("INSERT") {
DbOperation::Insert
} else if first_word.eq_ignore_ascii_case("UPDATE") {
DbOperation::Update
} else if first_word.eq_ignore_ascii_case("DELETE") {
DbOperation::Delete
} else {
DbOperation::Execute
}
}
}
impl std::fmt::Display for DbOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DbOperation::Select => write!(f, "SELECT"),
DbOperation::Insert => write!(f, "INSERT"),
DbOperation::Update => write!(f, "UPDATE"),
DbOperation::Delete => write!(f, "DELETE"),
DbOperation::Execute => write!(f, "EXECUTE"),
}
}
}
pub(crate) fn db_system_name(backend: DbBackend) -> &'static str {
match backend {
DbBackend::Postgres => "postgresql",
DbBackend::MySql => "mysql",
DbBackend::Sqlite => "sqlite",
}
}
pub(crate) fn record_query_result<T, E: std::fmt::Display>(
span: &tracing::Span,
result: &Result<T, E>,
) {
match result {
Ok(_) => {
span.record("otel.status_code", "OK");
}
Err(e) => {
span.record("otel.status_code", "ERROR");
span.record("exception.message", tracing::field::display(e));
}
}
}
}
#[cfg(feature = "tracing-spans")]
pub(crate) use inner::*;
#[cfg(feature = "tracing-spans")]
macro_rules! db_span {
($name:expr, $backend:expr, $sql:expr) => {{
let sql: &str = $sql;
let op = $crate::database::tracing_spans::DbOperation::from_sql(sql);
::tracing::info_span!(
$name,
otel.kind = "client",
db.system = $crate::database::tracing_spans::db_system_name($backend),
db.operation = %op,
db.statement = ::tracing::field::Empty,
otel.status_code = ::tracing::field::Empty,
exception.message = ::tracing::field::Empty,
)
}};
}
#[cfg(feature = "tracing-spans")]
pub(crate) use db_span;
macro_rules! with_db_span {
($name:expr, $backend:expr, $sql:expr, record_stmt = $record_stmt:expr, $fut:expr) => {{
#[cfg(all(feature = "tracing-spans", not(feature = "sync")))]
{
let span = $crate::database::tracing_spans::db_span!($name, $backend, $sql);
if $record_stmt {
span.record("db.statement", $sql);
}
let result = ::tracing::Instrument::instrument($fut, span.clone());
$crate::database::tracing_spans::record_query_result(&span, &result);
result
}
#[cfg(all(feature = "tracing-spans", feature = "sync"))]
{
let span = $crate::database::tracing_spans::db_span!($name, $backend, $sql);
if $record_stmt {
span.record("db.statement", $sql);
}
span.in_scope($fut)
}
#[cfg(not(feature = "tracing-spans"))]
{
$fut
}
}};
}
pub(crate) use with_db_span;
#[cfg(feature = "tracing-spans")]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_db_operation_from_sql() {
assert_eq!(
DbOperation::from_sql("SELECT * FROM users"),
DbOperation::Select
);
assert_eq!(
DbOperation::from_sql(" SELECT * FROM users"),
DbOperation::Select
);
assert_eq!(
DbOperation::from_sql("select * from users"),
DbOperation::Select
);
assert_eq!(
DbOperation::from_sql("INSERT INTO users"),
DbOperation::Insert
);
assert_eq!(
DbOperation::from_sql("UPDATE users SET"),
DbOperation::Update
);
assert_eq!(
DbOperation::from_sql("DELETE FROM users"),
DbOperation::Delete
);
assert_eq!(
DbOperation::from_sql("CREATE TABLE users"),
DbOperation::Execute
);
assert_eq!(
DbOperation::from_sql("DROP TABLE users"),
DbOperation::Execute
);
}
#[test]
fn test_db_system_name() {
assert_eq!(db_system_name(DbBackend::Postgres), "postgresql");
assert_eq!(db_system_name(DbBackend::MySql), "mysql");
assert_eq!(db_system_name(DbBackend::Sqlite), "sqlite");
}
#[test]
fn test_db_operation_display() {
assert_eq!(DbOperation::Select.to_string(), "SELECT");
assert_eq!(DbOperation::Insert.to_string(), "INSERT");
assert_eq!(DbOperation::Update.to_string(), "UPDATE");
assert_eq!(DbOperation::Delete.to_string(), "DELETE");
assert_eq!(DbOperation::Execute.to_string(), "EXECUTE");
}
}