diff --git a/core/src/event.rs b/core/src/event.rs index 800aea7..343d0e6 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,20 +1,17 @@ -use core::{ - fmt, - ops::{ControlFlow, RangeInclusive}, -}; +use core::{fmt, ops::ControlFlow}; use crate::{ key::{Key, ToKey}, props::{ByRef, Chain, ErasedProps, Props}, template::{Render, Template}, - time::Timestamp, + time::Extent, value::{ToValue, Value}, well_known::{MESSAGE_KEY, TEMPLATE_KEY, TIMESTAMP_KEY, TIMESTAMP_START_KEY}, }; #[derive(Clone)] pub struct Event<'a, P> { - ts: Option>, + ts: Option, tpl: Template<'a>, props: P, } @@ -34,17 +31,9 @@ impl<'a, P: Props> fmt::Debug for Event<'a, P> { } impl<'a, P: Props> Event<'a, P> { - pub fn point(ts: impl Into>, tpl: Template<'a>, props: P) -> Self { - Event::spanned(ts.into().map(|ts| ts..=ts), tpl, props) - } - - pub fn spanned( - ts: impl Into>>, - tpl: Template<'a>, - props: P, - ) -> Self { + pub fn new(ts: Option>, tpl: Template<'a>, props: P) -> Self { Event { - ts: ts.into(), + ts: ts.map(Into::into), tpl, props, } @@ -58,18 +47,8 @@ impl<'a, P: Props> Event<'a, P> { self.tpl.by_ref() } - pub fn timestamp(&self) -> Option { - self.ts.as_ref().map(|ts| *ts.start()) - } - - pub fn timespan(&self) -> Option> { - self.ts.as_ref().and_then(|ts| { - if *ts.start() != *ts.end() { - Some(ts.clone()) - } else { - None - } - }) + pub fn extent(&self) -> Option<&Extent> { + self.ts.as_ref() } pub fn chain(self, other: U) -> Event<'a, Chain> { @@ -111,7 +90,7 @@ impl<'a, P: Props> Event<'a, P> { } pub struct AllProps<'a, P> { - ts: Option>, + ts: Option, tpl: Template<'a>, msg: Render<'a, &'a P>, props: ByRef<'a, P>, @@ -123,8 +102,8 @@ impl<'a, P: Props> Props for AllProps<'a, P> { mut for_each: F, ) { if let Some(ref ts) = self.ts { - if *ts.start() != *ts.end() { - for_each(TIMESTAMP_START_KEY.to_key(), ts.start().to_value()); + if let Some(start) = ts.start() { + for_each(TIMESTAMP_START_KEY.to_key(), start.to_value()); } for_each(TIMESTAMP_KEY.to_key(), ts.end().to_value()); diff --git a/core/src/lib.rs b/core/src/lib.rs index 7b67da1..f570eb8 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,6 +2,7 @@ #[cfg(feature = "alloc")] extern crate alloc; +extern crate core; pub mod ambient; pub mod ctxt; diff --git a/core/src/time.rs b/core/src/time.rs index 5e14a58..bc40675 100644 --- a/core/src/time.rs +++ b/core/src/time.rs @@ -1,4 +1,4 @@ -use core::{cmp, fmt, str, str::FromStr, time::Duration}; +use core::{cmp, fmt, ops::RangeInclusive, str, str::FromStr, time::Duration}; use crate::{ empty::Empty, @@ -57,6 +57,77 @@ impl<'v> Value<'v> { } } +#[derive(Clone)] +pub struct Extent(ExtentInner); + +#[derive(Clone)] +enum ExtentInner { + Point(Timestamp), + Span(RangeInclusive), +} + +impl fmt::Debug for Extent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + ExtentInner::Point(ts) => fmt::Debug::fmt(&ts, f), + ExtentInner::Span(ref ts) => fmt::Debug::fmt(ts, f), + } + } +} + +impl fmt::Display for Extent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + ExtentInner::Point(ts) => fmt::Display::fmt(&ts, f), + ExtentInner::Span(ref ts) => { + fmt::Display::fmt(ts.start(), f)?; + write!(f, "..=")?; + fmt::Display::fmt(ts.end(), f) + } + } + } +} + +impl Extent { + pub fn point(ts: Timestamp) -> Extent { + Extent(ExtentInner::Point(ts)) + } + + pub fn span(ts: RangeInclusive) -> Extent { + if ts.start() == ts.end() { + Extent::point(*ts.end()) + } else { + Extent(ExtentInner::Span(ts)) + } + } + + pub fn start(&self) -> Option<&Timestamp> { + match self.0 { + ExtentInner::Point(_) => None, + ExtentInner::Span(ref ts) => Some(ts.start()), + } + } + + pub fn end(&self) -> &Timestamp { + match self.0 { + ExtentInner::Point(ref ts) => ts, + ExtentInner::Span(ref ts) => ts.end(), + } + } +} + +impl From for Extent { + fn from(point: Timestamp) -> Extent { + Extent::point(point) + } +} + +impl From> for Extent { + fn from(span: RangeInclusive) -> Extent { + Extent::span(span) + } +} + pub trait Clock { fn now(&self) -> Option; } diff --git a/macros/src/emit.rs b/macros/src/emit.rs index 0507b6a..b76abdd 100644 --- a/macros/src/emit.rs +++ b/macros/src/emit.rs @@ -13,21 +13,26 @@ pub struct ExpandTokens { } struct Args { + ts: TokenStream, to: TokenStream, when: TokenStream, } impl Parse for Args { fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut ts = Arg::token_stream("ts", |expr| { + Ok(quote!(Some(emit::time::Extent::from(#expr)))) + }); let mut to = Arg::token_stream("to", |expr| Ok(quote!(#expr))); let mut when = Arg::token_stream("when", |expr| Ok(quote!(#expr))); args::set_from_field_values( input.parse_terminated(FieldValue::parse, Token![,])?.iter(), - [&mut to, &mut when], + [&mut ts, &mut to, &mut when], )?; Ok(Args { + ts: ts.take().unwrap_or_else(|| quote!(None)), to: to.take().unwrap_or_else(|| quote!(emit::empty::Empty)), when: when.take().unwrap_or_else(|| quote!(emit::empty::Empty)), }) @@ -46,6 +51,7 @@ pub fn expand_tokens(opts: ExpandTokens) -> Result { let props_match_binding_tokens = props.match_binding_tokens(); let props_tokens = props.match_bound_tokens(); + let ts_tokens = args.ts; let to_tokens = args.to; let when_tokens = args.when; @@ -64,6 +70,7 @@ pub fn expand_tokens(opts: ExpandTokens) -> Result { emit::#receiver_tokens( #to_tokens, #when_tokens, + #ts_tokens, #level_tokens, #template_tokens, #props_tokens, diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c20aff2..c070ba5 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -23,6 +23,7 @@ mod fmt; mod hook; mod key; mod props; +mod span; mod template; mod util; mod with; @@ -67,7 +68,7 @@ Format a template. #[proc_macro] pub fn format(item: proc_macro::TokenStream) -> proc_macro::TokenStream { emit::expand_tokens(emit::ExpandTokens { - receiver: quote!(format), + receiver: quote!(__private::__format), level: quote!(default()), input: TokenStream::from(item), }) diff --git a/macros/src/span.rs b/macros/src/span.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/macros/src/span.rs @@ -0,0 +1 @@ + diff --git a/src/lib.rs b/src/lib.rs index eb2f546..6bd597a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,9 @@ extern crate alloc; use core::future::Future; +use std::ops::RangeInclusive; -use emit_core::{ctxt::Ctxt, filter::Filter, target::Target, time::Clock}; +use emit_core::{ctxt::Ctxt, filter::Filter, target::Target}; #[doc(inline)] pub use emit_macros::*; @@ -21,7 +22,11 @@ pub mod ctxt { pub use emit_core::ctxt::*; } -use emit_core::{value::ToValue, well_known::WellKnown}; +use emit_core::{ + time::{Clock, Extent}, + value::ToValue, + well_known::WellKnown, +}; pub use self::{ event::Event, key::Key, level::Level, props::Props, template::Template, time::Timestamp, @@ -42,13 +47,18 @@ mod setup; pub fn emit(evt: &Event) { let ambient = emit_core::ambient::get(); + let tpl = evt.template(); + let props = evt.props(); + base_emit( ambient, ambient, ambient, - ambient, - evt.template(), - evt.props(), + evt.extent() + .cloned() + .or_else(|| ambient.now().map(Extent::point)), + tpl, + props, ); } @@ -57,14 +67,12 @@ fn base_emit( to: impl Target, when: impl Filter, ctxt: impl Ctxt, - clock: impl Clock, + ts: Option, tpl: Template, props: impl Props, ) { ctxt.with_current(|ctxt| { - let ts = clock.now(); - - let evt = Event::point(ts, tpl, props.chain(ctxt)); + let evt = Event::new(ts, tpl, props.chain(ctxt)); if when.matches(&evt) { to.event(&evt); diff --git a/src/macro_hooks.rs b/src/macro_hooks.rs index 8194af0..fba726a 100644 --- a/src/macro_hooks.rs +++ b/src/macro_hooks.rs @@ -1,22 +1,20 @@ -use core::{any::Any, fmt}; - -use emit_core::value::{ToValue, Value}; +use core::{any::Any, fmt, future::Future}; use emit_core::{ ambient, ctxt::Ctxt, filter::Filter, - id::IdSource, level::Level, props::Props, target::Target, template::Template, - time::Clock, - well_known::{WellKnown, LEVEL_KEY}, + time::{Clock, Extent}, + value::Value, + well_known::LEVEL_KEY, }; + #[cfg(feature = "std")] use std::error::Error; -use std::future::Future; use crate::{ base_emit, base_with, base_with_future, @@ -302,14 +300,21 @@ impl<'a> __PrivateKeyHook for Key<'a> { } #[track_caller] -pub fn __emit(to: impl Target, when: impl Filter, lvl: Level, tpl: Template, props: impl Props) { +pub fn __emit( + to: impl Target, + when: impl Filter, + ts: Option, + lvl: Level, + tpl: Template, + props: impl Props, +) { let ambient = ambient::get(); base_emit( to.and(ambient), when.and(ambient), ambient, - ambient, + ts.or_else(|| ambient.now().map(Extent::point)), tpl, (LEVEL_KEY, lvl).chain(props), ); diff --git a/targets/otlp/src/logs.rs b/targets/otlp/src/logs.rs index 1b9b172..c525670 100644 --- a/targets/otlp/src/logs.rs +++ b/targets/otlp/src/logs.rs @@ -63,7 +63,8 @@ impl OtlpLogsTargetBuilder { impl emit_core::target::Target for OtlpLogsTarget { fn event(&self, evt: &emit_core::event::Event

) { let time_unix_nano = evt - .timestamp() + .extent() + .map(|ts| ts.end()) .map(|ts| ts.to_unix().as_nanos() as u64) .unwrap_or_default(); diff --git a/targets/term/src/lib.rs b/targets/term/src/lib.rs index 0b19e0b..aa8d625 100644 --- a/targets/term/src/lib.rs +++ b/targets/term/src/lib.rs @@ -53,7 +53,7 @@ impl emit::target::Target for Stdout { fn print(out: &BufferWriter, buf: &mut Buffer, evt: &emit::Event) { let mut header_empty = true; - if let Some(ts) = evt.timestamp() { + if let Some(ts) = evt.extent() { let _ = write!(buf, "[{:.0}", ts); header_empty = false;