Rust SDK · Recorder

With the recorder constructed and the middleware wired, your handlers can record events. The examples below are lifted from the secrets-vault examples repo.

Simple write

Set fields on CurrentEvent; the middleware records on response finish. Handlers that don't set action are skipped entirely (no audit noise):

use everscribe::axum::CurrentEvent;
use everscribe::event::Target;

async fn reveal(State(v): State<Vault>, ev: CurrentEvent, Path(id): Path<String>) -> Response {
    ev.with(|e| e.target = Target::new("secret", &id));

    let Some(s) = v.reveal(&id) else {
        return (StatusCode::NOT_FOUND, "not found").into_response();
    };

    ev.with(|e| {
        e.action = "secret.reveal".into();
        e.with_field("name", &s.name);
    });
    Json(json!({ "value": s.value })).into_response()
}

The middleware auto-fills result from the response status, so error and success paths both record correctly without you setting result by hand.

Mutation with before/after diff

For events that change a resource, populate change so the audit trail shows what changed:

async fn rotate(State(v): State<Vault>, ev: CurrentEvent, Path(id): Path<String>, Json(body): Json<Value>) -> Response {
    ev.with(|e| e.target = Target::new("secret", &id));

    let before = snapshot(&v.get(&id));   // pre-mutation state
    v.rotate(&id, body["value"].as_str().unwrap_or_default());
    let after = snapshot(&v.get(&id));    // post-mutation state

    ev.with(|e| {
        e.action = "secret.rotate".into();
        e.diff(&before, &after);          // populates change with the diff
    });
    StatusCode::NO_CONTENT.into_response()
}

before/after are anything that implements serde::Serialize. The diff helper JSON-normalizes both and stores them; the audit-log API computes the patch on ingest.

Redacted fields

diff_redacted scrubs sensitive JSON Pointer paths with "[REDACTED]" before storage:

ev.with(|e| {
    e.diff_redacted(&before, &after, &["/password_hash", "/api_keys/0"]);
});

Or pre-redact inside your snapshot function so plaintext never reaches the SDK — the vault examples take that approach (value is always "[REDACTED]").

Batch recording

The buffered recorder batches under the hood. record() enqueues; a background task sends batches every flush_interval or when flush_size is reached. Tune via RecorderOptions:

use everscribe::recorder::RecorderOptions;
use std::time::Duration;

let rec = es.new_recorder(RecorderOptions {
    buffer_size: 5000,
    flush_size: 500,
    flush_interval: Duration::from_secs(2),
    ..Default::default()
});

For graceful shutdown, drain the buffer before exit:

rec.close().await?;  // flushes pending events, then disposes

Multiple events per request

Some handlers produce more than one event. Build each Event directly with Event::new, copy the actor over, and record it explicitly; the recorder batches them on the next flush.

use everscribe::event::{Event, Target};

// `rec` constructed at boot; `actor` came off the request.
for uid in user_ids {
    let mut e = Event::new("secret.share");
    e.actor = actor.clone();
    e.target = Target::new("secret", &id);
    e.with_field("shared_with", &uid);
    rec.record(e).await.ok();
}

Direct recording

Not every event comes from an HTTP request. Cron jobs, queue workers, startup hooks. Build the event yourself: set actor to a service identity and set outcome by hand since there's no response status to derive it from.

use everscribe::event::{Actor, Event, Outcome, Target};

async fn rotate_expiring_secrets(rec: &everscribe::recorder::BufferedRecorder, vault: &Vault) {
    for s in vault.due_for_rotation() {
        let mut e = Event::new("secret.rotate");
        e.actor = Actor { r#type: "service".into(), id: "rotation-worker".into(), ..Default::default() };
        e.target = Target::new("secret", &s.id);
        e.outcome = Outcome { status: "ok".into(), ..Default::default() };
        rec.record(e).await.ok();
    }
}

The same recorder instance backs both your request handlers and your background jobs. Construct one at boot, share it, and close() it once on shutdown.