Skip to main content

zeroclaw_log/
macro.rs

1//! `record!` — the sole logging surface for the workspace.
2//!
3//! Call shape (compile-time-locked via the `Event` struct):
4//!
5//! ```ignore
6//! use zeroclaw_log::{record, Event, Action, EventOutcome};
7//!
8//! record!(INFO, Event::new(module_path!(), Action::Start), "starting step");
9//! record!(WARN, Event::new(module_path!(), Action::Fail).with_outcome(EventOutcome::Failure).with_attrs(serde_json::json!({"err": "timeout"})), "tool failed");
10//! ```
11//!
12//! Alias-bound attribution (channel, agent_alias, model_provider,
13//! tool, cron_job_id, …) is NOT a call-site argument — it is assembled
14//! automatically by the `LogCaptureLayer` from `attribution_span`s
15//! opened by entry points (channel listeners, the agent loop, cron
16//! tick handlers, the tool executor wrapper). To attach attribution
17//! to a region of work, wrap the work with [`crate::attribution_span`].
18
19/// Emit a structured ZeroClaw log event. The single positional `Event`
20/// expression carries the typed payload; the trailing literal is the
21/// human-readable message.
22#[macro_export]
23macro_rules! record {
24    ($level:ident, $event:expr, $msg:expr $(,)?) => {{
25        let __zc_event: $crate::Event = $event;
26        $crate::__private::tracing::event!(
27            target: "zeroclaw_log_event",
28            $crate::__private::tracing::Level::$level,
29            zc_name = %__zc_event.name,
30            zc_action = %__zc_event.action.as_str(),
31            zc_outcome = %__zc_event.outcome_str(),
32            zc_category = %__zc_event.category_str(),
33            zc_attrs = %__zc_event.attrs_str(),
34            zc_has_duration = %__zc_event.has_duration(),
35            zc_duration_ms = %__zc_event.duration_ms_or_zero(),
36            zc_file = %file!(),
37            zc_line = %line!(),
38            message = %$msg,
39        );
40    }};
41}
42
43/// Open an attribution span for the given `Attributable` thing. Every
44/// `record!` emitted while the returned span is entered inherits the
45/// thing's role + alias as alias-bound attribution on the resulting
46/// LogEvent. Wrap entry-point work with `.instrument(span)` (async) or
47/// `let _g = span.entered()` (sync).
48#[macro_export]
49macro_rules! attribution_span {
50    ($attributable:expr) => {{
51        let __zc_thing = $attributable;
52        let __zc_role = ::zeroclaw_api::attribution::Attributable::role(__zc_thing);
53        let __zc_alias = ::zeroclaw_api::attribution::Attributable::alias(__zc_thing);
54        $crate::__private::tracing::info_span!(
55            target: "zeroclaw_log_internal_attribution",
56            "zeroclaw_attribution",
57            zc_role_family = %__zc_role.family_str(),
58            zc_role_type = %__zc_role.composite_type().unwrap_or(""),
59            zc_attribution_field = %__zc_role.attribution_field().unwrap_or(""),
60            zc_composite_prefix = %__zc_role.composite_prefix().unwrap_or(""),
61            zc_default_category = %__zc_role.default_category(),
62            zc_alias = %__zc_alias,
63        )
64    }};
65}
66
67/// Open a free-form context span carrying ad-hoc fields (sender id,
68/// message id, turn id, etc.) for every `record!` inside its scope.
69/// Use sparingly — prefer `attribution_span!(thing)` for role-bearing
70/// attribution. This is for transient per-scope identifiers that
71/// aren't tied to an `Attributable`.
72///
73/// Field keys recognized by the layer: `agent_alias`, `tool`,
74/// `session_key`, `cron_job_id`, `risk_profile`, `runtime_profile`,
75/// `memory_namespace`, `skill_bundle`, `knowledge_bundle`, `mcp_bundle`,
76/// `peer_group`, `sop_name`, `model`, `embedding_provider`, `channel`,
77/// `model_provider`, `tts_provider`, `transcription_provider`,
78/// `tunnel_provider`. Anything else lands in event `attributes`.
79#[macro_export]
80macro_rules! scope {
81    ($($key:ident : $value:expr),+ $(,)? => $body:expr) => {{
82        use $crate::__private::tracing::Instrument;
83        ($body).instrument($crate::__private::tracing::info_span!(
84            target: "zeroclaw_log_internal_scope",
85            "zeroclaw_scope",
86            $($key = %($value)),+
87        ))
88    }};
89}
90
91/// `tokio::spawn` that propagates the caller's current span(s) into
92/// the spawned task. Use everywhere a per-message child task needs the
93/// parent's attribution.
94#[macro_export]
95macro_rules! spawn {
96    ($body:expr) => {{
97        #[allow(unused_imports)]
98        use $crate::__private::tracing::Instrument as _;
99        ::tokio::spawn(($body).in_current_span())
100    }};
101}
102
103#[cfg(test)]
104mod tests {
105    use crate::{Action, Event, EventOutcome};
106    use serde_json::json;
107
108    #[test]
109    fn record_compiles_minimal() {
110        record!(INFO, Event::new(module_path!(), Action::Note), "hello");
111    }
112
113    #[test]
114    fn record_compiles_with_attrs_and_outcome() {
115        record!(
116            WARN,
117            Event::new(module_path!(), Action::Fail)
118                .with_outcome(EventOutcome::Failure)
119                .with_attrs(json!({"code": 42})),
120            "failed"
121        );
122    }
123
124    #[test]
125    fn record_compiles_with_duration() {
126        record!(
127            INFO,
128            Event::new(module_path!(), Action::Complete)
129                .with_outcome(EventOutcome::Success)
130                .with_duration(123),
131            "done"
132        );
133    }
134}