Skip to content

Commit

Permalink
Doctests (#21)
Browse files Browse the repository at this point in the history
Move tests to doctests
Update a few docs
Fix crashes on Linux that only showed up when run as doctests.
  • Loading branch information
Robo210 authored Dec 16, 2024
1 parent e350142 commit 48fbc79
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 50 deletions.
145 changes: 106 additions & 39 deletions src/layer_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::native::{
///
/// Multiple `tracing_etw` layers can be created at the same time,
/// with different provider names/IDs, keywords, or output formats.
/// (Target filters)[tracing_subscriber::filter] can then be used to direct
/// [Target filters][tracing_subscriber::filter] can then be used to direct
/// specific events to specific layers.
pub struct LayerBuilder<OutMode: OutputMode> {
provider_name: Box<str>,
Expand All @@ -43,6 +43,20 @@ pub struct LayerBuilder<OutMode: OutputMode> {
}

impl LayerBuilder<NormalOutput> {
/// Creates a new ETW/user_events layer that will log events from a provider
/// with the given name.
///
/// ```
/// # use tracing_subscriber::prelude::*;
/// # let reg = tracing_subscriber::registry();
/// # let layer =
/// tracing_etw::LayerBuilder::new("SampleProviderName")
/// # ;
/// # let built = layer.build();
/// # assert!(built.is_ok());
/// # reg.with(built.unwrap());
/// ```
///
#[allow(clippy::new_ret_no_self)]
pub fn new(name: &str) -> LayerBuilder<NormalOutput> {
LayerBuilder::<NormalOutput> {
Expand All @@ -62,6 +76,18 @@ impl LayerBuilder<CommonSchemaOutput> {
/// Most ETW consumers will not benefit from events in this schema, and
/// may perform worse. Common Schema events are much slower to generate
/// and should not be enabled unless absolutely necessary.
///
/// ```
/// # use tracing_subscriber::prelude::*;
/// # let reg = tracing_subscriber::registry();
/// # let layer =
/// tracing_etw::LayerBuilder::new_common_schema_events("SampleProviderName")
/// # ;
/// # let built = layer.build();
/// # assert!(built.is_ok());
/// # reg.with(built.unwrap());
/// ```
///
pub fn new_common_schema_events(name: &str) -> LayerBuilder<CommonSchemaOutput> {
LayerBuilder::<CommonSchemaOutput> {
provider_name: name.to_string().into_boxed_str(),
Expand All @@ -77,6 +103,19 @@ impl<OutMode: OutputMode + 'static> LayerBuilder<OutMode> {
/// For advanced scenarios.
/// Assign a provider ID to the ETW provider rather than use
/// one generated from the provider name.
///
/// ```
/// # use tracing_subscriber::prelude::*;
/// # let reg = tracing_subscriber::registry();
/// # let layer =
/// tracing_etw::LayerBuilder::new("SampleProviderName")
/// .with_provider_id(&tracing_etw::native::GuidWrapper::from_name("SampleProviderName"))
/// # ;
/// # let built = layer.build();
/// # assert!(built.is_ok());
/// # reg.with(built.unwrap());
/// ```
///
pub fn with_provider_id<G>(mut self, guid: &G) -> Self
where
for<'a> &'a G: Into<GuidWrapper>,
Expand All @@ -88,6 +127,20 @@ impl<OutMode: OutputMode + 'static> LayerBuilder<OutMode> {
/// Get the current provider ID that will be used for the ETW provider.
/// This is a convenience function to help with tools that do not implement
/// the standard provider name to ID algorithm.
///
/// ```
/// # use tracing_subscriber::prelude::*;
/// # let reg = tracing_subscriber::registry();
/// # let layer =
/// tracing_etw::LayerBuilder::new("SampleProviderName")
/// # ;
/// assert!(
/// layer.get_provider_id() == tracing_etw::native::GuidWrapper::from_name("SampleProviderName"),
/// "default provider GUID is hashed from the provider name");
/// # let built = layer.build();
/// # assert!(built.is_ok());
/// # reg.with(built.unwrap());
/// ```
pub fn get_provider_id(&self) -> GuidWrapper {
self.provider_id
}
Expand All @@ -102,13 +155,36 @@ impl<OutMode: OutputMode + 'static> LayerBuilder<OutMode> {
///
/// Keyword value `0` is special in ETW (but not user_events), and should
/// not be used.
///
/// Keywords in ETW are bitmasks, with the high 16 bits being reserved by Microsoft.
/// See <https://learn.microsoft.com/en-us/windows/win32/wes/defining-keywords-used-to-classify-types-of-events>
/// for more information about keywords in ETW.
///
/// Keywords in user_events are not bitmasks.
///
/// ```
/// # use tracing_subscriber::prelude::*;
/// # let reg = tracing_subscriber::registry();
/// # let layer =
/// tracing_etw::LayerBuilder::new("SampleProviderName")
/// .with_default_keyword(0x1000)
/// # ;
/// # let built = layer.build();
/// # assert!(built.is_ok());
/// # reg.with(built.unwrap());
/// ```
///
pub fn with_default_keyword(mut self, kw: u64) -> Self {
self.default_keyword = kw;
self
}

/// For advanced scenarios.
/// Set the provider group to join this provider to.
///
/// For ETW, the group ID must be a GUID.
///
/// For user_events, the group ID must be a string.
pub fn with_provider_group<G>(mut self, group_id: &G) -> Self
where
for<'a> &'a G: Into<crate::native::ProviderGroupType>,
Expand Down Expand Up @@ -168,16 +244,25 @@ impl<OutMode: OutputMode + 'static> LayerBuilder<OutMode> {

#[cfg_attr(docsrs, doc(cfg(feature = "global_filter")))]
#[cfg(any(feature = "global_filter", docsrs))]
pub fn build_global_filter<S>(self) -> Result<EtwLayer<S, Mode>, EtwError>
pub fn build_global_filter<S>(self) -> Result<EtwLayer<S, OutMode>, EtwError>
where
S: Subscriber + for<'a> LookupSpan<'a>,
Mode::Provider: EventWriter<Mode> + 'static,
crate::native::Provider<OutMode>: EventWriter<OutMode>,
{
self.validate_config()?;

Ok(self.build_layer())
}

///
/// ```
/// # use tracing_subscriber::prelude::*;
/// # let reg = tracing_subscriber::registry();
/// let built_layer = tracing_etw::LayerBuilder::new("SampleProviderName").build();
/// assert!(built_layer.is_ok());
/// # reg.with(built_layer.unwrap());
/// ```
///
#[allow(clippy::type_complexity)]
#[cfg_attr(docsrs, doc(cfg(not(feature = "global_filter"))))]
#[cfg(any(not(feature = "global_filter"), docsrs))]
Expand All @@ -200,6 +285,24 @@ impl<OutMode: OutputMode + 'static> LayerBuilder<OutMode> {
/// Constructs the configured layer with a target [tracing_subscriber::filter] applied.
/// This can be used to target specific events to specific layers, and in effect allow
/// specific events to be logged only from specific ETW/user_event providers.
///
/// ```
/// # use tracing::event;
/// # use tracing_subscriber::prelude::*;
/// # let reg = tracing_subscriber::registry();
/// let built_layer = tracing_etw::LayerBuilder::new("SampleProviderName")
/// .build_with_target("MyTargetName");
/// assert!(built_layer.is_ok());
/// # reg.with(built_layer.unwrap());
///
/// // ...
///
/// event!(target: "MyTargetName", tracing::Level::INFO, "My event");
///
/// // When build_with_target is used, the provider name is also always added as a target
/// event!(target: "SampleProviderName", tracing::Level::INFO, "My event");
/// ```
///
#[allow(clippy::type_complexity)]
#[cfg_attr(docsrs, doc(cfg(not(feature = "global_filter"))))]
#[cfg(any(not(feature = "global_filter"), docsrs))]
Expand All @@ -222,39 +325,3 @@ impl<OutMode: OutputMode + 'static> LayerBuilder<OutMode> {
Ok(layer.with_filter(filter.and(targets)))
}
}

#[cfg(test)]
mod test {
use tracing_subscriber::{self, prelude::*};

use crate::native::GuidWrapper;

use super::LayerBuilder;

#[test]
fn build_normal() {
tracing_subscriber::registry()
.with(LayerBuilder::new("test_build_normal").build().unwrap());
}

#[test]
fn build_with_target() {
tracing_subscriber::registry().with(
LayerBuilder::new("test_build_with_target")
.with_default_keyword(5)
.build_with_target("asdf")
.unwrap(),
);
}

#[test]
fn build_provider_id() {
let provider_id = GuidWrapper::from_name("name");
tracing_subscriber::registry().with(
LayerBuilder::new("test_build_provider_id")
.with_provider_id(&provider_id)
.build()
.unwrap(),
);
}
}
36 changes: 27 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,29 +108,47 @@
//! to consumers asynchronously per the platform design.
//!
//! ### Heap Allocations
//!
//! Each `tracing-etw::Layer` that is added will heap allocate the provider name and GUID.
//!
//! Logging events with the [std::fmt::Debug](debug format specifier (`:?`)) will
//!
//! The goal of this crate is to have no heap allocations in the hot path (when an event is logged).
//! Currently there are a few circumstances when logging an event requires a heap
//! allocation, outlined below.
//!
//! Total memory usage by this crate will vary based on workload, but will usually
//! stay in the 10s or low 100s of kilobytes, and should remain stable once the
//! first event has been logged.
//!
//! - Each `tracing-etw::Layer` that is added will heap allocate some internal state
//! when `build` is called.
//!
//! - Logging events with the [Debug][std::fmt::Debug] format specifier (`:?`) will
//! necessitate a heap allocation to format the value into a string.
//!
//! Logging strings copies them to the heap first. This is a side-effect of how
//!
//! <div class="warning">
//!
//! All event messages (the format strings to the event) are, for reasons
//! outside of the control of this crate, formatted as debug formats rather than as
//! a string. This effectively means every single event is forced to perform at least
//! one heap allocation. In benchmarking, this does not seem to be a major performance issue.
//!
//! </div>
//!
//! - Logging strings copies them to the heap first. This is a side-effect of how
//! `tracing` presents the strings to each layer; the lifetime of the string is
//! too short for what this crate currently needs, but it may be possible to improve
//! this in the future.
//!
//! Logging a span allocates a copy of the span's fields on the heap. This is needed
//! - Logging a span allocates a copy of the span's fields on the heap. This is needed
//! so the values can be updated during execution and the final payload values logged
//! when the span ends. This allocation is freed when the span ends.
//!
//! The first time an event is logged (the event is enabled at the platform layer and
//! - The first time an event is logged (the event is enabled at the platform layer and
//! the logging code is run), this crate will scan the binary for any metadata left
//! by the `etw_event!` macro. This information will be cached in a single heap
//! allocation for later use by other logging calls. This cached memory is never freed
//! until the process exits; if this crate is used in a dynamic library that unloads
//! before the process exits, the memory will be leaked.
//!
//! A thread-local event builder is allocated for each thread that logs an event.
//! - A thread-local event builder is allocated for each thread that logs an event.
//! This allows for complete thread safety when logging events. This allocation
//! will stay alive until the thread ends. Additionally, the builder itself will allocate
//! scratch space for constructing the event. This scratch space will grow to fit the
Expand Down
2 changes: 1 addition & 1 deletion src/native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub(crate) use tracelogging_dynamic::Guid as native_guid;
use crate::error::EtwError;

#[doc(hidden)]
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct GuidWrapper(u128);

impl From<&native_guid> for GuidWrapper {
Expand Down
3 changes: 2 additions & 1 deletion src/statics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ fn process_static_metadata() -> Box<[ParsedEventMetadata]> {
// so we can guarantee we aren't making a reference to null here.
let events_slice = unsafe { &mut *core::ptr::slice_from_raw_parts_mut(start, stop_offset) };

if events_slice.is_empty() {
if events_slice.is_empty() || // On Windows, an empty binary produces an empty array
(events_slice.len() == 1 && events_slice[0].is_null()) { // On Linux, an empty binary produces a single array element of 0x0.
return Box::new([]);
}

Expand Down

0 comments on commit 48fbc79

Please sign in to comment.