Skip to content

Commit a259a08

Browse files
authored
support dotted.attribute syntax in the macros (#34)
1 parent 01f4769 commit a259a08

File tree

3 files changed

+227
-92
lines changed

3 files changed

+227
-92
lines changed

src/macros/impl_.rs

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,32 @@ use tracing_opentelemetry::OpenTelemetrySpanExt;
1313

1414
// Re-export macros marked with `#[macro_export]` from this module, because `#[macro_export]` places
1515
// them at the crate root.
16-
pub use crate::__tracing_span as tracing_span;
16+
pub use crate::{__log as log, __tracing_span as tracing_span};
1717

1818
#[macro_export]
1919
#[doc(hidden)]
2020
macro_rules! __tracing_span {
21-
(parent: $parent:expr, $level:expr, $format:expr, $($($arg:ident = $value:expr),+)?) => {{
21+
(parent: $parent:expr, $level:expr, $format:expr, $($($path:ident).+ = $value:expr),*) => {{
2222
// bind args early to avoid multiple evaluation
23-
$($(let $arg = $value;)*)?
23+
$crate::__bind_single_ident_args!($($($path).+ = $value),*);
2424
tracing::span!(
2525
parent: $parent,
2626
$level,
2727
$format,
28-
$($($arg = $arg,)*)?
28+
$($($path).+ = $crate::__evaluate_arg!($($path).+ = $value),)*
2929
logfire.msg = format_args!($format),
30-
logfire.json_schema = $crate::__json_schema!($($($arg),+)?),
30+
logfire.json_schema = $crate::__json_schema!($($($path).+),*),
3131
)
3232
}};
33-
($level:expr, $format:expr, $($($arg:ident = $value:expr),+)?) => {{
33+
($level:expr, $format:expr, $($($path:ident).+ = $value:expr),*) => {{
3434
// bind args early to avoid multiple evaluation
35-
$($(let $arg = $value;)*)?
35+
$crate::__bind_single_ident_args!($($($path).+ = $value),*);
3636
tracing::span!(
3737
$level,
3838
$format,
39-
$($($arg = $arg,)*)?
39+
$($($path).+ = $crate::__evaluate_arg!($($path).+ = $value),)*
4040
logfire.msg = format_args!($format),
41-
logfire.json_schema = $crate::__json_schema!($($($arg),+)?),
41+
logfire.json_schema = $crate::__json_schema!($($($path).+),*),
4242
)
4343
}};
4444
}
@@ -212,12 +212,12 @@ pub fn export_log_span(
212212
#[macro_export]
213213
#[doc(hidden)]
214214
macro_rules! __json_schema {
215-
($($($args:ident),+)?) => {
215+
($($($($path:ident).+),+)?) => {
216216
concat!("{\
217217
\"type\":\"object\",\
218218
\"properties\":{\
219219
",
220-
$($crate::__schema_args!($($args),*),)?
220+
$($crate::__schema_args!($($($path).+),*),)?
221221
"\
222222
}\
223223
}")
@@ -227,13 +227,90 @@ macro_rules! __json_schema {
227227
#[macro_export]
228228
#[doc(hidden)]
229229
macro_rules! __schema_args {
230-
($arg:ident, $($args:ident),+) => {
230+
($($path:ident).+, $($($rest:ident).+),+) => {
231231
// this is done recursively to avoid a trailing comma in JSON :/
232-
concat!($crate::__schema_args!($arg), ",", $crate::__schema_args!($($args),*))
232+
concat!($crate::__schema_args!($($path).+), ",", $crate::__schema_args!($($($rest).+),*))
233233
};
234-
($arg:ident) => {
234+
($($path:ident).+) => {
235235
// TODO proper type analysis for the args
236-
concat!("\"", stringify!($arg), "\":{}")
236+
concat!("\"", stringify!($($path).+), "\":{}")
237237
};
238238
() => {};
239239
}
240+
241+
/// Expands to `let $arg = $value` only for single ident args
242+
///
243+
/// Valid variable names in Rust can only have a single ident (and this is also true)
244+
/// of format syntax. We need to bind arguments which can go in the format string
245+
/// early to avoid multiple evaluation of the expressions.
246+
#[macro_export]
247+
#[doc(hidden)]
248+
macro_rules! __bind_single_ident_args {
249+
// single-ident arg: bind it
250+
($arg:ident = $value:expr $(, $($rest_arg:ident).+ = $rest_value:expr)*) => {
251+
let $arg = $value;
252+
$crate::__bind_single_ident_args!($($($rest_arg).+ = $rest_value),*)
253+
};
254+
// multi-ident arg: skip it
255+
($($path:ident).+ = $value:expr $(, $($rest_arg:ident).+ = $rest_value:expr)*) => {
256+
$crate::__bind_single_ident_args!($($($rest_arg).+ = $rest_value),*)
257+
};
258+
// base case: stop recursion
259+
() => { };
260+
}
261+
262+
/// Macro to evaluate the argument provided.
263+
///
264+
/// If the argument was single-ident, it was already evaluated so we should use the arg ident
265+
/// directly. If it was multi-ident, we should evaluate it now.
266+
#[macro_export]
267+
#[doc(hidden)]
268+
macro_rules! __evaluate_arg {
269+
// single ident arg should already have been bound
270+
($arg:ident = $value:expr) => {
271+
$arg
272+
};
273+
// multi-ident arg should be evaluated now
274+
($($path:ident).+ = $value:expr) => {
275+
$value
276+
};
277+
}
278+
279+
#[macro_export]
280+
#[doc(hidden)]
281+
macro_rules! __log {
282+
(parent: $parent:expr, $level:expr, $format:expr, $($($path:ident).+ = $value:expr),*) => {
283+
if tracing::span_enabled!($level) {
284+
// bind single ident args early to allow them in the format string
285+
// without multiple evaluation
286+
$crate::__bind_single_ident_args!($($($path).+ = $value),*);
287+
$crate::__macros_impl::export_log_span(
288+
$format,
289+
$parent,
290+
format!($format),
291+
$level,
292+
$crate::__json_schema!($($($path).+),*),
293+
file!(),
294+
line!(),
295+
module_path!(),
296+
[
297+
$({
298+
let arg_value = $crate::__evaluate_arg!($($path).+ = $value);
299+
$crate::__macros_impl::LogfireValue::new(
300+
stringify!($($path).+),
301+
$crate::__macros_impl::converter(&arg_value).convert_value(arg_value)
302+
)
303+
}),*
304+
]
305+
);
306+
}
307+
};
308+
}
309+
310+
#[cfg(test)]
311+
mod tests {
312+
#[test]
313+
fn test_schema_args() {
314+
assert_eq!(r#""arg1.a":{},"arg2.b":{}"#, __schema_args!(arg1.a, arg2.b));
315+
}
316+
}

src/macros/mod.rs

Lines changed: 70 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,24 @@ pub mod __macros_impl;
1717
/// parent: tracing::Span, // optional, can emit this
1818
/// level: tracing::Level, // optional
1919
/// "format string", // required, must be a format string accepted by `format!()`
20-
/// arg1 = value1, // optional, can be repeated
21-
/// .. // as many additional arg = value pairs as desired
20+
/// attr = value, // optional attributes, can be repeated
21+
/// .. // as many additional attr = value pairs as desired
2222
/// )
2323
/// ```
2424
///
25-
/// The format string only accepts arguments by name.
25+
/// ## Attributes
26+
///
27+
/// The `attr = value` pairs are captured as [attributes] on the span.
28+
///
29+
/// `dotted.name = value` is also supported (and encouraged by Opentelemetry) to namespace
30+
/// attributes. However, dotted names are not supported in Rust format strings.
31+
//
32+
/// ## Formatting
2633
///
27-
/// These can be automatically captured from the surrounding scope by the macro,
28-
/// in which case they will render in the message but will not be.
34+
/// The format string only accepts arguments by name. These can either be explicitly passed
35+
/// to `span!` as an attribute or captured from the surrounding scope by the macro. If captured
36+
/// from the surrounding scope, they will only be used as a format string and not exported
37+
/// as attributes.
2938
///
3039
/// # Examples
3140
///
@@ -48,20 +57,26 @@ pub mod __macros_impl;
4857
/// // With x included in the formatted message but not as an attribute
4958
/// let x = 42;
5059
/// let span = logfire::span!("Span with x = {x}, y = {y}", y = "hello");
60+
///
61+
/// // Attributes can either be a single name or a dotted.name
62+
/// // `dotted.name` is not available in the format string.
63+
/// let span = logfire::span!("Span with x = {x}, y = {y}", y = "hello", foo.bar = 42);
5164
/// ```
65+
///
66+
/// [attributes]: https://opentelemetry.io/docs/concepts/signals/traces/#attributes
5267
#[macro_export]
5368
macro_rules! span {
54-
(parent: $parent:expr, level: $level:expr, $format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
55-
$crate::__macros_impl::tracing_span!(parent: $parent, $level, $format, $($arg = $value),*)
69+
(parent: $parent:expr, level: $level:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
70+
$crate::__macros_impl::tracing_span!(parent: $parent, $level, $format, $($($path).+ = $value),*)
5671
};
57-
(parent: $parent:expr, $format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
58-
$crate::__macros_impl::tracing_span!(parent: $parent, tracing::Level::INFO, $format, $($arg = $value),*)
72+
(parent: $parent:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
73+
$crate::__macros_impl::tracing_span!(parent: $parent, tracing::Level::INFO, $format, $($($path).+ = $value),*)
5974
};
60-
(level: $level:expr, $format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
61-
$crate::__macros_impl::tracing_span!($level, $format, $($arg = $value),*)
75+
(level: $level:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
76+
$crate::__macros_impl::tracing_span!($level, $format, $($($path).+ = $value),*)
6277
};
63-
($format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
64-
$crate::__macros_impl::tracing_span!(tracing::Level::INFO, $format, $($arg = $value),*)
78+
($format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
79+
$crate::__macros_impl::tracing_span!(tracing::Level::INFO, $format, $($($path).+ = $value),*)
6580
};
6681
}
6782

@@ -70,11 +85,11 @@ macro_rules! span {
7085
/// See the [`log!`][macro@crate::log] macro for more details.
7186
#[macro_export]
7287
macro_rules! error {
73-
(parent: $parent:expr, $format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
74-
$crate::log!(parent: $parent, tracing::Level::ERROR, $format, $($arg = $value),*)
88+
(parent: $parent:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
89+
$crate::log!(parent: $parent, tracing::Level::ERROR, $format, $($($path).+ = $value),*)
7590
};
76-
($format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
77-
$crate::log!(tracing::Level::ERROR, $format, $($arg = $value),*)
91+
($format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
92+
$crate::log!(tracing::Level::ERROR, $format, $($($path).+ = $value),*)
7893
};
7994
}
8095

@@ -83,11 +98,11 @@ macro_rules! error {
8398
/// See the [`log!`][macro@crate::log] macro for more details.
8499
#[macro_export]
85100
macro_rules! warn {
86-
(parent: $parent:expr, $format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
87-
$crate::log!(parent: $parent, tracing::Level::WARN, $format, $($arg = $value),*)
101+
(parent: $parent:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
102+
$crate::log!(parent: $parent, tracing::Level::WARN, $format, $($($path).+ = $value),*)
88103
};
89-
($format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
90-
$crate::log!(tracing::Level::WARN, $format, $($arg = $value),*)
104+
($format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
105+
$crate::log!(tracing::Level::WARN, $format, $($($path).+ = $value),*)
91106
};
92107
}
93108

@@ -96,24 +111,24 @@ macro_rules! warn {
96111
/// See the [`log!`][macro@crate::log] macro for more details.
97112
#[macro_export]
98113
macro_rules! info {
99-
(parent: $parent:expr, $format:expr $(,$arg:ident = $value:expr)* $(,)?) => {
100-
$crate::log!(parent: $parent, tracing::Level::INFO, $format, $($arg = $value),*)
114+
(parent: $parent:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
115+
$crate::log!(parent: $parent, tracing::Level::INFO, $format, $($($path).+ = $value),*)
101116
};
102-
($format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
103-
$crate::log!(tracing::Level::INFO, $format, $($arg = $value),*)
117+
($format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
118+
$crate::log!(tracing::Level::INFO, $format, $($($path).+ = $value),*)
104119
};
105120
}
106121

107-
/// Emit a log at the ERROR level.
122+
/// Emit a log at the DEBUG level.
108123
///
109124
/// See the [`log!`][macro@crate::log] macro for more details.
110125
#[macro_export]
111126
macro_rules! debug {
112-
(parent: $parent:expr, $format:expr $(,$arg:ident = $value:expr)* $(,)?) => {
113-
$crate::log!(parent: $parent, tracing::Level::DEBUG, $format, $($arg = $value),*)
127+
(parent: $parent:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
128+
$crate::log!(parent: $parent, tracing::Level::DEBUG, $format, $($($path).+ = $value),*)
114129
};
115-
($format:expr $(, $arg:ident = $value:expr)* $(,)?) => {
116-
$crate::log!(tracing::Level::DEBUG, $format, $($arg = $value),*)
130+
($format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
131+
$crate::log!(tracing::Level::DEBUG, $format, $($($path).+ = $value),*)
117132
};
118133
}
119134

@@ -128,15 +143,24 @@ macro_rules! debug {
128143
/// parent: tracing::Span, // optional, can emit this
129144
/// tracing::Level, // required, see `info!` and variants for convenience
130145
/// "format string", // required, must be a format string accepted by `format!()`
131-
/// arg1 = value1, // optional, can be repeated
146+
/// attr = value, // optional attributes, can be repeated
132147
/// .. // as many additional arg = value pairs as desired
133148
/// )
134149
/// ```
135150
///
136-
/// The format string only accepts arguments by name.
151+
/// ## Attributes
137152
///
138-
/// These can be automatically captured from the surrounding scope by the macro,
139-
/// in which case they will render in the message but will not be.
153+
/// The `attr = value` pairs are captured as [attributes] on the span.
154+
///
155+
/// `dotted.name = value` is also supported (and encouraged by Opentelemetry) to namespace
156+
/// attributes. However, dotted names are not supported in Rust format strings.
157+
//
158+
/// ## Formatting
159+
///
160+
/// The format string only accepts arguments by name. These can either be explicitly passed
161+
/// to `span!` as an attribute or captured from the surrounding scope by the macro. If captured
162+
/// from the surrounding scope, they will only be used as a format string and not exported
163+
/// as attributes.
140164
///
141165
/// # Examples
142166
///
@@ -169,45 +193,19 @@ macro_rules! debug {
169193
/// logfire::log!(Level::INFO, "Log with x = {x}, y = {y}", y = "hello");
170194
/// // or
171195
/// logfire::info!("Log with x = {x}, y = {y}", y = "hello");
196+
///
197+
/// // Attributes can either be a single name or a dotted.name
198+
/// // `dotted.name` is not available in the format string.
199+
/// logfire::log!(Level::INFO, "Log with x = {x}, y = {y}", y = "hello", foo.bar = 42);
200+
/// // or
201+
/// logfire::info!("Log with x = {x}, y = {y}", y = "hello", foo.bar = 42);
172202
/// ```
173203
#[macro_export]
174204
macro_rules! log {
175-
(parent: $parent:expr, $level:expr, $format:expr, $($($arg:ident = $value:expr),+)?) => {{
176-
if tracing::span_enabled!($level) {
177-
// bind args early to avoid multiple evaluation
178-
$($(let $arg = $value;)*)?
179-
$crate::__macros_impl::export_log_span(
180-
$format,
181-
$parent,
182-
format!($format),
183-
$level,
184-
$crate::__json_schema!($($($arg),+)?),
185-
file!(),
186-
line!(),
187-
module_path!(),
188-
[
189-
$($($crate::__macros_impl::LogfireValue::new(stringify!($arg), $crate::__macros_impl::converter(&$arg).convert_value($arg)),)*)?
190-
]
191-
);
192-
}
193-
}};
194-
($level:expr, $format:expr, $($($arg:ident = $value:expr),+)?) => {{
195-
if tracing::span_enabled!($level) {
196-
// bind args early to avoid multiple evaluation
197-
$($(let $arg = $value;)*)?
198-
$crate::__macros_impl::export_log_span(
199-
$format,
200-
&tracing::Span::current(),
201-
format!($format),
202-
$level,
203-
$crate::__json_schema!($($($arg),+)?),
204-
file!(),
205-
line!(),
206-
module_path!(),
207-
[
208-
$($($crate::__macros_impl::LogfireValue::new(stringify!($arg), $crate::__macros_impl::converter(&$arg).convert_value($arg)),)*)?
209-
]
210-
);
211-
}
212-
}};
205+
(parent: $parent:expr, $level:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
206+
$crate::__macros_impl::log!(parent: $parent, $level, $format, $($($path).+ = $value),*)
207+
};
208+
($level:expr, $format:expr $(, $($path:ident).+ = $value:expr)* $(,)?) => {
209+
$crate::__macros_impl::log!(parent: &tracing::Span::current(), $level, $format, $($($path).+ = $value),*)
210+
};
213211
}

0 commit comments

Comments
 (0)