tracing_capture/predicates/
field.rs

1//! `field()` and `message()` predicate factories.
2
3use std::{any::type_name, borrow::Borrow, fmt, marker::PhantomData};
4
5use predicates::{
6    reflection::{Case, PredicateReflection, Product},
7    Predicate,
8};
9use tracing_tunnel::{FromTracedValue, TracedValue};
10
11use crate::{Captured, CapturedEvent};
12
13/// Conversion into a predicate for a [`TracedValue`] used in the [`field()`] function.
14pub trait IntoFieldPredicate {
15    /// Predicate output of the conversion. The exact type should be considered an implementation
16    /// detail and should not be relied upon.
17    type Predicate: Predicate<TracedValue>;
18    /// Performs the conversion.
19    fn into_predicate(self) -> Self::Predicate;
20}
21
22impl<P: Predicate<TracedValue>> IntoFieldPredicate for [P; 1] {
23    type Predicate = P;
24
25    fn into_predicate(self) -> Self::Predicate {
26        self.into_iter().next().unwrap()
27    }
28}
29
30macro_rules! impl_into_field_predicate {
31    ($($ty:ty),+) => {
32        $(
33        impl IntoFieldPredicate for $ty {
34            type Predicate = EquivPredicate<Self>;
35
36            fn into_predicate(self) -> Self::Predicate {
37                EquivPredicate { value: self }
38            }
39        }
40        )+
41    };
42}
43
44impl_into_field_predicate!(bool, i64, i128, u64, u128, f64, &str);
45
46/// Creates a predicate for a particular field of a [`CapturedSpan`] or [`CapturedEvent`].
47///
48/// # Arguments
49///
50/// The argument of this function is essentially a predicate for the [`TracedValue`] of the field.
51/// It may be:
52///
53/// - `bool`, `i64`, `i128`, `u64`, `u128`, `f64`, `&str`: will be compared to the `TracedValue`
54///   using the corresponding [`PartialEq`] implementation.
55/// - A predicate produced by the [`value()`] function.
56/// - Any `Predicate` for [`TracedValue`]. To bypass Rust orphaning rules, the predicate
57///   must be enclosed in square brackets (i.e., a one-value array).
58///
59/// [`CapturedSpan`]: crate::CapturedSpan
60///
61/// # Examples
62///
63/// ```
64/// # use predicates::{constant::always, ord::gt};
65/// # use tracing_subscriber::{layer::SubscriberExt, Registry};
66/// # use tracing_capture::{predicates::{field, value, ScanExt}, CaptureLayer, SharedStorage};
67/// let storage = SharedStorage::default();
68/// let subscriber = Registry::default().with(CaptureLayer::new(&storage));
69/// tracing::subscriber::with_default(subscriber, || {
70///     tracing::info_span!("compute", arg = 5_i32).in_scope(|| {
71///         tracing::info!("done");
72///     });
73/// });
74///
75/// let storage = storage.lock();
76/// // All of these access the single captured span.
77/// let spans = storage.scan_spans();
78/// let _ = spans.single(&field("arg", [always()]));
79/// let _ = spans.single(&field("arg", 5_i64));
80/// let _ = spans.single(&field("arg", value(gt(3_i64))));
81/// ```
82pub fn field<P: IntoFieldPredicate>(
83    name: &'static str,
84    matches: P,
85) -> FieldPredicate<P::Predicate> {
86    FieldPredicate {
87        name,
88        matches: matches.into_predicate(),
89    }
90}
91
92/// Predicate for a particular field of a [`CapturedSpan`] or [`CapturedEvent`] returned by
93/// the [`field()`] function.
94///
95/// [`CapturedSpan`]: crate::CapturedSpan
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub struct FieldPredicate<P> {
98    name: &'static str,
99    matches: P,
100}
101
102impl_bool_ops!(FieldPredicate<P>);
103
104impl<P: Predicate<TracedValue>> fmt::Display for FieldPredicate<P> {
105    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(formatter, "fields.{}({})", self.name, self.matches)
107    }
108}
109
110impl<P: Predicate<TracedValue>> PredicateReflection for FieldPredicate<P> {}
111
112impl<'a, P: Predicate<TracedValue>, T: Captured<'a>> Predicate<T> for FieldPredicate<P> {
113    fn eval(&self, variable: &T) -> bool {
114        variable
115            .value(self.name)
116            .is_some_and(|value| self.matches.eval(value))
117    }
118
119    fn find_case(&self, expected: bool, variable: &T) -> Option<Case<'_>> {
120        let Some(value) = variable.value(self.name) else {
121            return if expected {
122                None // was expecting a variable, but there is none
123            } else {
124                let product = Product::new(format!("fields.{}", self.name), "None");
125                Some(Case::new(Some(self), expected).add_product(product))
126            };
127        };
128
129        let child = self.matches.find_case(expected, value)?;
130        Some(Case::new(Some(self), expected).add_child(child))
131    }
132}
133
134#[doc(hidden)] // implementation detail (yet?)
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub struct EquivPredicate<V> {
137    value: V,
138}
139
140impl<V: fmt::Debug> fmt::Display for EquivPredicate<V> {
141    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(formatter, "var == {:?}", self.value)
143    }
144}
145
146impl<V: fmt::Debug> PredicateReflection for EquivPredicate<V> {}
147
148impl<V: fmt::Debug + PartialEq<TracedValue>> Predicate<TracedValue> for EquivPredicate<V> {
149    fn eval(&self, variable: &TracedValue) -> bool {
150        self.value == *variable
151    }
152
153    fn find_case(&self, expected: bool, variable: &TracedValue) -> Option<Case<'_>> {
154        if self.eval(variable) == expected {
155            let product = Product::new("var", format!("{variable:?}"));
156            Some(Case::new(Some(self), expected).add_product(product))
157        } else {
158            None
159        }
160    }
161}
162
163/// Creates a predicate for a [`TracedValue`] that checks whether the value matches
164/// the specified criteria for a particular subtype (e.g., an unsigned integer).
165/// If the value has another subtype, the predicate is false.
166///
167/// Returned predicates can be supplied to the [`field()`] function.
168///
169/// # Arguments
170///
171/// The argument must be a predicate for one of types that can be obtained from a [`TracedValue`]
172/// reference: `bool`, `i64`, `i128`, `u64`, `u128`, `f64`, or `str`. The type can be specified
173/// explicitly, but usually you can make Rust properly infer it.
174///
175/// # Examples
176///
177/// ```
178/// # use predicates::{ord::{gt, ne}, iter::in_hash, str::contains};
179/// # use tracing_capture::predicates::{field, value};
180/// let _ = field("return", value(gt(5.0)));
181/// let _ = field("name", value(contains("test")));
182/// let _ = field("float", value(in_hash([3_u64, 5])));
183/// // ^ Note the specified integer type.
184/// ```
185pub fn value<T, P>(matches: P) -> ValuePredicate<T, P>
186where
187    T: for<'a> FromTracedValue<'a> + ?Sized,
188    P: Predicate<T>,
189{
190    ValuePredicate {
191        matches,
192        _ty: PhantomData,
193    }
194}
195
196/// Predicate for [`TracedValue`]s returned by the [`value()`] function.
197#[derive(Debug)]
198pub struct ValuePredicate<T: ?Sized, P> {
199    matches: P,
200    _ty: PhantomData<fn(T)>,
201}
202
203impl<T: ?Sized, P: Clone> Clone for ValuePredicate<T, P> {
204    fn clone(&self) -> Self {
205        Self {
206            matches: self.matches.clone(),
207            _ty: PhantomData,
208        }
209    }
210}
211
212impl<T: ?Sized, P: Copy> Copy for ValuePredicate<T, P> {}
213
214impl<T: ?Sized, P: PartialEq> PartialEq for ValuePredicate<T, P> {
215    fn eq(&self, other: &Self) -> bool {
216        self.matches == other.matches
217    }
218}
219
220impl<T, P> fmt::Display for ValuePredicate<T, P>
221where
222    T: for<'a> FromTracedValue<'a> + ?Sized,
223    P: Predicate<T>,
224{
225    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
226        write!(formatter, "is<{}>({})", type_name::<T>(), self.matches)
227    }
228}
229
230impl<T, P> PredicateReflection for ValuePredicate<T, P>
231where
232    T: for<'a> FromTracedValue<'a> + ?Sized,
233    P: Predicate<T>,
234{
235}
236
237impl<T, P> Predicate<TracedValue> for ValuePredicate<T, P>
238where
239    T: for<'a> FromTracedValue<'a> + ?Sized,
240    P: Predicate<T>,
241{
242    fn eval(&self, variable: &TracedValue) -> bool {
243        T::from_value(variable).is_some_and(|value| self.matches.eval(value.borrow()))
244    }
245
246    fn find_case(&self, expected: bool, variable: &TracedValue) -> Option<Case<'_>> {
247        let value = T::from_value(variable);
248        let value = if let Some(value) = &value {
249            value.borrow()
250        } else {
251            return if expected {
252                None // was expecting another var type
253            } else {
254                let product = Product::new(format!("var.as<{}>", type_name::<T>()), "None");
255                Some(Case::new(Some(self), expected).add_product(product))
256            };
257        };
258
259        let child = self.matches.find_case(expected, value)?;
260        Some(Case::new(Some(self), expected).add_child(child))
261    }
262}
263
264impl<T, P> IntoFieldPredicate for ValuePredicate<T, P>
265where
266    T: for<'a> FromTracedValue<'a> + ?Sized,
267    P: Predicate<T>,
268{
269    type Predicate = Self;
270
271    fn into_predicate(self) -> Self::Predicate {
272        self
273    }
274}
275
276/// Creates a predicate for the message of a [`CapturedEvent`].
277///
278/// # Arguments
279///
280/// The argument of this function is a `str` predicate for the event message.
281///
282/// # Examples
283///
284/// ```
285/// # use predicates::{ord::eq, str::contains};
286/// # use tracing_subscriber::{layer::SubscriberExt, Registry};
287/// # use tracing_capture::{predicates::{message, ScanExt}, CaptureLayer, SharedStorage};
288/// let storage = SharedStorage::default();
289/// let subscriber = Registry::default().with(CaptureLayer::new(&storage));
290/// tracing::subscriber::with_default(subscriber, || {
291///     tracing::info_span!("compute").in_scope(|| {
292///         tracing::info!(result = 42, "computations completed");
293///     });
294/// });
295///
296/// let storage = storage.lock();
297/// // All of these access the single captured event.
298/// let events = storage.scan_events();
299/// let _ = events.single(&message(eq("computations completed")));
300/// let _ = events.single(&message(contains("completed")));
301/// ```
302pub fn message<P: Predicate<str>>(matches: P) -> MessagePredicate<P> {
303    MessagePredicate { matches }
304}
305
306/// Predicate for the message of a [`CapturedEvent`] returned by the [`message()`] function.
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308pub struct MessagePredicate<P> {
309    matches: P,
310}
311
312impl_bool_ops!(MessagePredicate<P>);
313
314impl<P: Predicate<str>> fmt::Display for MessagePredicate<P> {
315    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
316        write!(formatter, "message({})", self.matches)
317    }
318}
319
320impl<P: Predicate<str>> PredicateReflection for MessagePredicate<P> {}
321
322impl<P: Predicate<str>> Predicate<CapturedEvent<'_>> for MessagePredicate<P> {
323    fn eval(&self, variable: &CapturedEvent<'_>) -> bool {
324        variable
325            .message()
326            .is_some_and(|value| self.matches.eval(value))
327    }
328
329    fn find_case(&self, expected: bool, variable: &CapturedEvent<'_>) -> Option<Case<'_>> {
330        let Some(message) = variable.message() else {
331            return if expected {
332                None // was expecting a variable, but there is none
333            } else {
334                let product = Product::new("message", "None");
335                Some(Case::new(Some(self), expected).add_product(product))
336            };
337        };
338
339        let child = self.matches.find_case(expected, message)?;
340        Some(Case::new(Some(self), expected).add_child(child))
341    }
342}