Skip to main content

AuditSink

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".

Implementors§