use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::error::Error;
use crate::event::{EventKind, ObservabilityEvent, SCHEMA_VERSION};
pub const EVENT_TARGET: &str = "rig_tap";
static TICK: AtomicU64 = AtomicU64::new(0);
pub fn next_tick() -> u64 {
TICK.fetch_add(1, Ordering::Relaxed)
}
pub fn now_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
.unwrap_or(0)
}
pub fn current_span_id() -> Option<u64> {
tracing::Span::current().id().map(|id| id.into_u64())
}
pub fn build_event(conversation_id: impl Into<String>, kind: EventKind) -> ObservabilityEvent {
ObservabilityEvent {
version: SCHEMA_VERSION,
occurred_at_millis: now_millis(),
tick: next_tick(),
conversation_id: conversation_id.into(),
span_id: current_span_id(),
kind,
}
}
pub fn try_emit(event: &ObservabilityEvent) -> Result<(), Error> {
let json = serde_json::to_string(event)?;
let fields = event.kind.scalar_fields();
tracing::info!(
target: EVENT_TARGET,
event = %json,
rig_tap.version = event.version,
rig_tap.kind = event.kind.discriminant(),
rig_tap.conversation_id = %event.conversation_id,
rig_tap.tick = event.tick,
rig_tap.occurred_at_millis = event.occurred_at_millis,
rig_tap.span_id = event.span_id.unwrap_or(0),
rig_tap.kernel_id = fields.kernel_id,
rig_tap.tool_name = fields.tool_name,
rig_tap.call_id = fields.call_id,
rig_tap.skill_id = fields.skill_id,
rig_tap.model = fields.model,
rig_tap.response_id = fields.response_id,
rig_tap.previous_response_id = fields.previous_response_id,
rig_tap.dataset = fields.dataset,
rig_tap.metric = fields.metric,
rig_tap.verdict = fields.verdict,
rig_tap.error_class = fields.error_class,
);
Ok(())
}
pub fn emit(event: &ObservabilityEvent) {
if let Err(err) = try_emit(event) {
tracing::warn!(
target: EVENT_TARGET,
error = %err,
error_kind = err.kind(),
"rig-tap: failed to emit event",
);
}
}
pub fn emit_kind(conversation_id: impl Into<String>, kind: EventKind) {
let event = build_event(conversation_id, kind);
emit(&event);
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::panic,
clippy::indexing_slicing,
clippy::expect_used
)]
mod tests {
use super::*;
#[test]
fn tick_is_monotonic() {
let a = next_tick();
let b = next_tick();
assert!(b > a);
}
#[test]
fn build_event_stamps_envelope() {
let evt = build_event(
"c",
EventKind::PromptStarted {
model: "m".into(),
messages_in: 0,
},
);
assert_eq!(evt.version, SCHEMA_VERSION);
assert_eq!(evt.conversation_id, "c");
assert!(evt.occurred_at_millis > 0);
}
}