#![cfg(feature = "sqlx")]
use crate::core::stats::{normalize_sql, QueryKind};
use crate::core::task_ctx::{mark, mark_latency};
use std::fmt;
use std::time::Instant;
use tracing::{span::Attributes, Event, Id, Subscriber};
use tracing_subscriber::{layer::Context, Layer};
struct SqlSpanData {
key: String,
started_at: Instant,
}
struct SqlVisitor {
sql: Option<String>,
}
impl SqlVisitor {
fn new() -> Self {
Self { sql: None }
}
}
impl tracing::field::Visit for SqlVisitor {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
match field.name() {
"db.statement" | "statement" | "message" => {
self.sql = Some(value.to_string());
}
_ => {}
}
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn fmt::Debug) {
if self.sql.is_none() && field.name() == "message" {
self.sql = Some(format!("{value:?}"));
}
}
}
pub struct MOFSqlEvents;
impl MOFSqlEvents {
pub fn new() -> Self {
MOFSqlEvents
}
}
impl<S> Layer<S> for MOFSqlEvents
where
S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
{
fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
let span = match ctx.span(id) {
Some(s) => s,
None => return,
};
let target = span.metadata().target();
if !target.starts_with("sqlx::query") {
return;
}
let mut vis = SqlVisitor::new();
attrs.record(&mut vis);
let raw_sql = vis.sql.unwrap_or_else(|| target.to_string());
let key = normalize_sql(&raw_sql);
span.extensions_mut().insert(SqlSpanData {
key: key.clone(),
started_at: Instant::now(),
});
tracing::debug!(
target = "MoniOF::sql",
query = %raw_sql,
normalized = %key,
"SQL started"
);
}
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
let span = match ctx.span(&id) {
Some(s) => s,
None => return,
};
let mut exts = span.extensions_mut();
if let Some(data) = exts.remove::<SqlSpanData>() {
let ms = data.started_at.elapsed().as_millis();
let key = data.key.clone();
mark(QueryKind::Sql, &key);
mark_latency(QueryKind::Sql, &key, ms);
tracing::info!(
target = "MoniOF::sql",
key = %key,
latency_ms = %ms,
"SQL completed"
);
}
}
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
let target = event.metadata().target();
if !target.starts_with("sqlx::query") {
return;
}
let mut vis = SqlVisitor::new();
event.record(&mut vis);
let raw_sql = vis.sql.unwrap_or_else(|| target.to_string());
let key = normalize_sql(&raw_sql);
mark(QueryKind::Sql, &key);
tracing::debug!(
target = "MoniOF::sql",
query = %raw_sql,
normalized = %key,
"SQL event-only mode"
);
}
}