use std::sync::Arc;
use chrono::Utc;
use tracing::{
Event, Subscriber,
field::{Field, Visit},
};
use tracing_subscriber::{Layer, layer::Context};
use super::{aggregator::UsageAggregator, events::MutationAuditEvent};
const MUTATION_AUDIT_TARGET: &str = "fraiseql::mutation_audit";
struct AuditFieldVisitor {
mutation_name: String,
entity_type: String,
operation: String,
tenant_id: String,
}
impl AuditFieldVisitor {
const fn new() -> Self {
Self {
mutation_name: String::new(),
entity_type: String::new(),
operation: String::new(),
tenant_id: String::new(),
}
}
}
impl Visit for AuditFieldVisitor {
fn record_str(&mut self, field: &Field, value: &str) {
match field.name() {
"mutation_name" => value.clone_into(&mut self.mutation_name),
"entity_type" => value.clone_into(&mut self.entity_type),
"operation" => value.clone_into(&mut self.operation),
"tenant_id" => value.clone_into(&mut self.tenant_id),
_ => {},
}
}
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
let s = format!("{value:?}");
match field.name() {
"mutation_name" => self.mutation_name = s,
"entity_type" => self.entity_type = s,
"operation" => self.operation = s,
"tenant_id" => self.tenant_id = s,
_ => {},
}
}
}
#[derive(Clone)]
pub struct MutationAuditLayer {
aggregator: Arc<UsageAggregator>,
}
impl MutationAuditLayer {
#[must_use]
pub const fn new(aggregator: Arc<UsageAggregator>) -> Self {
Self { aggregator }
}
#[must_use]
pub const fn aggregator(&self) -> &Arc<UsageAggregator> {
&self.aggregator
}
}
impl<S: Subscriber> Layer<S> for MutationAuditLayer {
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
if event.metadata().target() != MUTATION_AUDIT_TARGET {
return;
}
let mut visitor = AuditFieldVisitor::new();
event.record(&mut visitor);
let period = Utc::now().format("%Y-%m").to_string();
let audit_event = MutationAuditEvent {
mutation_name: visitor.mutation_name,
entity_type: visitor.entity_type,
operation: visitor.operation,
tenant_id: visitor.tenant_id,
period,
};
self.aggregator.record(&audit_event);
}
}