datafusion_tracing/
instrumented_macros.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17//
18// This product includes software developed at Datadog (https://www.datadoghq.com/) Copyright 2025 Datadog, Inc.
19
20/// Constructs a new instrumentation `PhysicalOptimizerRule` for a DataFusion `ExecutionPlan`.
21///
22/// This macro is modeled after the [`span!`] macro from the [tracing] crate but is specifically designed
23/// to instrument DataFusion execution plans. It creates a new `PhysicalOptimizerRule` that will wrap
24/// each node of the plan in a custom `InstrumentedExec` node.
25///
26/// By default, it includes all known metrics fields relevant to DataFusion execution.
27///
28/// # Instrumentation Options
29///
30/// The instrumentation options are specified via the [`crate::InstrumentationOptions`] struct, which includes:
31/// - `record_metrics`: Enable or disable recording of DataFusion execution metrics.
32/// - `preview_limit`: Set the number of rows to preview per span (set to `0` to disable).
33/// - `preview_fn`: Provide an optional callback for formatting previewed batches.
34///   If unspecified, [`datafusion::arrow::util::pretty::pretty_format_batches`] will be used.
35/// - `custom_fields`: Provide custom key-value pairs for additional span metadata.
36///
37/// # Span Fields
38///
39/// This macro supports the same field–value syntax used by tracing's span macros, allowing you to add additional
40/// contextual information as needed.
41///
42/// Refer to the [tracing documentation][tracing_span] for more information on the syntax
43/// accepted by these macros, as it closely follows `span!`.
44///
45/// # Examples
46///
47/// Creating a new `InstrumentRule` to wrap the plan with TRACE level spans:
48/// ```rust
49/// # use datafusion_tracing::{instrument_with_spans, InstrumentationOptions};
50/// # use tracing::Level;
51/// let instrument_rule = instrument_with_spans!(Level::TRACE, options: InstrumentationOptions::default());
52/// ```
53///
54/// Adding additional fields to the instrumentation:
55/// ```rust
56/// # use datafusion_tracing::{instrument_with_spans, InstrumentationOptions};
57/// # use tracing::{field, Level};
58/// # use std::collections::HashMap;
59/// let custom_fields = HashMap::from([
60///     ("custom.key1".to_string(), "value1".to_string()),
61///     ("custom.key2".to_string(), "value2".to_string()),
62/// ]);
63/// let options = InstrumentationOptions {
64///    record_metrics: true,
65///    preview_limit: 10,
66///    custom_fields,
67///    ..Default::default()
68/// };
69/// let instrument_rule = instrument_with_spans!(
70///     Level::INFO,
71///     options: options,
72///     datafusion.additional_info = "some info",
73///     datafusion.user_id = 42,
74///     custom.key1 = field::Empty,
75///     custom.key2 = field::Empty,
76/// );
77/// // The instrumentation now includes additional fields, and all spans will be tagged with:
78/// // - "datafusion.additional_info": "some info"
79/// // - "datafusion.user_id": 42
80/// // - "custom.key1": "value1"
81/// // - "custom.key2": "value2"
82/// // as well as all DataFusion metrics fields, and a 10 line preview of the data.
83/// ```
84///
85/// [tracing_span]: https://docs.rs/tracing/latest/tracing/macro.span.html
86/// [`span!`]: https://docs.rs/tracing/latest/tracing/macro.span.html
87///
88/// **Note for crate Developers:**
89///
90/// The list of native datafusion metrics can be re-generated by running the following bash command at the root of the [datafusion](https://github.com/apache/datafusion) repository:
91/// ```bash
92/// (
93///   find . -type f -name '*.rs' ! -path '*/metrics/mod.rs' -exec grep -A2 'MetricBuilder::new' {} \; | grep -E '(counter|gauge|subset_time)'
94///   grep -E -o 'Self::.*=>.*"' datafusion/physical-plan/src/metrics/value.rs
95/// ) | cut -s -f 2 -d '"' | sort -u | sed 's/\(.*\)/datafusion.metrics.\1 = tracing::field::Empty,/g'
96/// ```
97#[macro_export]
98macro_rules! instrument_with_spans {
99    (target: $target:expr, $lvl:expr, options: $options:expr, $($fields:tt)*) => {{
100        let options = $options;
101        let custom_fields = options.custom_fields.clone();
102        $crate::new_instrument_rule(
103            std::sync::Arc::new(move || {
104                let span = tracing::span!(
105                    target: $target,
106                    $lvl,
107                    "InstrumentedExec",
108                    otel.name = tracing::field::Empty,
109                    datafusion.node = tracing::field::Empty,
110                    datafusion.partitioning = tracing::field::Empty,
111                    datafusion.emission_type = tracing::field::Empty,
112                    datafusion.boundedness = tracing::field::Empty,
113                    datafusion.preview = tracing::field::Empty,
114                    datafusion.preview_rows = tracing::field::Empty,
115                    datafusion.metrics.bloom_filter_eval_time = tracing::field::Empty,
116                    datafusion.metrics.build_input_batches = tracing::field::Empty,
117                    datafusion.metrics.build_input_rows = tracing::field::Empty,
118                    datafusion.metrics.build_mem_used = tracing::field::Empty,
119                    datafusion.metrics.build_time = tracing::field::Empty,
120                    datafusion.metrics.bytes_scanned = tracing::field::Empty,
121                    datafusion.metrics.elapsed_compute = tracing::field::Empty,
122                    datafusion.metrics.end_timestamp = tracing::field::Empty,
123                    datafusion.metrics.fetch_time = tracing::field::Empty,
124                    datafusion.metrics.file_open_errors = tracing::field::Empty,
125                    datafusion.metrics.file_scan_errors = tracing::field::Empty,
126                    datafusion.metrics.input_batches = tracing::field::Empty,
127                    datafusion.metrics.input_rows = tracing::field::Empty,
128                    datafusion.metrics.join_time = tracing::field::Empty,
129                    datafusion.metrics.mem_used = tracing::field::Empty,
130                    datafusion.metrics.metadata_load_time = tracing::field::Empty,
131                    datafusion.metrics.num_bytes = tracing::field::Empty,
132                    datafusion.metrics.num_predicate_creation_errors = tracing::field::Empty,
133                    datafusion.metrics.output_batches = tracing::field::Empty,
134                    datafusion.metrics.output_rows = tracing::field::Empty,
135                    datafusion.metrics.page_index_eval_time = tracing::field::Empty,
136                    datafusion.metrics.page_index_rows_matched = tracing::field::Empty,
137                    datafusion.metrics.page_index_rows_pruned = tracing::field::Empty,
138                    datafusion.metrics.peak_mem_used = tracing::field::Empty,
139                    datafusion.metrics.predicate_evaluation_errors = tracing::field::Empty,
140                    datafusion.metrics.pushdown_rows_matched = tracing::field::Empty,
141                    datafusion.metrics.pushdown_rows_pruned = tracing::field::Empty,
142                    datafusion.metrics.repartition_time = tracing::field::Empty,
143                    datafusion.metrics.row_groups_matched_bloom_filter = tracing::field::Empty,
144                    datafusion.metrics.row_groups_matched_statistics = tracing::field::Empty,
145                    datafusion.metrics.row_groups_pruned_bloom_filter = tracing::field::Empty,
146                    datafusion.metrics.row_groups_pruned_statistics = tracing::field::Empty,
147                    datafusion.metrics.row_pushdown_eval_time = tracing::field::Empty,
148                    datafusion.metrics.row_replacements = tracing::field::Empty,
149                    datafusion.metrics.send_time = tracing::field::Empty,
150                    datafusion.metrics.skipped_aggregation_rows = tracing::field::Empty,
151                    datafusion.metrics.spill_count = tracing::field::Empty,
152                    datafusion.metrics.spilled_bytes = tracing::field::Empty,
153                    datafusion.metrics.spilled_rows = tracing::field::Empty,
154                    datafusion.metrics.start_timestamp = tracing::field::Empty,
155                    datafusion.metrics.statistics_eval_time = tracing::field::Empty,
156                    datafusion.metrics.stream_memory_usage = tracing::field::Empty,
157                    datafusion.metrics.time_elapsed_opening = tracing::field::Empty,
158                    datafusion.metrics.time_elapsed_processing = tracing::field::Empty,
159                    datafusion.metrics.time_elapsed_scanning_total = tracing::field::Empty,
160                    datafusion.metrics.time_elapsed_scanning_until_data = tracing::field::Empty,
161                    $($fields)*
162                );
163                for (key, value) in custom_fields.iter() {
164                    span.record(key.as_str(), value);
165                }
166                span
167            }),
168            options
169        )
170    }};
171    (target: $target:expr, $lvl:expr, options: $options:expr) => {
172        $crate::instrument_with_spans!(target: $target, $lvl, options: $options,)
173    };
174    ($lvl:expr, options: $options:expr, $($fields:tt)*) => {
175        $crate::instrument_with_spans!(target: module_path!(), $lvl, options: $options, $($fields)*)
176    };
177    ($lvl:expr, options: $options:expr) => {
178        $crate::instrument_with_spans!(target: module_path!(), $lvl, options: $options)
179    };
180}
181
182/// Constructs a new instrumentation `PhysicalOptimizerRule` for a DataFusion `ExecutionPlan` at the trace level.
183///
184/// This macro automatically sets the tracing level to `TRACE` and is a convenience wrapper around
185/// [`instrument_with_spans!`] with the appropriate log level preset.
186///
187/// See [`instrument_with_spans!`] for details on instrumentation options and span fields.
188///
189/// # Examples
190///
191/// Basic usage with default options:
192/// ```rust
193/// # use datafusion_tracing::{instrument_with_trace_spans, InstrumentationOptions};
194/// let instrument_rule = instrument_with_trace_spans!(options: InstrumentationOptions::default());
195/// ```
196///
197/// Adding custom fields:
198/// ```rust
199/// # use datafusion_tracing::{instrument_with_trace_spans, InstrumentationOptions};
200/// # use tracing::field;
201/// # use std::collections::HashMap;
202/// let custom_fields = HashMap::from([
203///     ("custom.key1".to_string(), "value1".to_string()),
204/// ]);
205/// let options = InstrumentationOptions {
206///    custom_fields,
207///    ..Default::default()
208/// };
209/// let instrument_rule = instrument_with_trace_spans!(
210///     options: options,
211///     datafusion.additional_info = "some info",
212///     custom.key1 = field::Empty,
213/// );
214/// ```
215///
216/// [tracing_trace_span]: https://docs.rs/tracing/latest/tracing/macro.trace_span.html
217/// [`trace_span!`]: https://docs.rs/tracing/latest/tracing/macro.trace_span.html
218/// [`instrument_with_spans!`]: crate::instrument_with_spans!
219#[macro_export]
220macro_rules! instrument_with_trace_spans {
221    (target: $target:expr, options: $options:expr, $($field:tt)*) => {
222        $crate::instrument_with_spans!(
223            target: $target,
224            tracing::Level::TRACE,
225            options: $options,
226            $($field)*
227        )
228    };
229    (options: $options:expr, $($field:tt)*) => {
230        $crate::instrument_with_trace_spans!(target: module_path!(), options: $options, $($field)*)
231    };
232    (target: $target:expr, options: $options:expr) => {
233        $crate::instrument_with_trace_spans!(target: $target, options: $options, )
234    };
235    (options: $options:expr) => {
236        $crate::instrument_with_trace_spans!(target: module_path!(), options: $options)
237    };
238}
239
240/// Constructs a new instrumentation `PhysicalOptimizerRule` for a DataFusion `ExecutionPlan` at the debug level.
241///
242/// This macro automatically sets the tracing level to `DEBUG` and is a convenience wrapper around
243/// [`instrument_with_spans!`] with the appropriate log level preset.
244///
245/// See [`instrument_with_spans!`] for details on instrumentation options and span fields.
246///
247/// # Examples
248///
249/// Basic usage with default options:
250/// ```rust
251/// # use datafusion_tracing::{instrument_with_debug_spans, InstrumentationOptions};
252/// let instrument_rule = instrument_with_debug_spans!(options: InstrumentationOptions::default());
253/// ```
254///
255/// Adding custom fields:
256/// ```rust
257/// # use datafusion_tracing::{instrument_with_debug_spans, InstrumentationOptions};
258/// # use tracing::field;
259/// # use std::collections::HashMap;
260/// let custom_fields = HashMap::from([
261///     ("custom.key1".to_string(), "value1".to_string()),
262/// ]);
263/// let options = InstrumentationOptions {
264///    custom_fields,
265///    ..Default::default()
266/// };
267/// let instrument_rule = instrument_with_debug_spans!(
268///     options: options,
269///     datafusion.additional_info = "some info",
270///     custom.key1 = field::Empty,
271/// );
272/// ```
273///
274/// [tracing_debug_span]: https://docs.rs/tracing/latest/tracing/macro.debug_span.html
275/// [`debug_span!`]: https://docs.rs/tracing/latest/tracing/macro.debug_span.html
276/// [`instrument_with_spans!`]: crate::instrument_with_spans!
277#[macro_export]
278macro_rules! instrument_with_debug_spans {
279    (target: $target:expr, options: $options:expr, $($field:tt)*) => {
280        $crate::instrument_with_spans!(
281            target: $target,
282            tracing::Level::DEBUG,
283            options: $options,
284            $($field)*
285        )
286    };
287    (options: $options:expr, $($field:tt)*) => {
288        $crate::instrument_with_debug_spans!(target: module_path!(), options: $options, $($field)*)
289    };
290    (target: $target:expr, options: $options:expr) => {
291        $crate::instrument_with_debug_spans!(target: $target, options: $options, )
292    };
293    (options: $options:expr) => {
294        $crate::instrument_with_debug_spans!(target: module_path!(), options: $options)
295    };
296}
297
298/// Constructs a new instrumentation `PhysicalOptimizerRule` for a DataFusion `ExecutionPlan` at the info level.
299///
300/// This macro automatically sets the tracing level to `INFO` and is a convenience wrapper around
301/// [`instrument_with_spans!`] with the appropriate log level preset.
302///
303/// See [`instrument_with_spans!`] for details on instrumentation options and span fields.
304///
305/// # Examples
306///
307/// Basic usage with default options:
308/// ```rust
309/// # use datafusion_tracing::{instrument_with_info_spans, InstrumentationOptions};
310/// let instrument_rule = instrument_with_info_spans!(options: InstrumentationOptions::default());
311/// ```
312///
313/// Adding custom fields:
314/// ```rust
315/// # use datafusion_tracing::{instrument_with_info_spans, InstrumentationOptions};
316/// # use tracing::field;
317/// # use std::collections::HashMap;
318/// let custom_fields = HashMap::from([
319///     ("custom.key1".to_string(), "value1".to_string()),
320/// ]);
321/// let options = InstrumentationOptions {
322///    custom_fields,
323///    ..Default::default()
324/// };
325/// let instrument_rule = instrument_with_info_spans!(
326///     options: options,
327///     datafusion.additional_info = "some info",
328///     custom.key1 = field::Empty,
329/// );
330/// ```
331///
332/// [tracing_info_span]: https://docs.rs/tracing/latest/tracing/macro.info_span.html
333/// [`info_span!`]: https://docs.rs/tracing/latest/tracing/macro.info_span.html
334/// [`instrument_with_spans!`]: crate::instrument_with_spans!
335#[macro_export]
336macro_rules! instrument_with_info_spans {
337    (target: $target:expr, options: $options:expr, $($field:tt)*) => {
338        $crate::instrument_with_spans!(
339            target: $target,
340            tracing::Level::INFO,
341            options: $options,
342            $($field)*
343        )
344    };
345    (options: $options:expr, $($field:tt)*) => {
346        $crate::instrument_with_info_spans!(target: module_path!(), options: $options, $($field)*)
347    };
348    (target: $target:expr, options: $options:expr) => {
349        $crate::instrument_with_info_spans!(target: $target, options: $options, )
350    };
351    (options: $options:expr) => {
352        $crate::instrument_with_info_spans!(target: module_path!(), options: $options)
353    };
354}
355
356/// Constructs a new instrumentation `PhysicalOptimizerRule` for a DataFusion `ExecutionPlan` at the warn level.
357///
358/// This macro automatically sets the tracing level to `WARN` and is a convenience wrapper around
359/// [`instrument_with_spans!`] with the appropriate log level preset.
360///
361/// See [`instrument_with_spans!`] for details on instrumentation options and span fields.
362///
363/// # Examples
364///
365/// Basic usage with default options:
366/// ```rust
367/// # use datafusion_tracing::{instrument_with_warn_spans, InstrumentationOptions};
368/// let instrument_rule = instrument_with_warn_spans!(options: InstrumentationOptions::default());
369/// ```
370///
371/// Adding custom fields:
372/// ```rust
373/// # use datafusion_tracing::{instrument_with_warn_spans, InstrumentationOptions};
374/// # use tracing::field;
375/// # use std::collections::HashMap;
376/// let custom_fields = HashMap::from([
377///     ("custom.key1".to_string(), "value1".to_string()),
378/// ]);
379/// let options = InstrumentationOptions {
380///    custom_fields,
381///    ..Default::default()
382/// };
383/// let instrument_rule = instrument_with_warn_spans!(
384///     options: options,
385///     datafusion.additional_info = "some info",
386///     custom.key1 = field::Empty,
387/// );
388/// ```
389///
390/// [tracing_warn_span]: https://docs.rs/tracing/latest/tracing/macro.warn_span.html
391/// [`warn_span!`]: https://docs.rs/tracing/latest/tracing/macro.warn_span.html
392/// [`instrument_with_spans!`]: crate::instrument_with_spans!
393#[macro_export]
394macro_rules! instrument_with_warn_spans {
395    (target: $target:expr, options: $options:expr, $($field:tt)*) => {
396        $crate::instrument_with_spans!(
397            target: $target,
398            tracing::Level::WARN,
399            options: $options,
400            $($field)*
401        )
402    };
403    (options: $options:expr, $($field:tt)*) => {
404        $crate::instrument_with_warn_spans!(target: module_path!(), options: $options, $($field)*)
405    };
406    (target: $target:expr, options: $options:expr) => {
407        $crate::instrument_with_warn_spans!(target: $target, options: $options, )
408    };
409    (options: $options:expr) => {
410        $crate::instrument_with_warn_spans!(target: module_path!(), options: $options)
411    };
412}
413
414/// Constructs a new instrumentation `PhysicalOptimizerRule` for a DataFusion `ExecutionPlan` at the error level.
415///
416/// This macro automatically sets the tracing level to `ERROR` and is a convenience wrapper around
417/// [`instrument_with_spans!`] with the appropriate log level preset.
418///
419/// See [`instrument_with_spans!`] for details on instrumentation options and span fields.
420///
421/// # Examples
422///
423/// Basic usage with default options:
424/// ```rust
425/// # use datafusion_tracing::{instrument_with_error_spans, InstrumentationOptions};
426/// let instrument_rule = instrument_with_error_spans!(options: InstrumentationOptions::default());
427/// ```
428///
429/// Adding custom fields:
430/// ```rust
431/// # use datafusion_tracing::{instrument_with_error_spans, InstrumentationOptions};
432/// # use tracing::field;
433/// # use std::collections::HashMap;
434/// let custom_fields = HashMap::from([
435///     ("custom.key1".to_string(), "value1".to_string()),
436/// ]);
437/// let options = InstrumentationOptions {
438///    custom_fields,
439///    ..Default::default()
440/// };
441/// let instrument_rule = instrument_with_error_spans!(
442///     options: options,
443///     datafusion.additional_info = "some info",
444///     custom.key1 = field::Empty,
445/// );
446/// ```
447///
448/// [tracing_error_span]: https://docs.rs/tracing/latest/tracing/macro.error_span.html
449/// [`error_span!`]: https://docs.rs/tracing/latest/tracing/macro.error_span.html
450/// [`instrument_with_spans!`]: crate::instrument_with_spans!
451#[macro_export]
452macro_rules! instrument_with_error_spans {
453    (target: $target:expr, options: $options:expr, $($field:tt)*) => {
454        $crate::instrument_with_spans!(
455            target: $target,
456            tracing::Level::ERROR,
457            options: $options,
458            $($field)*
459        )
460    };
461    (options: $options:expr, $($field:tt)*) => {
462        $crate::instrument_with_error_spans!(target: module_path!(), options: $options, $($field)*)
463    };
464    (target: $target:expr, options: $options:expr) => {
465        $crate::instrument_with_error_spans!(target: $target, options: $options, )
466    };
467    (options: $options:expr) => {
468        $crate::instrument_with_error_spans!(target: module_path!(), options: $options)
469    };
470}