Trait AuditSink
pub trait AuditSink:
Send
+ Sync
+ 'static {
// Required method
fn record<'life0, 'life1, 'async_trait>(
&'life0 self,
event: &'life1 AuditEvent,
) -> Pin<Box<dyn Future<Output = Result<(), CratestackError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait;
}Expand description
Pluggable audit sink. Implementations fan audit events out to
downstream systems (Kafka topics, Redis pubsub, HTTP webhooks, S3
buckets) for long-term retention or SIEM ingestion. The in-database
audit table written by cratestack_sqlx remains the canonical
record; sinks are best-effort projections.
Only the plain .run(ctx) write path invokes record
automatically. .run_in_tx(&mut tx, ctx) — and db.transaction(...)
composed on top of it — persist the cratestack_audit row exactly the
same way, but never call record themselves: they hand the
transaction back to a caller who owns its commit, so there is no
point inside cratestack_sqlx that reliably runs after that commit
succeeds. Instead, every run_in_tx variant returns a
RunInTxOutcome carrying the AuditEvent(s) it already persisted,
and the generated Cratestack::dispatch_audit_sink(&self, events) is
the caller’s explicit opt-in — call it once, after your own commit
succeeds (cratestack#534). A sink implementation that assumes
“every audited write reaches record” is assuming something the
framework does not guarantee for any write composed through
run_in_tx/db.transaction(...): skipping (or forgetting) the
dispatch call leaves the cratestack_audit row committed and
record never invoked, silently.
record must not panic. cratestack_sqlx’s dispatch call site
(dispatch_audit_sink) awaits record after the mutation’s
transaction has already committed, with no catch_unwind around
it: a panicking implementation unwinds into the caller of run()/
dispatch_audit_sink(). Under an async runtime like Tokio this is typically
caught at the task boundary rather than crashing the process, but
the in-flight HTTP response for that already-successful, possibly
non-idempotent mutation is lost — a client retrying on a dropped
connection can resubmit a write that already happened. Return
Err(CratestackError) for any failure instead; it is logged and
swallowed by design (sinks are best-effort), which panicking is not.
Required Methods§
fn record<'life0, 'life1, 'async_trait>(
&'life0 self,
event: &'life1 AuditEvent,
) -> Pin<Box<dyn Future<Output = Result<(), CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".