// Copyright (C) 2023 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Tracing-subscriber layer for libatrace_rust. use ::atrace::AtraceTag; use std::fmt::Write; use tracing::span::Attributes; use tracing::span::Record; use tracing::{Event, Id, Subscriber}; use tracing_subscriber::field::Visit; use tracing_subscriber::layer::{Context, Layer}; use tracing_subscriber::registry::LookupSpan; /// Subscriber layer that forwards events to ATrace. pub struct AtraceSubscriber { tag: AtraceTag, should_record_fields: bool, should_filter: bool, } impl Default for AtraceSubscriber { fn default() -> Self { Self::new(AtraceTag::App) } } impl AtraceSubscriber { /// Makes a new subscriber with tag. pub fn new(tag: AtraceTag) -> AtraceSubscriber { AtraceSubscriber { tag, should_filter: false, should_record_fields: true } } /// Enables event and span filtering. With filtering enabled, this layer would filter events for /// all the layers of the subscriber. /// Use this to speed up the subscriber if it's the only layer. Do not enable if you need other /// layers to receive events when ATrace is disabled. pub fn with_filter(self) -> AtraceSubscriber { AtraceSubscriber { should_filter: true, ..self } } /// Disables recording of field values. pub fn without_fields(self) -> AtraceSubscriber { AtraceSubscriber { should_record_fields: false, ..self } } } // Internal methods. impl AtraceSubscriber { /// Checks that events and spans should be recorded in the span/event notification. fn should_process_event(&self) -> bool { // If `should_filter == true` we don't need to check the tag - it was already checked by // the layer filter in the `Layer::enabled()` method. // The checks are done in this order: // * `Layer::register_callsite()` - once per callsite, the result is cached. // * `Layer::enabled()` - once per span or event construction if the callsite is enabled. // * `should_process_event()` - on every notification like new span, span enter/exit/record, event. // The first two checks are global, i.e. affect other layers, and only enabled with `should_filter`. // Read more: // https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#filtering-with-layers self.should_filter || atrace::atrace_is_tag_enabled(self.tag) } } impl LookupSpan<'lookup>> Layer for AtraceSubscriber { fn register_callsite( &self, _metadata: &'static tracing::Metadata<'static>, ) -> tracing::subscriber::Interest { if self.should_filter { // When we return `Interest::sometimes()`, the `enabled()` method would get checked // every time. // We can't use callsite caching (`Interest::never()`) because there's no callback // for when tracing gets enabled - we need to check it every time. tracing::subscriber::Interest::sometimes() } else { // If we do not disable events in the layer, we always receive the notifications. tracing::subscriber::Interest::always() } } // When filtering in this layer is enabled, this method would get called on every event and span. // This filter affects all layers, so if this method returns false, it would disable the event // for others as well. fn enabled(&self, _metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool { !self.should_filter || atrace::atrace_is_tag_enabled(self.tag) } fn on_new_span(&self, attrs: &Attributes, id: &Id, ctx: Context) { if !self.should_record_fields || attrs.fields().is_empty() || !self.should_process_event() { return; } let span = ctx.span(id).unwrap(); let mut formatter = FieldFormatter::for_span(span.metadata().name()); attrs.record(&mut formatter); span.extensions_mut().insert(formatter); } fn on_record(&self, span: &Id, values: &Record, ctx: Context) { if !self.should_record_fields || !self.should_process_event() { return; } values .record(ctx.span(span).unwrap().extensions_mut().get_mut::().unwrap()); } fn on_enter(&self, id: &Id, ctx: Context) { if !self.should_process_event() { return; } let span = ctx.span(id).unwrap(); if span.fields().is_empty() || !self.should_record_fields { atrace::atrace_begin(self.tag, span.metadata().name()); } else { let span_extensions = span.extensions(); let formatter = span_extensions.get::().unwrap(); atrace::atrace_begin(self.tag, formatter.as_str()); } } fn on_exit(&self, _id: &Id, _ctx: Context) { if !self.should_process_event() { return; } atrace::atrace_end(self.tag); } fn on_event(&self, event: &Event, _ctx: Context) { if !self.should_process_event() { return; } if self.should_record_fields { let mut formatter = FieldFormatter::for_event(); event.record(&mut formatter); atrace::atrace_instant(self.tag, formatter.as_str()); } else if let Some(field) = event.metadata().fields().field("message") { struct MessageVisitor<'a> { tag: AtraceTag, field: &'a tracing::field::Field, } impl Visit for MessageVisitor<'_> { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { if field == self.field { atrace::atrace_instant(self.tag, value); } } fn record_debug( &mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug, ) { if field == self.field { atrace::atrace_instant(self.tag, &format!("{:?}", value)); } } } event.record(&mut MessageVisitor { tag: self.tag, field: &field }); } else { atrace::atrace_instant( self.tag, &format!("{} event", event.metadata().level().as_str()), ); } } } struct FieldFormatter { is_event: bool, s: String, } impl FieldFormatter { fn new() -> FieldFormatter { const DEFAULT_STR_CAPACITY: usize = 128; // Should fit most events without realloc. FieldFormatter { is_event: true, s: String::with_capacity(DEFAULT_STR_CAPACITY) } } fn for_event() -> FieldFormatter { FieldFormatter { is_event: true, ..FieldFormatter::new() } } fn for_span(span_name: &str) -> FieldFormatter { let mut formatter = FieldFormatter { is_event: false, ..FieldFormatter::new() }; formatter.s.push_str(span_name); formatter } fn as_str(&self) -> &str { &self.s } fn add_delimeter_if_needed(&mut self) { if !self.s.is_empty() { self.s.push_str(", "); } } } impl Visit for FieldFormatter { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { self.add_delimeter_if_needed(); if self.is_event && field.name() == "message" { self.s.push_str(value); } else { write!(&mut self.s, "{} = \"{}\"", field.name(), value).unwrap(); } } fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { self.add_delimeter_if_needed(); if self.is_event && field.name() == "message" { write!(&mut self.s, "{:?}", value).unwrap(); } else { write!(&mut self.s, "{} = {:?}", field.name(), value).unwrap(); } } } #[cfg(test)] use self::tests::mock_atrace as atrace; #[cfg(test)] mod tests { use super::*; use tracing::Level; use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; pub mod mock_atrace { use atrace::AtraceTag; use std::cell::RefCell; /// Contains logic to check binding calls. /// Implement this trait in the test with mocking logic and checks in implemented functions. /// Default implementations panic. pub trait ATraceMocker { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { panic!("Unexpected call"); } fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) { panic!("Unexpected call"); } fn atrace_end(&mut self, _tag: AtraceTag) { panic!("Unexpected call"); } fn atrace_instant(&mut self, _tag: AtraceTag, _name: &str) { panic!("Unexpected call"); } /// This method should contain checks to be performed at the end of the test. fn finish(&self) {} } struct DefaultMocker; impl ATraceMocker for DefaultMocker {} // Global mock object is thread-local, so that the tests can run safely in parallel. thread_local!(static MOCKER: RefCell> = RefCell::new(Box::new(DefaultMocker{}))); /// Sets the global mock object. fn set_mocker(mocker: Box) { MOCKER.with(|m| *m.borrow_mut() = mocker) } /// Calls the passed method `f` with a mutable reference to the global mock object. /// Example: /// ``` /// with_mocker(|mocker| mocker.atrace_begin(tag, name)) /// ``` fn with_mocker(f: F) -> R where F: FnOnce(&mut dyn ATraceMocker) -> R, { MOCKER.with(|m| f(m.borrow_mut().as_mut())) } /// Finish the test and perform final checks in the mocker. /// Calls `finish()` on the global mocker. /// /// Needs to be called manually at the end of each test that uses mocks. /// /// May panic, so it can not be called in `drop()` methods, /// since it may result in double panic. pub fn mocker_finish() { with_mocker(|m| m.finish()) } /// RAII guard that resets the mock to the default implementation. pub struct MockerGuard; impl Drop for MockerGuard { fn drop(&mut self) { set_mocker(Box::new(DefaultMocker {})); } } /// Sets the mock object for the duration of the scope. /// /// Returns a RAII guard that resets the mock back to default on destruction. pub fn set_scoped_mocker(m: T) -> MockerGuard { set_mocker(Box::new(m)); MockerGuard {} } // Wrapped functions that forward calls into mocker. pub fn atrace_is_tag_enabled(tag: AtraceTag) -> bool { with_mocker(|m| m.atrace_is_tag_enabled(tag)) } pub fn atrace_begin(tag: AtraceTag, name: &str) { with_mocker(|m| m.atrace_begin(tag, name)) } pub fn atrace_end(tag: AtraceTag) { with_mocker(|m| m.atrace_end(tag)) } pub fn atrace_instant(tag: AtraceTag, name: &str) { with_mocker(|m| m.atrace_instant(tag, name)) } } #[test] fn emits_span_begin() { #[derive(Default)] struct CallCheck { begin_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_begin(&mut self, tag: AtraceTag, name: &str) { self.begin_count += 1; assert!(self.begin_count < 2); assert_eq!(tag, AtraceTag::App); assert_eq!(name, "test span"); } fn atrace_end(&mut self, _tag: AtraceTag) {} fn finish(&self) { assert_eq!(self.begin_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default()), ); let _span = tracing::info_span!("test span").entered(); mock_atrace::mocker_finish(); } #[test] fn emits_span_end() { #[derive(Default)] struct CallCheck { end_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) {} fn atrace_end(&mut self, tag: AtraceTag) { self.end_count += 1; assert!(self.end_count < 2); assert_eq!(tag, AtraceTag::App); } fn finish(&self) { assert_eq!(self.end_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default()), ); { let _span = tracing::info_span!("test span").entered(); } mock_atrace::mocker_finish(); } #[test] fn span_begin_end_is_ordered() { #[derive(Default)] struct CallCheck { begin_count: u32, instant_count: u32, end_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) { assert_eq!(self.end_count, 0); assert_eq!(self.instant_count, 0); self.begin_count += 1; assert!(self.begin_count < 2); } fn atrace_instant(&mut self, _tag: AtraceTag, _name: &str) { assert_eq!(self.begin_count, 1); assert_eq!(self.end_count, 0); self.instant_count += 1; assert!(self.instant_count < 2); } fn atrace_end(&mut self, _tag: AtraceTag) { assert_eq!(self.begin_count, 1); assert_eq!(self.instant_count, 1); self.end_count += 1; assert!(self.end_count < 2); } fn finish(&self) { assert_eq!(self.begin_count, 1); assert_eq!(self.end_count, 1); assert_eq!(self.instant_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default()), ); { let _span = tracing::info_span!("span").entered(); tracing::info!("test info"); } mock_atrace::mocker_finish(); } #[test] fn emits_instant_event() { #[derive(Default)] struct CallCheck { instant_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_instant(&mut self, tag: AtraceTag, name: &str) { self.instant_count += 1; assert!(self.instant_count < 2); assert_eq!(tag, AtraceTag::App); assert_eq!(name, "test info"); } fn finish(&self) { assert_eq!(self.instant_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default()), ); tracing::info!("test info"); mock_atrace::mocker_finish(); } #[test] fn formats_event_without_message_with_fields_disabled() { #[derive(Default)] struct CallCheck { instant_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) { self.instant_count += 1; assert!(self.instant_count < 2); assert_eq!(name, "DEBUG event"); } fn finish(&self) { assert_eq!(self.instant_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default().without_fields()), ); tracing::debug!(foo = 1); mock_atrace::mocker_finish(); } #[test] fn formats_event_without_message_with_fields_enabled() { #[derive(Default)] struct CallCheck { instant_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) { self.instant_count += 1; assert!(self.instant_count < 2); assert_eq!(name, "foo = 1"); } fn finish(&self) { assert_eq!(self.instant_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default()), ); tracing::debug!(foo = 1); mock_atrace::mocker_finish(); } #[test] fn can_set_tag() { #[derive(Default)] struct CallCheck { begin_count: u32, instant_count: u32, end_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_begin(&mut self, tag: AtraceTag, _name: &str) { self.begin_count += 1; assert!(self.begin_count < 2); assert_eq!(tag, AtraceTag::WindowManager); } fn atrace_instant(&mut self, tag: AtraceTag, _name: &str) { self.instant_count += 1; assert!(self.instant_count < 2); assert_eq!(tag, AtraceTag::WindowManager); } fn atrace_end(&mut self, tag: AtraceTag) { self.end_count += 1; assert!(self.end_count < 2); assert_eq!(tag, AtraceTag::WindowManager); } fn finish(&self) { assert_eq!(self.begin_count, 1); assert_eq!(self.end_count, 1); assert_eq!(self.instant_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::new(AtraceTag::WindowManager)), ); { let _span = tracing::info_span!("span").entered(); tracing::info!("test info"); } mock_atrace::mocker_finish(); } #[test] fn fields_ignored_when_disabled() { #[derive(Default)] struct CallCheck { begin_count: u32, instant_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_begin(&mut self, _tag: AtraceTag, name: &str) { self.begin_count += 1; assert!(self.begin_count < 2); assert_eq!(name, "test span"); } fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) { self.instant_count += 1; assert!(self.instant_count < 2); assert_eq!(name, "test info"); } fn atrace_end(&mut self, _tag: AtraceTag) {} fn finish(&self) { assert_eq!(self.begin_count, 1); assert_eq!(self.instant_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default().without_fields()), ); let _span = tracing::info_span!("test span", bar = "foo").entered(); tracing::event!(Level::INFO, foo = "bar", "test info"); mock_atrace::mocker_finish(); } #[test] fn formats_instant_event_fields() { #[derive(Default)] struct CallCheck { instant_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) { self.instant_count += 1; assert!(self.instant_count < 2); assert_eq!(name, "test info, foo = \"bar\", baz = 5"); } fn finish(&self) { assert_eq!(self.instant_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default()), ); tracing::event!(Level::INFO, foo = "bar", baz = 5, "test info"); mock_atrace::mocker_finish(); } #[test] fn formats_span_fields() { #[derive(Default)] struct CallCheck { begin_count: u32, } impl mock_atrace::ATraceMocker for CallCheck { fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool { true } fn atrace_begin(&mut self, _tag: AtraceTag, name: &str) { self.begin_count += 1; assert!(self.begin_count < 2); assert_eq!(name, "test span, foo = \"bar\", baz = 5"); } fn atrace_end(&mut self, _tag: AtraceTag) {} fn finish(&self) { assert_eq!(self.begin_count, 1); } } let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default()); let _subscriber_guard = tracing::subscriber::set_default( tracing_subscriber::registry().with(AtraceSubscriber::default()), ); let _span = tracing::info_span!("test span", foo = "bar", baz = 5).entered(); mock_atrace::mocker_finish(); } }