sqlx_tracing/span.rs
1/// Macro to create a tracing span for a SQLx operation with OpenTelemetry-compatible fields.
2///
3/// - `$name`: The operation name (e.g., "sqlx.execute").
4/// - `$statement`: The SQL statement being executed.
5/// - `$attributes`: Connection or pool attributes for peer and db context.
6///
7/// This macro is used internally by the crate to instrument all major SQLx operations.
8#[macro_export]
9macro_rules! instrument {
10 ($name:expr, $statement:expr, $attributes:expr) => {
11 tracing::info_span!(
12 $name,
13 // Database name (if available)
14 "db.name" = $attributes.database,
15 // Operation type (filled by SQLx or left empty)
16 "db.operation" = ::tracing::field::Empty,
17 // The SQL query text
18 "db.query.text" = $statement,
19 // Number of affected rows (to be filled after execution)
20 "db.response.affected_rows" = ::tracing::field::Empty,
21 // Number of returned rows (to be filled after execution)
22 "db.response.returned_rows" = ::tracing::field::Empty,
23 // Status code of the response (to be filled after execution)
24 "db.response.status_code" = ::tracing::field::Empty,
25 // Table name (optional, left empty)
26 "db.sql.table" = ::tracing::field::Empty,
27 // Database system (e.g., "postgresql", "sqlite")
28 "db.system.name" = DB::SYSTEM,
29 // Error type, message, and stacktrace (to be filled on error)
30 "error.type" = ::tracing::field::Empty,
31 "error.message" = ::tracing::field::Empty,
32 "error.stacktrace" = ::tracing::field::Empty,
33 // Peer (server) host and port
34 "net.peer.name" = $attributes.host,
35 "net.peer.port" = $attributes.port,
36 // OpenTelemetry semantic fields
37 "otel.kind" = "client",
38 "otel.status_code" = ::tracing::field::Empty,
39 "otel.status_description" = ::tracing::field::Empty,
40 // Peer service name (if set)
41 "peer.service" = $attributes.name,
42 )
43 };
44}
45
46/// Records that a single row was returned in the current tracing span.
47/// Used for fetch_one operations.
48pub fn record_one<T>(_value: &T) {
49 let span = tracing::Span::current();
50 span.record("db.response.returned_rows", 1);
51}
52
53/// Records whether an optional row was returned in the current tracing span.
54/// Used for fetch_optional operations.
55pub fn record_optional<T>(value: &Option<T>) {
56 let span = tracing::Span::current();
57 span.record(
58 "db.response.returned_rows",
59 if value.is_some() { 1 } else { 0 },
60 );
61}
62
63/// Records error details in the current tracing span for a SQLx error.
64/// Sets OpenTelemetry status and error fields for observability backends.
65pub fn record_error(err: &sqlx::Error) {
66 let span = tracing::Span::current();
67 // Mark the span as an error for OpenTelemetry
68 span.record("otel.status_code", "error");
69 span.record("otel.status_description", err.to_string());
70 // Classify error type as client or server
71 match err {
72 sqlx::Error::ColumnIndexOutOfBounds { .. }
73 | sqlx::Error::ColumnDecode { .. }
74 | sqlx::Error::ColumnNotFound(_)
75 | sqlx::Error::Decode { .. }
76 | sqlx::Error::Encode { .. }
77 | sqlx::Error::RowNotFound
78 | sqlx::Error::TypeNotFound { .. } => {
79 span.record("error.type", "client");
80 }
81 _ => {
82 span.record("error.type", "server");
83 }
84 }
85 // Attach error message and stacktrace for debugging
86 span.record("error.message", err.to_string());
87 span.record("error.stacktrace", format!("{err:?}"));
88}