SBproxy events

July 9, 2026 ยท View on GitHub

Last modified: 2026-07-09

SBproxy has a small in-process event bus in sbproxy-observe. It defines a closed set of typed lifecycle events and a bus that code-level embedders publish to and register handler closures against. The shipped sbproxy binary does not publish to a global bus today; its own request telemetry flows through the sbproxy_* Prometheus metrics, the access log, and the request-event sink. The bus is the seam for embedders building on the workspace crates. Nothing crosses the process boundary; OSS has no webhook, file, or Lua sink.

Event types

ProxyEvent::event_type is the closed enum below. Variants serialise to snake_case JSON.

NameWhen
request_startedA new request entered the pipeline.
request_completedThe request finished without an error.
request_errorThe request terminated with an error.
auth_deniedAuthentication rejected the request.
policy_deniedA policy (rate limit, IP filter, WAF, request limit) blocked the request.
cache_hitA response was served from the response cache.
cache_missThe cache lookup found no usable entry.
provider_selectedAn AI provider was chosen for routing.
budget_exceededAn AI spend or quota budget was exhausted.
guardrail_triggeredAn AI guardrail flagged or blocked content.
config_reloadedThe proxy configuration reloaded successfully.

Circuit-breaker activity is a metric in OSS (sbproxy_circuit_breaker_transitions_total), not an event. See metrics-stability.md.

The three AI variants (provider_selected, budget_exceeded, guardrail_triggered) name AI-gateway moments, but the shipped AI path records those through the sbproxy_ai_* metrics and the usage ledger rather than through this bus. Use the enum variants when your embedding code publishes its own events; use the metrics and ledger when you want the gateway's built-in accounting.

Event shape

pub struct ProxyEvent {
    pub event_type: EventType,
    pub hostname: String,
    pub tenant_id: String,         // empty when no tenant resolved
    pub timestamp: u64,            // Unix epoch milliseconds
    pub data: serde_json::Value,   // event-specific payload
}

data is a free-form JSON map; keys vary per event. The event carries a tenant_id (empty string in single-tenant deployments) so handlers can filter per tenant without parsing data. The bus does not stamp severity, workspace_id, or tags; derive those from data in your handler.

Subscribing programmatically

Each EventBus::subscribe call binds a closure to one event type. Publishers fan out to all bound closures synchronously, in the order they registered.

use sbproxy_observe::events::{EventBus, EventType, ProxyEvent};

let bus = EventBus::new();

bus.subscribe(EventType::BudgetExceeded, Box::new(|event: &ProxyEvent| {
    eprintln!("budget tripped on {}: {}", event.hostname, event.data);
}));

bus.subscribe(EventType::ConfigReloaded, Box::new(|_| {
    metrics::counter!("config_reload_total").increment(1);
}));

Handlers run on the publisher's thread, so a slow or panicking handler stalls the request that emitted the event. Keep the body short and offload long work onto a queue you push to from the closure.

No events: YAML block

The OSS bus is a code-level extension point, so there is no events: config. Webhook, file, and Lua sinks are tracked under the enterprise roadmap; the YAML block lands with them.

See also