use std::ffi::c_void;
use std::fmt;
use std::sync::atomic::{AtomicPtr, Ordering};
use tracing::field::{Field, Visit};
use tracing::Subscriber;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Layer;
use super::callbacks::{FfiLogEntry, FfiLogLevel, LogCallbackFn};
use super::types::FfiStr;
use super::vtable_gen::{current_instance_log_ctx, InstanceLogContext};
const _: () = assert!(
std::mem::size_of::<*mut ()>() == std::mem::size_of::<LogCallbackFn>(),
"LogCallbackFn size must match pointer size"
);
pub struct FfiTracingLayer {
log_cb: &'static AtomicPtr<()>,
log_ctx: &'static AtomicPtr<c_void>,
plugin_id: &'static str,
}
impl FfiTracingLayer {
pub fn new(
log_cb: &'static AtomicPtr<()>,
log_ctx: &'static AtomicPtr<c_void>,
plugin_id: &'static str,
) -> Self {
Self {
log_cb,
log_ctx,
plugin_id,
}
}
}
impl<S> Layer<S> for FfiTracingLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(
&self,
attrs: &tracing::span::Attributes<'_>,
id: &tracing::span::Id,
ctx: Context<'_, S>,
) {
let mut visitor = ComponentFieldVisitor::default();
attrs.record(&mut visitor);
if visitor.has_component_info() {
if let Some(span) = ctx.span(id) {
let info = SpanComponentInfo {
instance_id: visitor.instance_id.unwrap_or_default(),
component_id: visitor.component_id.unwrap_or_default(),
component_type: visitor.component_type.unwrap_or_default(),
};
span.extensions_mut().insert(info);
}
}
}
fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
let mut message_visitor = MessageVisitor::default();
event.record(&mut message_visitor);
let message = message_visitor.message;
let level = match *event.metadata().level() {
tracing::Level::ERROR => FfiLogLevel::Error,
tracing::Level::WARN => FfiLogLevel::Warn,
tracing::Level::INFO => FfiLogLevel::Info,
tracing::Level::DEBUG => FfiLogLevel::Debug,
tracing::Level::TRACE => FfiLogLevel::Trace,
};
let span_info = ctx.event_span(event).and_then(|span| {
if let Some(info) = span.extensions().get::<SpanComponentInfo>() {
return Some(info.clone());
}
for ancestor in span.scope().skip(1) {
if let Some(info) = ancestor.extensions().get::<SpanComponentInfo>() {
return Some(info.clone());
}
}
None
});
let (instance_id, component_id) = if let Some(inst_ctx) = current_instance_log_ctx() {
(inst_ctx.instance_id.clone(), inst_ctx.component_id.clone())
} else if let Some(ref info) = span_info {
(info.instance_id.clone(), info.component_id.clone())
} else {
(String::new(), String::new())
};
if let Some(inst_ctx) = current_instance_log_ctx() {
if let Some(cb) = inst_ctx.log_cb {
let entry = FfiLogEntry {
level,
plugin_id: FfiStr::from_str(self.plugin_id),
target: FfiStr::from_str(event.metadata().target()),
message: FfiStr::from_str(&message),
timestamp_us: super::types::now_us(),
instance_id: FfiStr::from_str(&instance_id),
component_id: FfiStr::from_str(&component_id),
};
cb(inst_ctx.log_ctx, &entry);
return;
}
}
let cb_ptr = self.log_cb.load(Ordering::Acquire);
if cb_ptr.is_null() {
return;
}
let cb: LogCallbackFn = unsafe { std::mem::transmute(cb_ptr) };
let ctx_ptr = self.log_ctx.load(Ordering::Acquire);
let entry = FfiLogEntry {
level,
plugin_id: FfiStr::from_str(self.plugin_id),
target: FfiStr::from_str(event.metadata().target()),
message: FfiStr::from_str(&message),
timestamp_us: super::types::now_us(),
instance_id: FfiStr::from_str(&instance_id),
component_id: FfiStr::from_str(&component_id),
};
cb(ctx_ptr, &entry);
}
}
#[derive(Clone)]
struct SpanComponentInfo {
instance_id: String,
component_id: String,
component_type: String,
}
#[derive(Default)]
struct ComponentFieldVisitor {
instance_id: Option<String>,
component_id: Option<String>,
component_type: Option<String>,
}
impl ComponentFieldVisitor {
fn has_component_info(&self) -> bool {
self.instance_id.is_some() || self.component_id.is_some()
}
}
impl Visit for ComponentFieldVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
let s = format!("{value:?}").trim_matches('"').to_string();
match field.name() {
"instance_id" => self.instance_id = Some(s),
"component_id" => self.component_id = Some(s),
"component_type" => self.component_type = Some(s),
_ => {}
}
}
fn record_str(&mut self, field: &Field, value: &str) {
match field.name() {
"instance_id" => self.instance_id = Some(value.to_string()),
"component_id" => self.component_id = Some(value.to_string()),
"component_type" => self.component_type = Some(value.to_string()),
_ => {}
}
}
}
#[derive(Default)]
struct MessageVisitor {
message: String,
}
impl Visit for MessageVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
if field.name() == "message" {
self.message = format!("{value:?}").trim_matches('"').to_string();
} else if self.message.is_empty() {
self.message = format!("{value:?}").trim_matches('"').to_string();
}
}
fn record_str(&mut self, field: &Field, value: &str) {
if field.name() == "message" || self.message.is_empty() {
self.message = value.to_string();
}
}
}
pub fn init_tracing_subscriber(
log_cb: &'static AtomicPtr<()>,
log_ctx: &'static AtomicPtr<c_void>,
plugin_id: &'static str,
) {
use tracing_subscriber::layer::SubscriberExt;
let _ = tracing_log::LogTracer::init();
let layer = FfiTracingLayer::new(log_cb, log_ctx, plugin_id);
let subscriber = tracing_subscriber::registry().with(layer);
let _ = tracing::subscriber::set_global_default(subscriber);
}