use std::{fmt::Write as _, ops::Range};
use tracing::field;
use crate::{AshTrayce, EventInfo, EventProvider, Fields};
pub type LogAshes = AshTrayce<LogEventProvider>;
#[derive(Debug)]
#[non_exhaustive]
pub struct FieldInfo {
pub value: Range<usize>,
pub name: &'static str,
}
impl FieldInfo {
pub fn value(&self) -> Option<Range<usize>> {
if self.value == (usize::MAX..usize::MAX) {
None
} else {
Some(Range::clone(&self.value))
}
}
pub fn get_value<'s>(&self, str: &'s str) -> Option<&'s str> {
self.value().map(|range| &str[range])
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct LogEvent {
pub fields: Range<usize>,
pub metadata: &'static tracing::Metadata<'static>,
}
pub struct LogEventProvider {
pub field_infos: Vec<FieldInfo>,
pub string: String,
}
impl LogEventProvider {
pub const fn new() -> Self {
Self {
field_infos: Vec::new(),
string: String::new(),
}
}
fn make_visitor_impl(&mut self, event: &mut LogEvent) -> impl tracing::field::Visit {
struct V<'a> {
p: &'a mut LogEventProvider,
fields: Range<usize>,
}
impl<'a> field::Visit for V<'a> {
fn record_debug(&mut self, field: &field::Field, value: &dyn std::fmt::Debug) {
if field.index() >= self.fields.len() {
return; }
let str_start = self.p.string.len();
self.p
.string
.write_fmt(format_args!("{value:?}"))
.unwrap_or_else(|_| {
panic!("could not format field {:?} to string", field.name())
});
let str_end = self.p.string.len();
let field_idx = self
.fields
.start
.checked_add(field.index()).expect("the field info should already exist at this index, so the index should also not overflow usize");
self.p.field_infos[field_idx].value = str_start..str_end;
}
}
V {
p: self,
fields: Range::clone(&event.fields),
}
}
}
impl Default for LogEventProvider {
fn default() -> Self {
Self::new()
}
}
impl EventProvider for LogEventProvider {
type Event = LogEvent;
#[inline]
fn should_use_visitor_if_values_given(&self) -> bool {
false
}
fn make_event(&mut self, _: usize, info: EventInfo) -> Self::Event {
let field_start = self.field_infos.len();
let iter = match info.fields {
Fields::Full(field_set) => {
self.field_infos.reserve(field_set.len());
field_set.iter()
}
Fields::Iter(iter) => iter,
};
for field in iter {
self.field_infos.push(FieldInfo {
value: usize::MAX..usize::MAX,
name: field.name(),
});
}
let field_end = self.field_infos.len();
let mut event = LogEvent {
fields: field_start..field_end,
metadata: info.metadata,
};
if let Some(values) = info.values_early {
values.record(&mut self.make_visitor_impl(&mut event));
}
event
}
fn make_visitor(&mut self, _: usize, event: &mut Self::Event) -> impl tracing::field::Visit {
self.make_visitor_impl(event)
}
#[inline]
fn should_span_enter() -> bool {
false
}
#[inline]
fn should_span_exit() -> bool {
false
}
}
#[cfg(feature = "serde")]
mod serde {
use std::ops::Range;
use serde::{Serialize, ser::SerializeMap};
use tracing_serde::{AsSerde, SerializeLevel};
use crate::providers::log::{LogAshes, LogEvent};
struct SerializeEventCtx<'a> {
ashes: &'a LogAshes,
event: &'a LogEvent,
}
impl<'a> Serialize for SerializeEventCtx<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(self.event.fields.len()))?;
for field_idx in Range::clone(&self.event.fields) {
let info = &self.ashes.provider.field_infos[field_idx];
map.serialize_entry(info.name, &info.get_value(&self.ashes.provider.string))?;
}
map.end()
}
}
#[derive(Serialize)]
struct SerializeEvent<'a> {
name: &'static str,
target: &'a str,
level: SerializeLevel<'a>,
#[serde(rename = "mod")]
module_path: Option<&'a str>,
file: Option<&'a str>,
line: Option<u32>,
is_span: bool,
ctx: SerializeEventCtx<'a>,
}
impl Serialize for LogAshes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.ash
.serializable_with(|event| {
let metadata = event.metadata;
SerializeEvent {
ctx: SerializeEventCtx { ashes: self, event },
name: metadata.name(),
target: metadata.target(),
level: metadata.level().as_serde(),
module_path: metadata.module_path(),
file: metadata.file(),
line: metadata.line(),
is_span: metadata.is_span(),
}
})
.serialize(serializer)
}
}
}