Architecture
This section describes emit
's key components and how they fit together.
Crate organization
emit
is split into a few subcrates:
classDiagram direction RL emit_core <.. emit_macros emit_core <.. emit emit_macros <.. emit class emit_macros { emit_core = "0.17.0-alpha.17" proc-macro2 = "1" quote = "1" syn = "2" } emit <.. emit_term emit <.. emit_file emit <.. emit_otlp emit <.. emit_custom emit <.. app : required class emit { emit_core = "0.17.0-alpha.17" emit_macros = "0.17.0-alpha.17" } emit_term .. app : optional emit_file .. app : optional emit_otlp .. app : optional emit_custom .. app : optional class emit_term { emit = "0.17.0-alpha.17" } class emit_file { emit = "0.17.0-alpha.17" } class emit_otlp { emit = "0.17.0-alpha.17" } class emit_custom["other emitter"] { emit = "0.17.0-alpha.17" } class app["your app"] { emit = "0.17.0-alpha.17" emit_term = "0.17.0-alpha.17"* emit_file = "0.17.0-alpha.17"* emit_otlp = "0.17.0-alpha.17"* } click emit_core href "https://docs.rs/emit_core/0.11.0-alpha.21/emit_core/index.html" click emit_macros href "https://docs.rs/emit_macros/0.11.0-alpha.21/emit_macros/index.html" click emit href "https://docs.rs/emit/0.11.0-alpha.21/emit/index.html" click emit_term href "https://docs.rs/emit_term/0.11.0-alpha.21/emit_term/index.html" click emit_file href "https://docs.rs/emit_file/0.11.0-alpha.21/emit_file/index.html" click emit_otlp href "https://docs.rs/emit_otlp/0.11.0-alpha.21/emit_otlp/index.html"
emit
: The main library that re-exportsemit_core
andemit_macros
. This is the one your applications depend on.emit_core
: Just the fundamental APIs. It includes theshared()
andinternal()
runtimes. The goal of this library is to remain stable, even if macro syntax evolves over time.emit_macros
:emit!
,#[span]
, and other procedural macros.
The emit
library doesn't implement anywhere for you to send your diagnostics itself, but there are other libraries that do:
emit_term
: Writes to the console. See Emitting to the console for details.emit_file
: Writes to rolling files. See Emitting to rolling files for details.emit_otlp
: Writes OpenTelemetry's wire protocol. See Emitting via OTLP for details.
You can also write your own emitters by implementing the Emitter
trait. See Writing an Emitter for details.
Events
Event
s are the central data type in emit
that all others hang off. They look like this:
classDiagram direction RL Timestamp <.. Extent Str <.. Props Value <.. Props class Props { for_each(Fn(Str, Value))* } <<Trait>> Props Path <.. Event Props <.. Event Template <.. Event class Extent { as_point() Timestamp as_range() Option~Range~Timestamp~~ } Extent <.. Event class Event { mdl() Path tpl() Template extent() Extent props() Props } click Event href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Event.html" click Timestamp href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Timestamp.html" click Extent href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Extent.html" click Str href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Str.html" click Value href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Value.html" click Props href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Props.html" click Template href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Template.html" click Path href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Path.html"
Events include:
- A
Path
for the component that generated them. - A
Template
for their human-readable description. Templates can also make good low-cardinality identifiers for a specific shape of event. - An
Extent
for the time the event is relevant. The extent itself may be a singleTimestamp
for a point in time, or a pair of timestamps representing an active time range. Props
for structured key-value pairs attached to the event. These can be lazily interpolated into the template.
See Event data model for more details.
Runtimes
In emit
, a diagnostic pipeline is an instance of a Runtime
. Each runtime is an isolated set of components that help construct and emit diagnostic events in your applications. It looks like this:
classDiagram direction RL class AmbientSlot { get() Runtime } Runtime <.. AmbientSlot class Runtime { emitter() Emitter filter() Filter ctxt() Ctxt clock() Clock rng() Rng } Emitter <.. Runtime Filter <.. Runtime Ctxt <.. Runtime Clock <.. Runtime Rng <.. Runtime class Emitter { emit(Event)* } <<Trait>> Emitter class Filter { matches() bool* } <<Trait>> Filter class Ctxt { open(Props)* with_current(FnOnce~Props~)* } <<Trait>> Ctxt class Clock { now() Timestamp* } <<Trait>> Clock class Rng { fill([u8])* } <<Trait>> Rng click Emitter href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Emitter.html" click Filter href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Filter.html" click Ctxt href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Ctxt.html" click Clock href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Clock.html" click Rng href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Rng.html" click Runtime href "https://docs.rs/emit/0.11.0-alpha.21/emit/runtime/struct.Runtime.html" click AmbientSlot href "https://docs.rs/emit/0.11.0-alpha.21/emit/runtime/struct.AmbientSlot.html"
A Runtime
includes:
Emitter
: Responsible for sending events to some outside observer.Filter
: Responsible for determining whether an event should be emitted or not.Ctxt
: Responsible for storing ambient context that's appended to events as they're constructed.Clock
: Responsible for assigning timestamps to events and running timers.Rng
: Responsible for generating unique identifiers like trace and span ids.
An AmbientSlot
is a container for a Runtime
that manages global initialization. emit
includes two built-in ambient slots:
shared()
: The runtime used by default when not otherwise specified.internal()
: The runtime used by other runtimes for self diagnostics.
You can also define your own AmbientSlot
s or use Runtime
s directly.
Event construction and emission
When the emit!
macro is called, an event is constructed using features of the runtime before being emitted through it. This is how it works:
flowchart start((start)) --> macro["`<code>emit!('a {x}', y)</code>`"] macro --> tpl["`<code>Template('a {x}')</code>`"] macro --> macro_props["`<code>Props { x, y }</code>`"] ctxt{{"`<code>Ctxt::Current</code>`"}} --> ctxt_props["`<code>Props { z }</code>`"] props["`<code>Props { x, y, z }</code>`"] macro_props --> props ctxt_props --> props clock{{"`<code>Clock::now</code>`"}} --> ts["`<code>Timestamp</code>`"] --> extent["`<code>Extent::point</code>`"] mdl_path["`<code>module_path!()</code>`"] --> mdl["`<code>Path('a::b::c')</code>`"] event["`<code>Event</code>`"] props -- props --> event extent -- extent --> event tpl -- tpl --> event mdl -- mdl --> event filter{"`<code>Filter::matches</code>`"} event --> filter filter -- false --> filter_no(((discard))) emitter{{"`<code>Emitter::emit</code>`"}} filter -- true --> emitter emitter --> END(((end))) click macro href "https://docs.rs/emit/0.11.0-alpha.21/emit/macro.emit.html" click tpl href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Template.html" click macro_props href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Props.html" click ctxt_props href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Props.html" click props href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Props.html" click mdl href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Path.html" click ts href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Timestamp.html" click extent href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Extent.html" click event href "https://docs.rs/emit/0.11.0-alpha.21/emit/struct.Event.html" click emitter href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Emitter.html" click filter href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Filter.html" click ctxt href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Ctxt.html" click clock href "https://docs.rs/emit/0.11.0-alpha.21/emit/trait.Clock.html"
When constructing an event, the runtime provides the current timestamp and any ambient context. When emitting an event, the runtime filters out events to discard and emits the ones that remain.
Once an event is constructed, it no longer distinguishes properties attached directly from properties added by the ambient context.
You don't need to use macros to construct events. You can also do it manually to get more control over the data they contain.