console_subscriber/
visitors.rs

1//! These visitors are responsible for extracting the relevant
2//! fields from tracing metadata and producing the parts
3//! needed to construct `Event` instances.
4
5use super::{attribute, WakeOp};
6use console_api as proto;
7use proto::resources::resource;
8use tracing_core::{
9    field::{self, Visit},
10    span,
11};
12
13const LOCATION_FILE: &str = "loc.file";
14const LOCATION_LINE: &str = "loc.line";
15const LOCATION_COLUMN: &str = "loc.col";
16const INHERIT_FIELD_NAME: &str = "inherits_child_attrs";
17
18/// Used to extract the fields needed to construct
19/// an Event::Resource from the metadata of a tracing span
20/// that has the following shape:
21///
22/// tracing::trace_span!(
23///     "runtime.resource",
24///     concrete_type = "Sleep",
25///     kind = "timer",
26///     is_internal = true,
27///     inherits_child_attrs = true,
28/// );
29///
30/// Fields:
31/// concrete_type - indicates the concrete rust type for this resource
32/// kind - indicates the type of resource (i.e. timer, sync, io )
33/// is_internal - whether this is a resource type that is not exposed publicly (i.e. BatchSemaphore)
34/// inherits_child_attrs - whether this resource should inherit the state attributes of its children
35#[derive(Default)]
36pub(crate) struct ResourceVisitor {
37    concrete_type: Option<String>,
38    kind: Option<resource::Kind>,
39    is_internal: bool,
40    inherit_child_attrs: bool,
41    line: Option<u32>,
42    file: Option<String>,
43    column: Option<u32>,
44}
45
46pub(crate) struct ResourceVisitorResult {
47    pub(crate) concrete_type: String,
48    pub(crate) kind: resource::Kind,
49    pub(crate) location: Option<proto::Location>,
50    pub(crate) is_internal: bool,
51    pub(crate) inherit_child_attrs: bool,
52}
53
54/// Used to extract all fields from the metadata
55/// of a tracing span
56pub(crate) struct FieldVisitor {
57    fields: Vec<proto::Field>,
58    meta_id: proto::MetaId,
59}
60
61/// Used to extract the fields needed to construct
62/// an `Event::Spawn` from the metadata of a tracing span
63/// that has the following shape:
64///
65/// ```
66/// tracing::trace_span!(
67///     target: "tokio::task",
68///     "runtime.spawn",
69///     kind = "local",
70///     task.name = "some_name",
71///     loc.file = "some_file.rs",
72///     loc.line = 555,
73///     loc.col = 5,
74/// );
75/// ```
76///
77/// # Fields
78///
79/// This visitor has special behavior for `loc.line`, `loc.file`, and `loc.col`
80/// fields, which are interpreted as a Rust source code location where the task
81/// was spawned, if they are present. Other fields are recorded as arbitrary
82/// key-value pairs.
83pub(crate) struct TaskVisitor {
84    field_visitor: FieldVisitor,
85    line: Option<u32>,
86    file: Option<String>,
87    column: Option<u32>,
88}
89
90/// Used to extract the fields needed to construct
91/// an Event::AsyncOp from the metadata of a tracing span
92/// that has the following shape:
93///
94/// tracing::trace_span!(
95///     "runtime.resource.async_op",
96///     source = "Sleep::new_timeout",
97/// );
98///
99/// Fields:
100/// source - the method which has created an instance of this async operation
101#[derive(Default)]
102pub(crate) struct AsyncOpVisitor {
103    source: Option<String>,
104    inherit_child_attrs: bool,
105}
106
107/// Used to extract the fields needed to construct
108/// an Event::Waker from the metadata of a tracing span
109/// that has the following shape:
110///
111/// tracing::trace!(
112///     target: "tokio::task::waker",
113///     op = "waker.clone",
114///     task.id = id.into_u64(),
115/// );
116///
117/// Fields:
118/// task.id - the id of the task this waker will wake
119/// op - the operation associated with this waker event
120#[derive(Default)]
121pub(crate) struct WakerVisitor {
122    id: Option<span::Id>,
123    op: Option<WakeOp>,
124}
125
126/// Used to extract the fields needed to construct
127/// an Event::PollOp from the metadata of a tracing event
128/// that has the following shape:
129///
130/// tracing::trace!(
131///     target: "runtime::resource::poll_op",
132///     op_name = "poll_elapsed",
133///     readiness = "pending"
134/// );
135///
136/// Fields:
137/// op_name - the name of this resource poll operation
138/// readiness - the result of invoking this poll op, describing its readiness
139#[derive(Default)]
140pub(crate) struct PollOpVisitor {
141    op_name: Option<String>,
142    is_ready: Option<bool>,
143}
144
145/// Used to extract the fields needed to construct
146/// an Event::StateUpdate from the metadata of a tracing event
147/// that has the following shape:
148///
149/// tracing::trace!(
150///     target: "runtime::resource::state_update",
151///     duration = duration,
152///     duration.unit = "ms",
153///     duration.op = "override",
154/// );
155///
156/// Fields:
157/// attribute_name - a field value for a field that has the name of the resource attribute being updated
158/// value - the value for this update
159/// unit - the unit for the value being updated (e.g. ms, s, bytes)
160/// op - the operation that this update performs to the value of the resource attribute (one of: ovr, sub, add)
161pub(crate) struct StateUpdateVisitor {
162    meta_id: proto::MetaId,
163    field: Option<proto::Field>,
164    unit: Option<String>,
165    op: Option<attribute::UpdateOp>,
166}
167
168impl ResourceVisitor {
169    pub(crate) const RES_SPAN_NAME: &'static str = "runtime.resource";
170    const RES_CONCRETE_TYPE_FIELD_NAME: &'static str = "concrete_type";
171    const RES_VIZ_FIELD_NAME: &'static str = "is_internal";
172    const RES_KIND_FIELD_NAME: &'static str = "kind";
173    const RES_KIND_TIMER: &'static str = "timer";
174
175    pub(crate) fn result(self) -> Option<ResourceVisitorResult> {
176        let concrete_type = self.concrete_type?;
177        let kind = self.kind?;
178
179        let location = if self.file.is_some() && self.line.is_some() && self.column.is_some() {
180            Some(proto::Location {
181                file: self.file,
182                line: self.line,
183                column: self.column,
184                ..Default::default()
185            })
186        } else {
187            None
188        };
189
190        Some(ResourceVisitorResult {
191            concrete_type,
192            kind,
193            location,
194            is_internal: self.is_internal,
195            inherit_child_attrs: self.inherit_child_attrs,
196        })
197    }
198}
199
200impl Visit for ResourceVisitor {
201    fn record_debug(&mut self, _: &field::Field, _: &dyn std::fmt::Debug) {}
202
203    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
204        match field.name() {
205            Self::RES_CONCRETE_TYPE_FIELD_NAME => self.concrete_type = Some(value.to_string()),
206            Self::RES_KIND_FIELD_NAME => {
207                let kind = Some(match value {
208                    Self::RES_KIND_TIMER => {
209                        resource::kind::Kind::Known(resource::kind::Known::Timer as i32)
210                    }
211                    other => resource::kind::Kind::Other(other.to_string()),
212                });
213                self.kind = Some(resource::Kind { kind });
214            }
215            LOCATION_FILE => self.file = Some(value.to_string()),
216            _ => {}
217        }
218    }
219
220    fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
221        match field.name() {
222            Self::RES_VIZ_FIELD_NAME => self.is_internal = value,
223            INHERIT_FIELD_NAME => self.inherit_child_attrs = value,
224            _ => {}
225        }
226    }
227
228    fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
229        match field.name() {
230            LOCATION_LINE => self.line = Some(value as u32),
231            LOCATION_COLUMN => self.column = Some(value as u32),
232            _ => {}
233        }
234    }
235}
236
237impl FieldVisitor {
238    pub(crate) fn new(meta_id: proto::MetaId) -> Self {
239        FieldVisitor {
240            fields: Vec::default(),
241            meta_id,
242        }
243    }
244    pub(crate) fn result(self) -> Vec<proto::Field> {
245        self.fields
246    }
247}
248
249impl TaskVisitor {
250    pub(crate) fn new(meta_id: proto::MetaId) -> Self {
251        TaskVisitor {
252            field_visitor: FieldVisitor::new(meta_id),
253            line: None,
254            file: None,
255            column: None,
256        }
257    }
258
259    pub(crate) fn result(self) -> (Vec<proto::Field>, Option<proto::Location>) {
260        let fields = self.field_visitor.result();
261        let location = if self.file.is_some() && self.line.is_some() && self.column.is_some() {
262            Some(proto::Location {
263                file: self.file,
264                line: self.line,
265                column: self.column,
266                ..Default::default()
267            })
268        } else {
269            None
270        };
271
272        (fields, location)
273    }
274}
275
276impl Visit for TaskVisitor {
277    fn record_debug(&mut self, field: &field::Field, value: &dyn std::fmt::Debug) {
278        self.field_visitor.record_debug(field, value);
279    }
280
281    fn record_i64(&mut self, field: &tracing_core::Field, value: i64) {
282        self.field_visitor.record_i64(field, value);
283    }
284
285    fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
286        match field.name() {
287            LOCATION_LINE => self.line = Some(value as u32),
288            LOCATION_COLUMN => self.column = Some(value as u32),
289            _ => self.field_visitor.record_u64(field, value),
290        }
291    }
292
293    fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
294        self.field_visitor.record_bool(field, value);
295    }
296
297    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
298        if field.name() == LOCATION_FILE {
299            self.file = Some(value.to_string());
300        } else {
301            self.field_visitor.record_str(field, value);
302        }
303    }
304}
305
306impl Visit for FieldVisitor {
307    fn record_debug(&mut self, field: &field::Field, value: &dyn std::fmt::Debug) {
308        self.fields.push(proto::Field {
309            name: Some(field.name().into()),
310            value: Some(value.into()),
311            metadata_id: Some(self.meta_id),
312        });
313    }
314
315    fn record_i64(&mut self, field: &tracing_core::Field, value: i64) {
316        self.fields.push(proto::Field {
317            name: Some(field.name().into()),
318            value: Some(value.into()),
319            metadata_id: Some(self.meta_id),
320        });
321    }
322
323    fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
324        self.fields.push(proto::Field {
325            name: Some(field.name().into()),
326            value: Some(value.into()),
327            metadata_id: Some(self.meta_id),
328        });
329    }
330
331    fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
332        self.fields.push(proto::Field {
333            name: Some(field.name().into()),
334            value: Some(value.into()),
335            metadata_id: Some(self.meta_id),
336        });
337    }
338
339    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
340        self.fields.push(proto::Field {
341            name: Some(field.name().into()),
342            value: Some(value.into()),
343            metadata_id: Some(self.meta_id),
344        });
345    }
346}
347
348impl AsyncOpVisitor {
349    pub(crate) const ASYNC_OP_SPAN_NAME: &'static str = "runtime.resource.async_op";
350    const ASYNC_OP_SRC_FIELD_NAME: &'static str = "source";
351
352    pub(crate) fn result(self) -> Option<(String, bool)> {
353        let inherit = self.inherit_child_attrs;
354        self.source.map(|s| (s, inherit))
355    }
356}
357
358impl Visit for AsyncOpVisitor {
359    fn record_debug(&mut self, _: &field::Field, _: &dyn std::fmt::Debug) {}
360
361    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
362        if field.name() == Self::ASYNC_OP_SRC_FIELD_NAME {
363            self.source = Some(value.to_string());
364        }
365    }
366
367    fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
368        if field.name() == INHERIT_FIELD_NAME {
369            self.inherit_child_attrs = value;
370        }
371    }
372}
373
374impl WakerVisitor {
375    const WAKE: &'static str = "waker.wake";
376    const WAKE_BY_REF: &'static str = "waker.wake_by_ref";
377    const CLONE: &'static str = "waker.clone";
378    const DROP: &'static str = "waker.drop";
379    const TASK_ID_FIELD_NAME: &'static str = "task.id";
380
381    pub(crate) fn result(self) -> Option<(span::Id, WakeOp)> {
382        self.id.zip(self.op)
383    }
384}
385
386impl Visit for WakerVisitor {
387    fn record_debug(&mut self, _: &field::Field, _: &dyn std::fmt::Debug) {
388        // don't care (yet?)
389    }
390
391    fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
392        if field.name() == Self::TASK_ID_FIELD_NAME {
393            self.id = Some(span::Id::from_u64(value));
394        }
395    }
396
397    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
398        if field.name() == "op" {
399            self.op = Some(match value {
400                Self::WAKE => WakeOp::Wake { self_wake: false },
401                Self::WAKE_BY_REF => WakeOp::WakeByRef { self_wake: false },
402                Self::CLONE => WakeOp::Clone,
403                Self::DROP => WakeOp::Drop,
404                _ => return,
405            });
406        }
407    }
408}
409
410impl PollOpVisitor {
411    pub(crate) const POLL_OP_EVENT_TARGET: &'static str = "runtime::resource::poll_op";
412    const OP_NAME_FIELD_NAME: &'static str = "op_name";
413    const OP_READINESS_FIELD_NAME: &'static str = "is_ready";
414
415    pub(crate) fn result(self) -> Option<(String, bool)> {
416        let op_name = self.op_name?;
417        let is_ready = self.is_ready?;
418        Some((op_name, is_ready))
419    }
420}
421
422impl Visit for PollOpVisitor {
423    fn record_debug(&mut self, _: &field::Field, _: &dyn std::fmt::Debug) {}
424
425    fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
426        if field.name() == Self::OP_READINESS_FIELD_NAME {
427            self.is_ready = Some(value)
428        }
429    }
430
431    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
432        if field.name() == Self::OP_NAME_FIELD_NAME {
433            self.op_name = Some(value.to_string());
434        }
435    }
436}
437
438impl StateUpdateVisitor {
439    pub(crate) const RE_STATE_UPDATE_EVENT_TARGET: &'static str = "runtime::resource::state_update";
440    pub(crate) const AO_STATE_UPDATE_EVENT_TARGET: &'static str =
441        "runtime::resource::async_op::state_update";
442
443    const STATE_OP_SUFFIX: &'static str = ".op";
444    const STATE_UNIT_SUFFIX: &'static str = ".unit";
445
446    const OP_ADD: &'static str = "add";
447    const OP_SUB: &'static str = "sub";
448    const OP_OVERRIDE: &'static str = "override";
449
450    pub(crate) fn new(meta_id: proto::MetaId) -> Self {
451        StateUpdateVisitor {
452            meta_id,
453            field: None,
454            unit: None,
455            op: None,
456        }
457    }
458
459    pub(crate) fn result(self) -> Option<attribute::Update> {
460        Some(attribute::Update {
461            field: self.field?,
462            op: self.op,
463            unit: self.unit,
464        })
465    }
466}
467
468impl Visit for StateUpdateVisitor {
469    fn record_debug(&mut self, field: &field::Field, value: &dyn std::fmt::Debug) {
470        if !field.name().ends_with(Self::STATE_OP_SUFFIX)
471            && !field.name().ends_with(Self::STATE_UNIT_SUFFIX)
472        {
473            self.field = Some(proto::Field {
474                name: Some(field.name().into()),
475                value: Some(value.into()),
476                metadata_id: Some(self.meta_id),
477            });
478        }
479    }
480
481    fn record_i64(&mut self, field: &field::Field, value: i64) {
482        if !field.name().ends_with(Self::STATE_OP_SUFFIX)
483            && !field.name().ends_with(Self::STATE_UNIT_SUFFIX)
484        {
485            self.field = Some(proto::Field {
486                name: Some(field.name().into()),
487                value: Some(value.into()),
488                metadata_id: Some(self.meta_id),
489            });
490        }
491    }
492
493    fn record_u64(&mut self, field: &field::Field, value: u64) {
494        if !field.name().ends_with(Self::STATE_OP_SUFFIX)
495            && !field.name().ends_with(Self::STATE_UNIT_SUFFIX)
496        {
497            self.field = Some(proto::Field {
498                name: Some(field.name().into()),
499                value: Some(value.into()),
500                metadata_id: Some(self.meta_id),
501            });
502        }
503    }
504
505    fn record_bool(&mut self, field: &field::Field, value: bool) {
506        if !field.name().ends_with(Self::STATE_OP_SUFFIX)
507            && !field.name().ends_with(Self::STATE_UNIT_SUFFIX)
508        {
509            self.field = Some(proto::Field {
510                name: Some(field.name().into()),
511                value: Some(value.into()),
512                metadata_id: Some(self.meta_id),
513            });
514        }
515    }
516
517    fn record_str(&mut self, field: &field::Field, value: &str) {
518        if field.name().ends_with(Self::STATE_OP_SUFFIX) {
519            match value {
520                Self::OP_ADD => self.op = Some(attribute::UpdateOp::Add),
521                Self::OP_SUB => self.op = Some(attribute::UpdateOp::Sub),
522                Self::OP_OVERRIDE => self.op = Some(attribute::UpdateOp::Override),
523                _ => {}
524            };
525        } else if field.name().ends_with(Self::STATE_UNIT_SUFFIX) {
526            self.unit = Some(value.to_string());
527        } else {
528            self.field = Some(proto::Field {
529                name: Some(field.name().into()),
530                value: Some(value.into()),
531                metadata_id: Some(self.meta_id),
532            });
533        }
534    }
535}