Files
buzz/crates/buzz-datastore-tracing/src/lib.rs
Dave Grochowski 397796c5f3 feat(tracing): add PostgreSQL tracing spans (#3678)
## Why
Expose PostgreSQL datastore latency within existing request traces so
slow logical database operations can be identified without recording
tenant data or query arguments.

## What
- Add client spans around logical PostgreSQL operations across the
database facade, search, audit, replica fencing, and command persistence
- Use a dedicated `buzz_datastore` target and `db.system.name =
"postgresql"` for filtering and backend classification
- Exclude health-check database calls and scrub raw identifiers and
errors from newly traced paths

## Risk Assessment
Medium — this instruments frequently used datastore paths and increases
trace volume when enabled, but does not change SQL execution or
datastore behavior. Existing OpenTelemetry filtering controls export.

## References
- Pre-push clippy and fast unit-test hooks passed

Generated with Amp

---------

Signed-off-by: David Grochowski <dgrochowski@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-08-12 08:04:54 +10:00

169 lines
5.5 KiB
Rust

//! Policy-enforcing instrumentation for logical datastore operations.
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::{
parenthesized, parse_macro_input, Error, Ident, ItemFn, LitStr, Result, ReturnType, Token, Type,
};
struct DatastoreArgs {
name: LitStr,
system: LitStr,
fields: Option<TokenStream2>,
}
impl Parse for DatastoreArgs {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let mut name = None;
let mut system = None;
let mut fields = None;
while !input.is_empty() {
let key: Ident = input.parse()?;
match key.to_string().as_str() {
"name" => {
if name.is_some() {
return Err(Error::new(key.span(), "duplicate `name` argument"));
}
input.parse::<Token![=]>()?;
name = Some(input.parse()?);
}
"system" => {
if system.is_some() {
return Err(Error::new(key.span(), "duplicate `system` argument"));
}
input.parse::<Token![=]>()?;
system = Some(input.parse()?);
}
"fields" => {
if fields.is_some() {
return Err(Error::new(key.span(), "duplicate `fields` argument"));
}
let content;
parenthesized!(content in input);
fields = Some(content.parse()?);
}
_ => {
return Err(Error::new(
key.span(),
"expected `name`, `system`, or `fields`",
))
}
}
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(Self {
name: name.ok_or_else(|| Error::new(input.span(), "missing `name`"))?,
system: system.ok_or_else(|| Error::new(input.span(), "missing `system`"))?,
fields,
})
}
}
/// Instruments an async logical datastore operation according to Buzz policy.
///
/// PostgreSQL spans always omit function arguments, use the `buzz_datastore`
/// target, and expose only canonical semantic fields plus explicitly supplied
/// safe fields. An `Err` sets `otel.status_code` without inspecting the error.
#[proc_macro_attribute]
pub fn datastore_span(args: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as DatastoreArgs);
let mut function = parse_macro_input!(item as ItemFn);
if function.sig.asyncness.is_none() {
return Error::new_spanned(
function.sig.fn_token,
"`datastore_span` requires an async function",
)
.into_compile_error()
.into();
}
if args.system.value() != "postgresql" {
return Error::new_spanned(
args.system,
"unsupported datastore system; only `postgresql` is currently supported",
)
.into_compile_error()
.into();
}
let name = args.name;
let extra_fields = args.fields.map(|tokens| quote!(, #tokens));
function.attrs.push(syn::parse_quote!(
#[::tracing::instrument(
target = "buzz_datastore",
name = #name,
skip_all,
fields(
otel.kind = "client",
db.system.name = "postgresql",
otel.status_code = ::tracing::field::Empty
#extra_fields
)
)]
));
let return_type: Type = match &function.sig.output {
ReturnType::Type(_, ty) => *ty.clone(),
ReturnType::Default => syn::parse_quote!(()),
};
let returns_result = match &return_type {
Type::Path(path) => path
.path
.segments
.last()
.is_some_and(|segment| segment.ident == "Result"),
_ => false,
};
let original_body = function.block;
let result = format_ident!("__buzz_datastore_result_7f3a9c");
let record_error = returns_result.then(|| {
quote! {
if #result.is_err() {
::tracing::Span::current().record("otel.status_code", "ERROR");
}
}
});
function.block = Box::new(syn::parse_quote!({
let #result: #return_type = (async #original_body).await;
#record_error
#result
}));
quote!(#function).into()
}
#[cfg(test)]
mod tests {
use super::DatastoreArgs;
#[test]
fn parses_safe_tracing_fields() {
let args: DatastoreArgs = syn::parse_str(
r#"name = "audit", system = "postgresql", fields(action = %entry.action, from_seq = from_seq, limit = limit)"#,
)
.expect("valid arguments");
assert!(args.fields.is_some());
}
#[test]
fn rejects_duplicate_arguments() {
for args in [
r#"name = "one", name = "two", system = "postgresql""#,
r#"name = "one", system = "postgresql", system = "postgresql""#,
r#"name = "one", system = "postgresql", fields(a = 1), fields(b = 2)"#,
] {
let error = match syn::parse_str::<DatastoreArgs>(args) {
Ok(_) => panic!("duplicate accepted"),
Err(error) => error,
};
assert!(error.to_string().starts_with("duplicate `"));
}
}
}