use crate::PolicyDecision;
use sha2::{Digest, Sha256};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PolicyTelemetryEvent {
pub decision_label: &'static str,
pub allowed: bool,
pub elapsed: Duration,
pub action_hash: String,
pub action_len_bytes: usize,
pub agent_id_hash: String,
}
impl PolicyTelemetryEvent {
pub fn new(agent_id: &str, action: &str, decision: &PolicyDecision, elapsed: Duration) -> Self {
Self {
decision_label: decision.label(),
allowed: decision.is_allowed(),
elapsed,
action_hash: hex_sha256(action),
action_len_bytes: action.len(),
agent_id_hash: hex_sha256(agent_id),
}
}
}
pub trait TelemetrySink: Send + Sync {
fn record_policy_evaluation(&self, event: &PolicyTelemetryEvent);
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopTelemetrySink;
impl TelemetrySink for NoopTelemetrySink {
fn record_policy_evaluation(&self, _event: &PolicyTelemetryEvent) {}
}
#[derive(Debug, Clone)]
pub struct OtelTelemetrySink {
instrumentation_name: &'static str,
}
impl OtelTelemetrySink {
pub fn new() -> Self {
Self {
instrumentation_name: "agentmesh",
}
}
pub fn with_instrumentation_name(instrumentation_name: &'static str) -> Self {
Self {
instrumentation_name,
}
}
}
impl Default for OtelTelemetrySink {
fn default() -> Self {
Self::new()
}
}
impl TelemetrySink for OtelTelemetrySink {
fn record_policy_evaluation(&self, event: &PolicyTelemetryEvent) {
use opentelemetry::trace::{Span, Tracer};
use opentelemetry::{global, KeyValue};
let tracer = global::tracer(self.instrumentation_name);
let mut span = tracer.start("agentmesh.policy.evaluate");
span.set_attribute(KeyValue::new(
"agentmesh.policy.decision",
event.decision_label,
));
span.set_attribute(KeyValue::new("agentmesh.policy.allowed", event.allowed));
span.set_attribute(KeyValue::new(
"agentmesh.policy.elapsed_ms",
event.elapsed.as_secs_f64() * 1000.0,
));
span.set_attribute(KeyValue::new(
"agentmesh.policy.action_hash",
event.action_hash.clone(),
));
span.set_attribute(KeyValue::new(
"agentmesh.policy.action_len_bytes",
event.action_len_bytes as i64,
));
span.set_attribute(KeyValue::new(
"agentmesh.agent.id_hash",
event.agent_id_hash.clone(),
));
if !event.allowed {
span.set_status(opentelemetry::trace::Status::error(
"policy_decision_not_allowed",
));
}
span.end();
}
}
pub fn hex_sha256(value: &str) -> String {
let digest = Sha256::digest(value.as_bytes());
let mut encoded = String::with_capacity(digest.len() * 2);
for byte in digest {
use std::fmt::Write as _;
let _ = write!(&mut encoded, "{byte:02x}");
}
encoded
}