tracing_capture/predicates/
field.rs

1//! `field()` and `message()` predicate factories.
2
3use predicates::{
4    reflection::{Case, PredicateReflection, Product},
5    Predicate,
6};
7
8use std::{any::type_name, borrow::Borrow, fmt, marker::PhantomData};
9
10use crate::{Captured, CapturedEvent};
11use tracing_tunnel::{FromTracedValue, TracedValue};
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            .map_or(false, |value| self.matches.eval(value))
117    }
118
119    fn find_case(&self, expected: bool, variable: &T) -> Option<Case<'_>> {
120        let value = if let Some(value) = variable.value(self.name) {
121            value
122        } else {
123            return if expected {
124                None // was expecting a variable, but there is none
125            } else {
126                let product = Product::new(format!("fields.{}", self.name), "None");
127                Some(Case::new(Some(self), expected).add_product(product))
128            };
129        };
130
131        let child = self.matches.find_case(expected, value)?;
132        Some(Case::new(Some(self), expected).add_child(child))
133    }
134}
135
136#[doc(hidden)] // implementation detail (yet?)
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub struct EquivPredicate<V> {
139    value: V,
140}
141
142impl<V: fmt::Debug> fmt::Display for EquivPredicate<V> {
143    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(formatter, "var == {:?}", self.value)
145    }
146}
147
148impl<V: fmt::Debug> PredicateReflection for EquivPredicate<V> {}
149
150impl<V: fmt::Debug + PartialEq<TracedValue>> Predicate<TracedValue> for EquivPredicate<V> {
151    fn eval(&self, variable: &TracedValue) -> bool {
152        self.value == *variable
153    }
154
155    fn find_case(&self, expected: bool, variable: &TracedValue) -> Option<Case<'_>> {
156        if self.eval(variable) == expected {
157            let product = Product::new("var", format!("{variable:?}"));
158            Some(Case::new(Some(self), expected).add_product(product))
159        } else {
160            None
161        }
162    }
163}
164
165/// Creates a predicate for a [`TracedValue`] that checks whether the value matches
166/// the specified criteria for a particular subtype (e.g., an unsigned integer).
167/// If the value has another subtype, the predicate is false.
168///
169/// Returned predicates can be supplied to the [`field()`] function.
170///
171/// # Arguments
172///
173/// The argument must be a predicate for one of types that can be obtained from a [`TracedValue`]
174/// reference: `bool`, `i64`, `i128`, `u64`, `u128`, `f64`, or `str`. The type can be specified
175/// explicitly, but usually you can make Rust properly infer it.
176///
177/// # Examples
178///
179/// ```
180/// # use predicates::{ord::{gt, ne}, iter::in_hash, str::contains};
181/// # use tracing_capture::predicates::{field, value};
182/// let _ = field("return", value(gt(5.0)));
183/// let _ = field("name", value(contains("test")));
184/// let _ = field("float", value(in_hash([3_u64, 5])));
185/// // ^ Note the specified integer type.
186/// ```
187pub fn value<T, P>(matches: P) -> ValuePredicate<T, P>
188where
189    T: for<'a> FromTracedValue<'a> + ?Sized,
190    P: Predicate<T>,
191{
192    ValuePredicate {
193        matches,
194        _ty: PhantomData,
195    }
196}
197
198/// Predicate for [`TracedValue`]s returned by the [`value()`] function.
199#[derive(Debug)]
200pub struct ValuePredicate<T: ?Sized, P> {
201    matches: P,
202    _ty: PhantomData<fn(T)>,
203}
204
205impl<T: ?Sized, P: Clone> Clone for ValuePredicate<T, P> {
206    fn clone(&self) -> Self {
207        Self {
208            matches: self.matches.clone(),
209            _ty: PhantomData,
210        }
211    }
212}
213
214impl<T: ?Sized, P: Copy> Copy for ValuePredicate<T, P> {}
215
216impl<T: ?Sized, P: PartialEq> PartialEq for ValuePredicate<T, P> {
217    fn eq(&self, other: &Self) -> bool {
218        self.matches == other.matches
219    }
220}
221
222impl<T, P> fmt::Display for ValuePredicate<T, P>
223where
224    T: for<'a> FromTracedValue<'a> + ?Sized,
225    P: Predicate<T>,
226{
227    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
228        write!(formatter, "is<{}>({})", type_name::<T>(), self.matches)
229    }
230}
231
232impl<T, P> PredicateReflection for ValuePredicate<T, P>
233where
234    T: for<'a> FromTracedValue<'a> + ?Sized,
235    P: Predicate<T>,
236{
237}
238
239impl<T, P> Predicate<TracedValue> for ValuePredicate<T, P>
240where
241    T: for<'a> FromTracedValue<'a> + ?Sized,
242    P: Predicate<T>,
243{
244    fn eval(&self, variable: &TracedValue) -> bool {
245        T::from_value(variable).map_or(false, |value| self.matches.eval(value.borrow()))
246    }
247
248    fn find_case(&self, expected: bool, variable: &TracedValue) -> Option<Case<'_>> {
249        let value = T::from_value(variable);
250        let value = if let Some(value) = &value {
251            value.borrow()
252        } else {
253            return if expected {
254                None // was expecting another var type
255            } else {
256                let product = Product::new(format!("var.as<{}>", type_name::<T>()), "None");
257                Some(Case::new(Some(self), expected).add_product(product))
258            };
259        };
260
261        let child = self.matches.find_case(expected, value)?;
262        Some(Case::new(Some(self), expected).add_child(child))
263    }
264}
265
266impl<T, P> IntoFieldPredicate for ValuePredicate<T, P>
267where
268    T: for<'a> FromTracedValue<'a> + ?Sized,
269    P: Predicate<T>,
270{
271    type Predicate = Self;
272
273    fn into_predicate(self) -> Self::Predicate {
274        self
275    }
276}
277
278/// Creates a predicate for the message of a [`CapturedEvent`].
279///
280/// # Arguments
281///
282/// The argument of this function is a `str` predicate for the event message.
283///
284/// # Examples
285///
286/// ```
287/// # use predicates::{ord::eq, str::contains};
288/// # use tracing_subscriber::{layer::SubscriberExt, Registry};
289/// # use tracing_capture::{predicates::{message, ScanExt}, CaptureLayer, SharedStorage};
290/// let storage = SharedStorage::default();
291/// let subscriber = Registry::default().with(CaptureLayer::new(&storage));
292/// tracing::subscriber::with_default(subscriber, || {
293///     tracing::info_span!("compute").in_scope(|| {
294///         tracing::info!(result = 42, "computations completed");
295///     });
296/// });
297///
298/// let storage = storage.lock();
299/// // All of these access the single captured event.
300/// let events = storage.scan_events();
301/// let _ = events.single(&message(eq("computations completed")));
302/// let _ = events.single(&message(contains("completed")));
303/// ```
304pub fn message<P: Predicate<str>>(matches: P) -> MessagePredicate<P> {
305    MessagePredicate { matches }
306}
307
308/// Predicate for the message of a [`CapturedEvent`] returned by the [`message()`] function.
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub struct MessagePredicate<P> {
311    matches: P,
312}
313
314impl_bool_ops!(MessagePredicate<P>);
315
316impl<P: Predicate<str>> fmt::Display for MessagePredicate<P> {
317    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
318        write!(formatter, "message({})", self.matches)
319    }
320}
321
322impl<P: Predicate<str>> PredicateReflection for MessagePredicate<P> {}
323
324impl<P: Predicate<str>> Predicate<CapturedEvent<'_>> for MessagePredicate<P> {
325    fn eval(&self, variable: &CapturedEvent<'_>) -> bool {
326        variable
327            .message()
328            .map_or(false, |value| self.matches.eval(value))
329    }
330
331    fn find_case(&self, expected: bool, variable: &CapturedEvent<'_>) -> Option<Case<'_>> {
332        let message = if let Some(message) = variable.message() {
333            message
334        } else {
335            return if expected {
336                None // was expecting a variable, but there is none
337            } else {
338                let product = Product::new("message", "None");
339                Some(Case::new(Some(self), expected).add_product(product))
340            };
341        };
342
343        let child = self.matches.find_case(expected, message)?;
344        Some(Case::new(Some(self), expected).add_child(child))
345    }
346}