├── .gitignore ├── Cargo.toml ├── CODE_OF_CONDUCT.md ├── win_etw_metadata ├── Cargo.toml └── src │ └── lib.rs ├── examples └── hello_world │ ├── Cargo.toml │ └── src │ └── main.rs ├── win_etw_logger ├── Cargo.toml ├── examples │ └── hello.rs └── src │ └── lib.rs ├── win_etw_macros ├── Cargo.toml └── src │ ├── well_known_types.rs │ ├── tests.rs │ └── lib.rs ├── .github └── workflows │ └── ci-workflow.yml ├── win_etw_tracing ├── Cargo.toml └── src │ └── lib.rs ├── LICENSE ├── win_etw_provider ├── Cargo.toml └── src │ ├── lib.rs │ ├── types.rs │ ├── guid.rs │ ├── data_descriptor.rs │ └── provider.rs ├── SECURITY.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | /.vscode 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "examples/hello_world", 6 | "win_etw_logger", 7 | "win_etw_macros", 8 | "win_etw_metadata", 9 | "win_etw_provider", 10 | "win_etw_tracing", 11 | ] 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /win_etw_metadata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "win_etw_metadata" 3 | version = "0.1.3" 4 | authors = ["Arlie Davis "] 5 | edition = "2018" 6 | description = "Provides metadata definitions for the win_etw_provider and win_etw_macros crates." 7 | license = "Apache-2.0 OR MIT" 8 | repository = "https://github.com/microsoft/rust_win_etw" 9 | readme = "../README.md" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | bitflags = "^2.0" 15 | 16 | [features] 17 | default = ["metadata_headers"] 18 | metadata_headers = [] 19 | -------------------------------------------------------------------------------- /examples/hello_world/Cargo.toml: -------------------------------------------------------------------------------- 1 | # This package is not published. 2 | 3 | [package] 4 | name = "hello_world" 5 | version = "0.1.1" 6 | authors = ["Arlie Davis "] 7 | edition = "2018" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | win_etw_macros = { path = "../../win_etw_macros" } 13 | win_etw_provider = { path = "../../win_etw_provider", features = ["std"] } 14 | widestring = "^1.0" 15 | 16 | [target.'cfg(windows)'.dependencies] 17 | windows-sys = { version = "0.61.2", features = ["Win32_Foundation"] } 18 | 19 | [features] 20 | default = [] 21 | -------------------------------------------------------------------------------- /win_etw_logger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "win_etw_logger" 3 | version = "0.1.11" 4 | authors = ["Arlie Davis "] 5 | edition = "2018" 6 | description = "A Rust log provider which forwards events to Event Tracing for Windows (ETW)." 7 | license = "Apache-2.0 OR MIT" 8 | repository = "https://github.com/microsoft/rust_win_etw" 9 | readme = "../README.md" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | log = { version = "^0.4", features = ["std"] } 15 | win_etw_provider = { version = "0.1.14", path = "../win_etw_provider" } 16 | win_etw_macros = { version = "0.1.11", path = "../win_etw_macros" } 17 | win_etw_metadata = { version = "0.1.3", path = "../win_etw_metadata" } 18 | -------------------------------------------------------------------------------- /win_etw_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "win_etw_macros" 3 | version = "0.1.11" 4 | authors = ["Arlie Davis "] 5 | edition = "2018" 6 | description = "Enables apps to report events to Event Tracing for Windows (ETW)." 7 | license = "Apache-2.0 OR MIT" 8 | repository = "https://github.com/microsoft/rust_win_etw" 9 | readme = "../README.md" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | proc-macro2 = "^1.0" 18 | syn = { version = "^2.0", features = ["full", "extra-traits"] } 19 | quote = "^1.0" 20 | win_etw_metadata = { version = "0.1.3", path = "../win_etw_metadata" } 21 | uuid = { version = "^1.19", features = ["v5"]} 22 | sha1_smol = "1.0.0" 23 | -------------------------------------------------------------------------------- /.github/workflows/ci-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | RUSTFLAGS: -Dwarnings 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | rust: [1.77.0, stable] 17 | os: [windows-latest, ubuntu-latest] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: ${{ matrix.rust }} 24 | profile: minimal 25 | override: true 26 | components: rustfmt 27 | 28 | - name: Build 29 | run: cargo build --verbose 30 | - name: Run tests 31 | run: cargo test --verbose 32 | - name: fmt 33 | if: matrix.rust == 'stable' 34 | run: cargo fmt --all -- --check 35 | 36 | -------------------------------------------------------------------------------- /win_etw_tracing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "win_etw_tracing" 3 | version = "0.1.7" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | description = "Provides a backend for the `tracing` crate that logs events to ETW (Event Tracing for Windows)." 7 | repository = "https://github.com/microsoft/rust_win_etw" 8 | readme = "../README.md" 9 | 10 | [features] 11 | default = ["tracing-log"] 12 | 13 | [dependencies] 14 | bytes = "1" 15 | tracing = "0.1" 16 | tracing-log = { version = "0.2", optional = true, default-features = false, features = ["log-tracer", "std"] } 17 | tracing-subscriber = { version = "0.3.22", default-features = false, features = ["smallvec", "fmt", "std"] } 18 | uuid = "1.0" 19 | win_etw_metadata = { path = "../win_etw_metadata", version = "0.1.3" } 20 | win_etw_provider = { path = "../win_etw_provider", version = "0.1.14", features = ["uuid"] } 21 | 22 | [dev-dependencies] 23 | anyhow = "1" 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /win_etw_provider/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "win_etw_provider" 3 | version = "0.1.14" 4 | authors = ["Arlie Davis "] 5 | edition = "2018" 6 | description = "Enables apps to report events to Event Tracing for Windows (ETW)." 7 | license = "Apache-2.0 OR MIT" 8 | repository = "https://github.com/microsoft/rust_win_etw" 9 | readme = "../README.md" 10 | rust-version = "1.77" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | widestring = {version = "^1.2", default-features = false, features = ["alloc"]} 16 | zerocopy = { version = "0.8.21", features = ["derive"] } 17 | win_etw_metadata = { version = "0.1.3", path = "../win_etw_metadata" } 18 | uuid = {version = "1", optional = true} 19 | 20 | [target.'cfg(windows)'.dependencies] 21 | windows-sys = { version = "0.61.2", features = ["Win32_System_Diagnostics_Etw", "Win32_Foundation"] } 22 | 23 | [target.'cfg(windows)'.dev-dependencies] 24 | windows-sys = { version = "0.61.2", features = ["Win32_System_Diagnostics_Etw", "Win32_Foundation", "Win32_Networking_WinSock"] } 25 | 26 | [dev-dependencies] 27 | uuid = "1" 28 | 29 | [features] 30 | std = [] 31 | default = ["no_std"] 32 | no_std = [] 33 | uuid = ["dep:uuid"] 34 | # dev is used only for development 35 | dev = ["std"] 36 | -------------------------------------------------------------------------------- /win_etw_logger/examples/hello.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, error, info, log_enabled, trace, warn, Level}; 2 | use std::time::Duration; 3 | use win_etw_logger::TraceLogger; 4 | 5 | fn main() { 6 | let logger = TraceLogger::new().unwrap(); 7 | 8 | log::set_boxed_logger(Box::new(logger)).unwrap(); 9 | log::set_max_level(log::LevelFilter::Debug); 10 | 11 | info!("Rust logging through ETW! n = {}", 42); 12 | warn!("This is too much fun"); 13 | debug!("maybe we can make this code work"); 14 | 15 | let args = std::env::args().collect::>(); 16 | if args.len() >= 2 && args[1] == "loop" { 17 | eprintln!("looping"); 18 | loop { 19 | std::thread::sleep(Duration::from_millis(3000)); 20 | error!("error: something pretty bad happened!"); 21 | warn!("warn: something warning-worthy happened"); 22 | info!("info: something normal happened"); 23 | debug!("debug: noisy debug noisy debug"); 24 | trace!("trace: noisy tracing noisy tracing"); 25 | 26 | let error_is_enabled = log_enabled!(Level::Error); 27 | let warn_is_enabled = log_enabled!(Level::Warn); 28 | let info_is_enabled = log_enabled!(Level::Info); 29 | let debug_is_enabled = log_enabled!(Level::Debug); 30 | let trace_is_enabled = log_enabled!(Level::Trace); 31 | eprintln!( 32 | "is_enabled? error: {:5?}, warn: {:5?}, info: {:5?}, debug: {:5?}, trace: {:5?}", 33 | error_is_enabled, 34 | warn_is_enabled, 35 | info_is_enabled, 36 | debug_is_enabled, 37 | trace_is_enabled, 38 | ); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /win_etw_provider/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Enables Rust apps to report events using Event Tracing for Windows. 2 | //! 3 | //! See [About Event Tracing](https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing). 4 | 5 | #![deny(missing_docs)] 6 | #![cfg_attr(all(not(test), not(feature = "std")), no_std)] 7 | #![cfg_attr(not(windows), allow(unused))] 8 | 9 | extern crate alloc; 10 | 11 | mod guid; 12 | mod provider; 13 | 14 | pub mod types; 15 | 16 | #[doc(inline)] 17 | pub use guid::GUID; 18 | 19 | #[doc(inline)] 20 | pub use provider::*; 21 | 22 | #[doc(hidden)] 23 | pub use types::*; 24 | 25 | #[doc(inline)] 26 | pub use types::{SocketAddrV4, SocketAddrV6, FILETIME}; 27 | 28 | #[doc(hidden)] 29 | pub use win_etw_metadata as metadata; 30 | 31 | mod data_descriptor; 32 | 33 | #[doc(inline)] 34 | pub use data_descriptor::EventDataDescriptor; 35 | 36 | /// Errors returned by `win_etw_provider` functions. 37 | /// 38 | /// When compiling for non-Windows platforms, this Error type becomes an uninhabited type. 39 | #[derive(Clone, PartialEq, Eq, Debug)] 40 | #[non_exhaustive] 41 | pub enum Error { 42 | /// A Windows (Win32) error code. 43 | #[cfg(target_os = "windows")] 44 | WindowsError(u32), 45 | 46 | /// The operation is not supported on this platform. 47 | /// 48 | /// Most operations defined in this crate do nothing on non-Windows platforms. Those operations 49 | /// that return information, such as the `new_activity_id()` function, use this error value. 50 | NotSupported, 51 | } 52 | 53 | /// Allows an application to override the parameters for an event. The first parameter of each 54 | /// generated event method is `options: Option<&EventOptions>`. 55 | #[derive(Default)] 56 | pub struct EventOptions { 57 | /// Overrides the level of the event, if present. Each event method has a default, which can be 58 | /// specified using (for example) `#[event(level = "warn")]`. If the event declaration does not 59 | /// specify a level, then the level will be `Level::VERBOSE`. 60 | pub level: Option, 61 | 62 | /// Specifies the activity ID of this event. 63 | pub activity_id: Option, 64 | 65 | /// Specifies a related activity ID for this event. This enables an application to indicate 66 | /// that two sets of events are related, by associating the activity IDs of the two sets. 67 | /// This is sometimes known as _event correlation_. 68 | pub related_activity_id: Option, 69 | } 70 | 71 | pub use win_etw_metadata::Level; 72 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /win_etw_logger/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides a `log::Log` implementation that sends events to Event Tracing for Windows (ETW). 2 | 3 | #![no_std] 4 | #![deny(missing_docs)] 5 | #![forbid(unsafe_code)] 6 | #![allow(clippy::unreadable_literal)] 7 | #![allow(clippy::useless_let_if_seq)] 8 | 9 | extern crate alloc; 10 | use alloc::string::ToString; 11 | use core::sync::atomic::{AtomicBool, Ordering}; 12 | use win_etw_macros::trace_logging_provider; 13 | 14 | #[trace_logging_provider(guid = "7f006a22-73fb-4c17-b1eb-0a3070f9f187")] 15 | trait RustLogProvider { 16 | fn log(module_path: &str, file: &str, line: u32, message: &str); 17 | } 18 | 19 | /// Provides a `log::Log` implementation that sends events to Event Tracing for Windows (ETW). 20 | pub struct TraceLogger { 21 | provider: RustLogProvider, 22 | log_module_path: AtomicBool, 23 | log_file_path: AtomicBool, 24 | } 25 | 26 | impl TraceLogger { 27 | /// Registers the `TraceLogger` with ETW. 28 | pub fn new() -> Result { 29 | let provider = RustLogProvider::new(); 30 | Ok(TraceLogger { 31 | provider, 32 | log_module_path: AtomicBool::new(true), 33 | log_file_path: AtomicBool::new(true), 34 | }) 35 | } 36 | 37 | /// Controls whether Rust module paths are included in event records. 38 | /// The default is `true` (module paths are included). 39 | /// This is provided to give control over privacy and to control the size of event records. 40 | pub fn set_log_module_path(&self, value: bool) { 41 | self.log_module_path.store(value, Ordering::Release); 42 | } 43 | 44 | /// Controls whether source file names and line numbers are included in event 45 | /// records. The default is `true` (source file names and line numbers are included). 46 | /// This is provided to give control over privacy and to control the size of event records. 47 | pub fn set_log_file_path(&self, value: bool) { 48 | self.log_file_path.store(value, Ordering::Release); 49 | } 50 | 51 | /// Returns `true` if this logger will include Rust module paths in event records. 52 | pub fn log_module_path(&self) -> bool { 53 | self.log_module_path.load(Ordering::Acquire) 54 | } 55 | 56 | /// Returns `true` if this logger will include source file names and line numbers in event 57 | /// records. 58 | pub fn log_file_path(&self) -> bool { 59 | self.log_file_path.load(Ordering::Acquire) 60 | } 61 | } 62 | 63 | fn level_to_etw(level: log::Level) -> win_etw_provider::Level { 64 | match level { 65 | log::Level::Error => win_etw_provider::Level::ERROR, 66 | log::Level::Warn => win_etw_provider::Level::WARN, 67 | log::Level::Info => win_etw_provider::Level::INFO, 68 | log::Level::Debug => win_etw_provider::Level::VERBOSE, 69 | log::Level::Trace => win_etw_provider::Level(6), 70 | } 71 | } 72 | 73 | impl log::Log for TraceLogger { 74 | fn enabled(&self, metadata: &log::Metadata) -> bool { 75 | self.provider 76 | .log_is_enabled(Some(level_to_etw(metadata.level()))) 77 | } 78 | 79 | fn log(&self, record: &log::Record) { 80 | let module_path = if self.log_module_path() { 81 | record.module_path().unwrap_or("") 82 | } else { 83 | "" 84 | }; 85 | 86 | let file_path; 87 | let file_line; 88 | if self.log_file_path() { 89 | file_path = record.file().unwrap_or(""); 90 | file_line = record.line().unwrap_or(0); 91 | } else { 92 | file_path = ""; 93 | file_line = 0; 94 | } 95 | 96 | let message = record.args().to_string(); 97 | let metadata = record.metadata(); 98 | 99 | let options = win_etw_provider::EventOptions { 100 | level: Some(level_to_etw(metadata.level())), 101 | ..Default::default() 102 | }; 103 | self.provider 104 | .log(Some(&options), module_path, file_path, file_line, &message); 105 | } 106 | 107 | fn flush(&self) {} 108 | } 109 | -------------------------------------------------------------------------------- /win_etw_provider/src/types.rs: -------------------------------------------------------------------------------- 1 | //! Contains items that are part of the implementation of `win_etw`, but not intended to be used 2 | //! directly by application code. Only code generated by the `trace_logging_provider` macro 3 | //! should use these types. 4 | #![doc(hidden)] 5 | 6 | pub use widestring::{U16CStr, U16CString}; 7 | 8 | use crate::EventDataDescriptor; 9 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 10 | 11 | /// The value used in `SocketAddrV4::family` to identify IPv4 addresses. 12 | pub const AF_INET: u16 = 2; 13 | 14 | /// The value used in `SocketAddrV6::family` to identify IPv6 addresses. 15 | pub const AF_INET6: u16 = 23; 16 | 17 | /// This has the same in-memory representation as the Win32 `[SOCKADDR_IN]` structure. 18 | /// 19 | /// [SOCKADDR_IN]: https://docs.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-sockaddr_in 20 | #[repr(C)] 21 | #[derive(IntoBytes, Immutable, Clone)] 22 | pub struct SocketAddrV4 { 23 | /// Address family identifier. 24 | pub family: u16, 25 | /// Port identifier, stored in big-endian form. 26 | pub port: [u8; 2], 27 | /// IPv4 address, stored in big-endian form. 28 | pub address: [u8; 4], 29 | /// Zero padding. 30 | pub zero: [u8; 8], 31 | } 32 | 33 | impl From<&core::net::SocketAddrV4> for SocketAddrV4 { 34 | fn from(value: &core::net::SocketAddrV4) -> Self { 35 | let port = value.port(); 36 | Self { 37 | family: AF_INET, 38 | address: value.ip().octets(), 39 | port: port.to_be_bytes(), 40 | zero: [0; 8], 41 | } 42 | } 43 | } 44 | 45 | impl<'a> From<&'a crate::types::SocketAddrV4> for EventDataDescriptor<'a> { 46 | fn from(value: &'a crate::types::SocketAddrV4) -> EventDataDescriptor<'a> { 47 | Self::from(value.as_bytes()) 48 | } 49 | } 50 | 51 | /// See `[SOCKADDR_IN6_LH](https://docs.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-sockaddr_in6_lh)`. 52 | #[repr(C)] 53 | #[derive(Clone, IntoBytes, FromBytes, Immutable)] 54 | pub struct SocketAddrV6 { 55 | /// Address family identifier. 56 | pub family: u16, 57 | /// Port identifier, stored in big-endian form. 58 | pub port: [u8; 2], 59 | /// IPv6 flow info. 60 | pub flow_info: [u8; 4], 61 | /// IPv6 address. 62 | pub address: [u8; 16], 63 | /// IPv6 scope. 64 | pub scope_id: [u8; 4], 65 | } 66 | 67 | impl From<&core::net::SocketAddrV6> for SocketAddrV6 { 68 | fn from(value: &core::net::SocketAddrV6) -> Self { 69 | Self { 70 | family: AF_INET6, 71 | port: value.port().to_be_bytes(), 72 | flow_info: value.flowinfo().to_be_bytes(), 73 | address: value.ip().octets(), 74 | scope_id: value.scope_id().to_be_bytes(), 75 | } 76 | } 77 | } 78 | 79 | impl<'a> From<&'a crate::types::SocketAddrV6> for EventDataDescriptor<'a> { 80 | fn from(value: &'a crate::types::SocketAddrV6) -> EventDataDescriptor<'a> { 81 | Self::from(value.as_bytes()) 82 | } 83 | } 84 | 85 | /// See `[FILETIME](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime)`. 86 | #[repr(transparent)] 87 | #[derive(Copy, Clone, Eq, PartialEq, Hash)] 88 | pub struct FILETIME(pub u64); 89 | 90 | #[cfg(feature = "std")] 91 | mod std_support { 92 | use super::*; 93 | 94 | use core::convert::TryFrom; 95 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 96 | 97 | /// Time elapsed between the Windows epoch and the UNIX epoch. 98 | const WINDOWS_EPOCH_TO_UNIX_EPOCH: Duration = Duration::from_secs(11_644_473_600); 99 | 100 | pub struct OutOfRangeError; 101 | 102 | impl TryFrom for FILETIME { 103 | type Error = OutOfRangeError; 104 | fn try_from(t: SystemTime) -> Result { 105 | match t.duration_since(UNIX_EPOCH) { 106 | Ok(unix_elapsed) => { 107 | let windows_elapsed: Duration = unix_elapsed + WINDOWS_EPOCH_TO_UNIX_EPOCH; 108 | Ok(FILETIME((windows_elapsed.as_nanos() / 100) as u64)) 109 | } 110 | Err(_) => Err(OutOfRangeError), 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /win_etw_provider/src/guid.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | use zerocopy::{FromBytes, IntoBytes}; 3 | 4 | /// Initializes a `GUID` from literal values. 5 | #[macro_export] 6 | macro_rules! guid { 7 | ( 8 | $a:expr, 9 | $b:expr, 10 | $c:expr, 11 | $d:expr 12 | ) => { 13 | $crate::GUID { 14 | data1: $a, 15 | data2: $b, 16 | data3: $c, 17 | data4: $d, 18 | } 19 | }; 20 | 21 | ( 22 | $a:expr, 23 | $b:expr, 24 | $c:expr, 25 | $d0:expr, 26 | $d1:expr, 27 | $d2:expr, 28 | $d3:expr, 29 | $d4:expr, 30 | $d5:expr, 31 | $d6:expr, 32 | $d7:expr 33 | ) => { 34 | $crate::GUID { 35 | data1: $a, 36 | data2: $b, 37 | data3: $c, 38 | data4: [$d0, $d1, $d2, $d3, $d4, $d5, $d6, $d7], 39 | } 40 | }; 41 | } 42 | 43 | /// The Windows [`GUID`](https://docs.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid) 44 | /// type. 45 | /// 46 | /// `win_etw_provider` defines this type, rather than directly referencing (or re-exporting) 47 | /// an equivalent type from other crates in order to minimize its dependencies. `GUID` has a well- 48 | /// defined byte representation, so converting between different implementations of `GUID` is 49 | /// not a problem. 50 | #[repr(C)] 51 | #[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, IntoBytes, FromBytes)] 52 | pub struct GUID { 53 | /// Contains bytes 0-3 (inclusive) of the GUID. 54 | pub data1: u32, 55 | /// Contains bytes 4-5 (inclusive) of the GUID. 56 | pub data2: u16, 57 | /// Contains bytes 6-7 (inclusive) of the GUID. 58 | pub data3: u16, 59 | /// Contains bytes 8-15 (inclusive) of the GUID. 60 | pub data4: [u8; 8], 61 | } 62 | 63 | impl core::fmt::Display for GUID { 64 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 65 | write!( 66 | fmt, 67 | "{:08x}-{:04x}-{:04x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", 68 | self.data1, 69 | self.data2, 70 | self.data3, 71 | self.data4[0], 72 | self.data4[1], 73 | self.data4[2], 74 | self.data4[3], 75 | self.data4[4], 76 | self.data4[5], 77 | self.data4[6], 78 | self.data4[7] 79 | ) 80 | } 81 | } 82 | 83 | impl core::fmt::Debug for GUID { 84 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 85 | core::fmt::Display::fmt(self, fmt) 86 | } 87 | } 88 | 89 | #[cfg(target_os = "windows")] 90 | impl From for GUID { 91 | fn from(value: windows_sys::core::GUID) -> Self { 92 | Self { 93 | data1: value.data1, 94 | data2: value.data2, 95 | data3: value.data3, 96 | data4: value.data4, 97 | } 98 | } 99 | } 100 | 101 | #[cfg(target_os = "windows")] 102 | impl From for windows_sys::core::GUID { 103 | fn from(value: GUID) -> Self { 104 | Self { 105 | data1: value.data1, 106 | data2: value.data2, 107 | data3: value.data3, 108 | data4: value.data4, 109 | } 110 | } 111 | } 112 | 113 | #[cfg(feature = "uuid")] 114 | impl From for GUID { 115 | fn from(value: uuid::Uuid) -> Self { 116 | let fields = value.as_fields(); 117 | Self { 118 | data1: fields.0, 119 | data2: fields.1, 120 | data3: fields.2, 121 | data4: *fields.3, 122 | } 123 | } 124 | } 125 | 126 | #[cfg(feature = "uuid")] 127 | impl TryFrom<&str> for GUID { 128 | type Error = uuid::Error; 129 | 130 | fn try_from(value: &str) -> Result { 131 | use uuid::Uuid; 132 | let uuid = Uuid::parse_str(value)?; 133 | Ok(uuid.into()) 134 | } 135 | } 136 | 137 | #[cfg(feature = "uuid")] 138 | #[cfg(test)] 139 | mod test { 140 | use crate::guid::GUID; 141 | use core::convert::TryFrom; 142 | use uuid::Uuid; 143 | #[test] 144 | fn test_uuid() { 145 | let uuid = Uuid::parse_str("1a1a1a1a-2b2b-3c3c-4142-434546474849").unwrap(); 146 | let guid: GUID = uuid.into(); 147 | assert_eq!(guid.data1, 0x1a1a_1a1a); 148 | assert_eq!(guid.data2, 0x2b2b); 149 | assert_eq!(guid.data3, 0x3c3c); 150 | assert_eq!(guid.data4, [0x41, 0x42, 0x43, 0x45, 0x46, 0x47, 0x48, 0x49]); 151 | } 152 | 153 | #[test] 154 | fn test_try_from_str() { 155 | let guid_str = "1a1a1a1a-2b2b-3c3c-4142-434546474849"; 156 | let guid: GUID = GUID::try_from(guid_str).unwrap(); 157 | assert_eq!(guid.data1, 0x1a1a_1a1a); 158 | assert_eq!(guid.data2, 0x2b2b); 159 | assert_eq!(guid.data3, 0x3c3c); 160 | assert_eq!(guid.data4, [0x41, 0x42, 0x43, 0x45, 0x46, 0x47, 0x48, 0x49]); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /win_etw_provider/src/data_descriptor.rs: -------------------------------------------------------------------------------- 1 | use crate::guid::GUID; 2 | use core::marker::PhantomData; 3 | use core::mem::size_of; 4 | use widestring::{U16CStr, U16Str}; 5 | use zerocopy::IntoBytes; 6 | 7 | /// Contains a reference to the data for an event field. The type of the data is not specified in 8 | /// this structure; instead, the type of the data is stored in the event's metadata. 9 | /// (See `win_etw_metadata::InFlag`.) 10 | /// 11 | /// The data that this type points to must have a well-defined (stable) byte representation. For 12 | /// example, `u32` has a well-defined byte representation, as long as there is agreement about 13 | /// whether the value is stored in big-endian or little-endian order. Similarly, `[u8]` and 14 | /// `[u32]` have well-defined byte representations. However, types such as `[bool]` do not have a 15 | /// stable byte representation, and so `EventDataDescriptor` cannot point to `&[bool]`. 16 | /// 17 | /// This type provides implementations of `From` that can be used to point to event data. 18 | /// All of the `EventDataDescriptor::From` implementations for types require that the types have a 19 | /// stable, guaranteed byte representation, and that is legal (meaningful) to read that byte 20 | /// representation. 21 | /// 22 | /// This type is equivalent to the Win32 structure `EVENT_DATA_DESCRIPTOR`, and its representation 23 | /// is guaranteed to be equivalent. 24 | /// 25 | /// # Implementation warning! 26 | /// 27 | /// This code is responsible for ensuring memory safety. Even though it contains only simple 28 | /// primitives, these primitives are actually native pointers and pointer bounds. This data 29 | /// structure is passed to the ETW implementation, which dereferences those pointers. The Rust 30 | /// type checker cannot "see" these dereferences, since they occur in non-Rust code, so the borrow 31 | /// checker does not know that `EventDataDescriptor` deals with memory safety. This is why the 32 | /// `phantom_ref` field exists, and it is _crucial_ that this code be used and encapsulated 33 | /// correctly. 34 | /// 35 | /// For type safety to be conserved, the following invariants *must* be maintained: 36 | /// 37 | /// * The `'a` lifetime parameter of `EventDataDescriptor<'a>` must be correctly associated with 38 | /// the lifetime of any reference that is used to construct an instance of `EventDataDescriptor`. 39 | /// 40 | /// * The fields of `EventDataDescriptor` must remain private. Arbitrary user code cannot be 41 | /// permitted to construct instances of `EventDataDescriptor` with arbitrary values for these 42 | /// fields. 43 | /// 44 | /// * `EventDataDescriptor` should only be used to pass to ETW functions. 45 | #[repr(C)] 46 | #[derive(Clone)] 47 | pub struct EventDataDescriptor<'a> { 48 | // descriptor: evntprov::EVENT_DATA_DESCRIPTOR, 49 | ptr: u64, 50 | size: u32, 51 | 52 | /// In the Windows SDK, this field is marked "reserved" and is a union. However, it is clear 53 | /// from usage within `traceloggingprovider.h` that this field is used to identify event data, 54 | /// provider metadata, and event metadata. 55 | kind: u32, 56 | 57 | /// Represents the lifetime of the pointed-to data. 58 | phantom_ref: PhantomData<&'a ()>, 59 | } 60 | 61 | impl EventDataDescriptor<'static> { 62 | /// Returns an empty data descriptor. 63 | pub fn empty() -> Self { 64 | Self { 65 | ptr: 0, 66 | size: 0, 67 | kind: 0, 68 | phantom_ref: PhantomData, 69 | } 70 | } 71 | } 72 | 73 | const EVENT_DATA_DESCRIPTOR_TYPE_PROVIDER_METADATA: u32 = 2; 74 | const EVENT_DATA_DESCRIPTOR_TYPE_EVENT_METADATA: u32 = 1; 75 | 76 | impl<'a> EventDataDescriptor<'a> { 77 | /// Creates an `EventDataDescriptor` for provider metadata. 78 | pub fn for_provider_metadata(s: &'a [u8]) -> Self { 79 | Self { 80 | ptr: s.as_ptr() as usize as u64, 81 | size: s.len() as u32, 82 | kind: EVENT_DATA_DESCRIPTOR_TYPE_PROVIDER_METADATA, 83 | phantom_ref: PhantomData, 84 | } 85 | } 86 | 87 | /// Creates an `EventDataDescriptor` for the metadata that describes a single event. 88 | pub fn for_event_metadata(s: &'a [u8]) -> Self { 89 | Self { 90 | ptr: s.as_ptr() as usize as u64, 91 | size: s.len() as u32, 92 | kind: EVENT_DATA_DESCRIPTOR_TYPE_EVENT_METADATA, 93 | phantom_ref: PhantomData, 94 | } 95 | } 96 | 97 | /// Creates a `EventDataDescriptor for a slice of bytes. 98 | pub fn for_bytes(s: &'a [u8]) -> Self { 99 | Self { 100 | ptr: s.as_ptr() as usize as u64, 101 | size: s.len() as u32, 102 | kind: 0, 103 | phantom_ref: PhantomData, 104 | } 105 | } 106 | } 107 | 108 | macro_rules! well_known_types { 109 | ( 110 | $( 111 | $t:ident ; 112 | )* 113 | ) => { 114 | $( 115 | impl<'a> From<&'a $t> for EventDataDescriptor<'a> { 116 | fn from(value: &'a $t) -> EventDataDescriptor<'a> { 117 | EventDataDescriptor::for_bytes(value.as_bytes()) 118 | } 119 | } 120 | 121 | impl<'a> From<&'a [$t]> for EventDataDescriptor<'a> { 122 | fn from(value: &'a [$t]) -> EventDataDescriptor<'a> { 123 | EventDataDescriptor::for_bytes(value.as_bytes()) 124 | } 125 | } 126 | )* 127 | } 128 | } 129 | 130 | well_known_types! { 131 | bool; 132 | u8; u16; u32; u64; 133 | i8; i16; i32; i64; 134 | f32; f64; 135 | usize; isize; 136 | } 137 | 138 | impl<'a> From<&'a str> for EventDataDescriptor<'a> { 139 | fn from(value: &'a str) -> EventDataDescriptor<'a> { 140 | let bytes: &'a [u8] = value.as_bytes(); 141 | EventDataDescriptor::for_bytes(bytes) 142 | } 143 | } 144 | 145 | impl<'a> From<&'a U16Str> for EventDataDescriptor<'a> { 146 | fn from(value: &'a U16Str) -> EventDataDescriptor<'a> { 147 | Self { 148 | ptr: value.as_ptr() as usize as u64, 149 | size: (value.len() * 2) as u32, 150 | kind: 0, 151 | phantom_ref: PhantomData, 152 | } 153 | } 154 | } 155 | 156 | impl<'a> From<&'a U16CStr> for EventDataDescriptor<'a> { 157 | fn from(value: &'a U16CStr) -> EventDataDescriptor<'a> { 158 | Self { 159 | ptr: value.as_ptr() as usize as u64, 160 | size: (value.len() * 2) as u32, 161 | kind: 0, 162 | phantom_ref: PhantomData, 163 | } 164 | } 165 | } 166 | 167 | impl<'a> From<&'a GUID> for EventDataDescriptor<'a> { 168 | fn from(value: &'a GUID) -> EventDataDescriptor<'a> { 169 | Self { 170 | ptr: value as *const GUID as usize as u64, 171 | size: size_of::() as u32, 172 | kind: 0, 173 | phantom_ref: PhantomData, 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /win_etw_macros/src/well_known_types.rs: -------------------------------------------------------------------------------- 1 | use syn::parse_quote; 2 | use win_etw_metadata::{InFlag, OutFlag}; 3 | 4 | pub struct WellKnownTypeInfo { 5 | pub ty: syn::Type, 6 | pub code: WellKnownType, 7 | pub in_type: InFlag, 8 | pub is_ref: bool, 9 | /// Indicates whether this type can be used in a slice, e.g. &[T]. 10 | /// Should probably rename to `can_slice`. 11 | pub primitive: bool, 12 | pub opts: WellKnownTypeOptions, 13 | } 14 | 15 | #[derive(Default)] 16 | pub struct WellKnownTypeOptions { 17 | pub out_type: Option, 18 | pub in_type_expr: Option, 19 | pub replacement_type: Option, 20 | #[allow(unused)] 21 | pub can_output_hex: bool, 22 | } 23 | 24 | macro_rules! well_known_types{ 25 | ( 26 | $( 27 | $t:ident: $tt:ty => { 28 | is_ref: $is_ref:expr, 29 | primitive: $primitive:expr, 30 | in_type: $in_type:expr, 31 | $( $opt_name:ident: $opt_value:expr, )* 32 | } 33 | )* 34 | ) => { 35 | #[allow(non_camel_case_types)] 36 | #[derive(Copy, Clone, Eq, PartialEq)] 37 | pub enum WellKnownType { 38 | $($t,)* 39 | } 40 | 41 | #[allow(non_snake_case)] 42 | pub struct WellKnownTypes { 43 | $( 44 | $t: WellKnownTypeInfo, 45 | )* 46 | } 47 | 48 | impl WellKnownTypes { 49 | pub fn new() -> Self { 50 | Self { 51 | $( 52 | $t: WellKnownTypeInfo { 53 | ty: parse_quote!( $tt ), 54 | code: WellKnownType::$t, 55 | is_ref: $is_ref, 56 | primitive: $primitive, 57 | in_type: $in_type, 58 | opts: WellKnownTypeOptions { 59 | $($opt_name: $opt_value,)* 60 | .. 61 | WellKnownTypeOptions::default() 62 | }, 63 | }, 64 | )* 65 | } 66 | } 67 | 68 | pub fn find(&self, ty: &syn::Type) -> Option<&WellKnownTypeInfo> { 69 | $( 70 | if *ty == self.$t.ty { 71 | return Some(&self.$t); 72 | } 73 | )* 74 | None 75 | } 76 | } 77 | } 78 | } 79 | 80 | well_known_types! { 81 | bool: bool => { 82 | is_ref: false, 83 | primitive: true, 84 | in_type: InFlag::UINT8, 85 | out_type: Some(OutFlag::BOOLEAN), 86 | } 87 | u8: u8 => { is_ref: false, primitive: true, in_type: InFlag::UINT8, can_output_hex: true, } 88 | u16: u16 => { is_ref: false, primitive: true, in_type: InFlag::UINT16, can_output_hex: true, } 89 | u32: u32 => { is_ref: false, primitive: true, in_type: InFlag::UINT32, can_output_hex: true, } 90 | u64: u64 => { is_ref: false, primitive: true, in_type: InFlag::UINT64, can_output_hex: true, } 91 | i8: i8 => { is_ref: false, primitive: true, in_type: InFlag::INT8, can_output_hex: true, } 92 | i16: i16 => { is_ref: false, primitive: true, in_type: InFlag::INT16, can_output_hex: true, } 93 | i32: i32 => { is_ref: false, primitive: true, in_type: InFlag::INT32, can_output_hex: true, } 94 | i64: i64 => { is_ref: false, primitive: true, in_type: InFlag::INT64, can_output_hex: true, } 95 | f32: f32 => { is_ref: false, primitive: true, in_type: InFlag::FLOAT, } 96 | f64: f64 => { is_ref: false, primitive: true, in_type: InFlag::DOUBLE, } 97 | usize: usize => { is_ref: false, primitive: true, in_type: InFlag::NULL, 98 | in_type_expr: Some(parse_quote!{ 99 | ::win_etw_provider::metadata::InFlag::USIZE.bits() 100 | }), 101 | can_output_hex: true, 102 | } 103 | isize: isize => { is_ref: false, primitive: true, in_type: InFlag::NULL, 104 | in_type_expr: Some(parse_quote!{ 105 | ::win_etw_provider::metadata::InFlag::ISIZE.bits() 106 | }), 107 | can_output_hex: true, 108 | } 109 | ref_str: &str => { 110 | is_ref: true, 111 | primitive: false, 112 | in_type: InFlag::COUNTED_ANSI_STRING, 113 | out_type: Some(OutFlag::UTF8), 114 | } 115 | u16str: &U16Str => { 116 | is_ref: true, 117 | primitive: false, 118 | in_type: InFlag::COUNTED_UNICODE_STRING, 119 | replacement_type: Some(parse_quote!(&::widestring::U16Str)), 120 | } 121 | u16cstr: &U16CStr => { 122 | is_ref: true, 123 | primitive: false, 124 | in_type: InFlag::COUNTED_UNICODE_STRING, 125 | replacement_type: Some(parse_quote!(&::widestring::U16CStr)), 126 | } 127 | osstr: &OsStr => { 128 | is_ref: true, 129 | primitive: false, 130 | in_type: InFlag::COUNTED_UNICODE_STRING, 131 | replacement_type: Some(parse_quote!(&::std::ffi::OsStr)), 132 | } 133 | guid: &GUID => { 134 | is_ref: true, primitive: false, 135 | in_type: InFlag::GUID, 136 | replacement_type: Some(parse_quote!(&::win_etw_provider::GUID)), 137 | } 138 | SocketAddrV4: &SocketAddrV4 => { 139 | is_ref: false, 140 | primitive: false, 141 | in_type: InFlag::BINARY, 142 | out_type: Some(OutFlag::SOCKETADDRESS), 143 | replacement_type: Some(parse_quote!(&::std::net::SocketAddrV4)), 144 | } 145 | SocketAddrV6: &SocketAddrV6 => { 146 | is_ref: false, 147 | primitive: false, 148 | in_type: InFlag::BINARY, 149 | out_type: Some(OutFlag::SOCKETADDRESS), 150 | replacement_type: Some(parse_quote!(&::std::net::SocketAddrV6)), 151 | } 152 | SocketAddr: &SocketAddr => { 153 | is_ref: false, 154 | primitive: false, 155 | in_type: InFlag::BINARY, 156 | out_type: Some(OutFlag::SOCKETADDRESS), 157 | replacement_type: Some(parse_quote!(&::std::net::SocketAddr)), 158 | } 159 | SystemTime: SystemTime => { 160 | is_ref: false, 161 | primitive: false, 162 | in_type: InFlag::FILETIME, 163 | replacement_type: Some(parse_quote!(::std::time::SystemTime)), 164 | } 165 | FILETIME: FILETIME => { 166 | is_ref: true, 167 | primitive: false, 168 | in_type: InFlag::FILETIME, 169 | replacement_type: Some(parse_quote!(::win_etw_provider::FILETIME)), 170 | } 171 | HRESULT: HRESULT => { 172 | is_ref: false, 173 | primitive: false, 174 | in_type: InFlag::INT32, 175 | replacement_type: Some(parse_quote!(i32)), 176 | out_type: Some(OutFlag::HRESULT), 177 | } 178 | WIN32ERROR: WIN32ERROR => { 179 | is_ref: false, 180 | primitive: false, 181 | in_type: InFlag::UINT32, 182 | replacement_type: Some(parse_quote!(u32)), 183 | out_type: Some(OutFlag::WIN32ERROR), 184 | } 185 | NTSTATUS: NTSTATUS => { 186 | is_ref: false, 187 | primitive: false, 188 | in_type: InFlag::UINT32, 189 | replacement_type: Some(parse_quote!(u32)), 190 | out_type: Some(OutFlag::NTSTATUS), 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /examples/hello_world/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unreadable_literal)] 2 | #![forbid(unsafe_code)] 3 | use win_etw_macros::trace_logging_provider; 4 | 5 | use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; 6 | use std::time::{Duration, SystemTime}; 7 | use widestring::{U16CString, U16String}; 8 | use win_etw_provider::{guid, FILETIME, GUID}; 9 | 10 | // {861A3948-3B6B-4DDF-B862-B2CB361E238E} 11 | // DEFINE_GUID(my_provider_guid, 0x861a3948, 0x3b6b, 0x4ddf, 0xb8, 0x62, 0xb2, 0xcb, 0x36, 0x1e, 0x23, 0x8e); 12 | const EXAMPLE_GUID: GUID = 13 | guid!(0x861a3948, 0x3b6b, 0x4ddf, 0xb8, 0x62, 0xb2, 0xcb, 0x36, 0x1e, 0x23, 0x8e); 14 | 15 | fn main() { 16 | let hello_provider = HelloWorldProvider::new(); 17 | 18 | hello_provider.arg_str(None, "Hello, world!"); 19 | hello_provider.arg_slice_u8(None, &[44, 55, 66]); 20 | hello_provider.arg_slice_i32(None, &[10001, 20002, 30003]); 21 | hello_provider.arg_f32(None, core::f32::consts::PI); 22 | hello_provider.arg_guid(None, &EXAMPLE_GUID); 23 | 24 | let client_addr_v4: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::new(192, 168, 23, 42), 6667); 25 | hello_provider.client_connected_v4(None, &client_addr_v4); 26 | 27 | let client_addr_v6 = "[2001:db8::1]:8080".parse::().unwrap(); 28 | hello_provider.client_connected_v6(None, &client_addr_v6); 29 | 30 | hello_provider.client_connected(None, &SocketAddr::V4(client_addr_v4)); 31 | hello_provider.client_connected(None, &SocketAddr::V6(client_addr_v6)); 32 | 33 | hello_provider.something_bad_happened(None, "uh oh!"); 34 | 35 | hello_provider.file_created(None, SystemTime::now()); 36 | hello_provider.file_created_filetime( 37 | None, 38 | FILETIME((11644473600 + (3 * 365 + 31 + 28 + 31 + 30 + 31 + 15) * 86400) * 10_000_000), 39 | ); 40 | 41 | hello_provider.arg_u32_hex(None, 0xcafef00d); 42 | 43 | use std::ffi::OsString; 44 | 45 | hello_provider.arg_u16str(None, &U16String::from_str("this is a u16str")); 46 | hello_provider.arg_u16cstr(None, &U16CString::from_str("this is a u16cstr").unwrap()); 47 | hello_provider.arg_osstr(None, &OsString::from("hello!")); 48 | 49 | #[cfg(target_os = "windows")] 50 | { 51 | use windows_sys::Win32::Foundation::{ 52 | ERROR_OUT_OF_PAPER, ERROR_SXS_UNTRANSLATABLE_HRESULT, STATUS_ABIOS_INVALID_COMMAND, 53 | }; 54 | 55 | hello_provider.arg_hresult(None, ERROR_SXS_UNTRANSLATABLE_HRESULT as i32); 56 | hello_provider.arg_ntstatus(None, STATUS_ABIOS_INVALID_COMMAND as u32); 57 | hello_provider.arg_win32error(None, ERROR_OUT_OF_PAPER); 58 | } 59 | 60 | let args = std::env::args().collect::>(); 61 | if args.len() >= 2 && args[1] == "loop" { 62 | eprintln!("looping"); 63 | loop { 64 | std::thread::sleep(Duration::from_millis(3000)); 65 | hello_provider.hello(None, "Looping... (from Rust)"); 66 | hello_provider.message_at_critical(None, "something super exciting happened!"); 67 | hello_provider.message_at_error(None, "something pretty bad happened!"); 68 | hello_provider.message_at_warn(None, "something warning-worthy happened"); 69 | hello_provider.message_at_info(None, "something normal happened"); 70 | hello_provider.message_at_verbose(None, "noisy noisy noisy"); 71 | hello_provider.message_at_level_8(None, "incredibly detailed level 8 tracing"); 72 | } 73 | } 74 | } 75 | 76 | /// Hello, World, from ETW 77 | #[trace_logging_provider( 78 | guid = "861A3948-3B6B-4DDF-B862-B2CB361E238E", 79 | provider_group_guid = "6aeb6059-444a-4606-a3ea-06fd00fe3378" 80 | )] 81 | trait HelloWorldProvider { 82 | fn hello(a: &str); 83 | fn arg_i32(a: i32); 84 | fn arg_u8(a: u8); 85 | 86 | /// Log a floating point value. 87 | #[event(level = "info")] 88 | fn arg_f32(a: f32); 89 | 90 | fn arg_slice_u8(arg: &[u8]); 91 | fn arg_slice_i32(arg: &[i32]); 92 | fn arg_str(arg: &str); 93 | 94 | fn arg_guid(arg: &GUID); 95 | 96 | #[event(level = "error")] 97 | fn something_bad_happened(message: &str); 98 | 99 | #[event(task = 42, opcode = 99)] 100 | fn client_connected_v4(client_addr: &SocketAddrV4); 101 | 102 | #[event(task = 42, opcode = 99)] 103 | fn client_connected_v6(client_addr: &SocketAddrV6); 104 | 105 | #[event(task = 42, opcode = 99)] 106 | fn client_connected(client_addr: &SocketAddr); 107 | 108 | fn file_created(create_time: SystemTime); 109 | 110 | fn file_created_filetime(t: FILETIME); 111 | 112 | fn arg_bool(a: bool); 113 | 114 | fn arg_usize(a: usize); 115 | fn arg_isize(a: isize); 116 | 117 | fn arg_u32_hex(#[event(output = "hex")] a: u32); 118 | 119 | fn arg_hresult(a: HRESULT); 120 | fn arg_ntstatus(a: NTSTATUS); 121 | fn arg_win32error(a: WIN32ERROR); 122 | 123 | fn arg_u16str(a: &U16Str); 124 | fn arg_u16cstr(a: &U16CStr); 125 | fn arg_osstr(a: &OsStr); 126 | 127 | #[event(level = "critical")] 128 | fn message_at_critical(msg: &str); 129 | 130 | #[event(level = "info")] 131 | fn message_at_info(msg: &str); 132 | 133 | #[event(level = "warn")] 134 | fn message_at_warn(msg: &str); 135 | 136 | #[event(level = "error")] 137 | fn message_at_error(msg: &str); 138 | 139 | #[event(level = "verbose")] 140 | fn message_at_verbose(msg: &str); 141 | 142 | #[event(level = 8)] 143 | fn message_at_level_8(msg: &str); 144 | } 145 | 146 | #[trace_logging_provider(guid = "76d66486-d11a-47a8-af05-88942b6edb55")] 147 | trait AnotherFineProvider { 148 | fn arg_str(arg: &str); 149 | } 150 | 151 | #[trace_logging_provider(guid = "b9978f10-b3e0-4bbe-a4f2-160a2e7148d6")] 152 | trait TestManyEvents { 153 | fn arg_none(); 154 | fn arg_bool(a: bool); 155 | fn arg_u8(a: u8); 156 | fn arg_u16(a: u16); 157 | fn arg_u32(a: u32); 158 | fn arg_u64(a: u64); 159 | fn arg_i8(a: i8); 160 | fn arg_i16(a: i16); 161 | fn arg_i32(a: i32); 162 | fn arg_i64(a: i64); 163 | fn arg_f32(a: f32); 164 | fn arg_f64(a: f64); 165 | fn arg_usize(a: usize); 166 | fn arg_isize(a: isize); 167 | 168 | fn arg_slice_bool(a: &[bool]); 169 | fn arg_slice_u8(a: &[u8]); 170 | fn arg_slice_u16(a: &[u16]); 171 | fn arg_slice_u32(a: &[u32]); 172 | fn arg_slice_u64(a: &[u64]); 173 | fn arg_slice_i8(a: &[i8]); 174 | fn arg_slice_i16(a: &[i16]); 175 | fn arg_slice_i32(a: &[i32]); 176 | fn arg_slice_i64(a: &[i64]); 177 | fn arg_slice_f32(a: &[f32]); 178 | fn arg_slice_f64(a: &[f64]); 179 | fn arg_slice_usize(a: &[usize]); 180 | fn arg_slice_isize(a: &[isize]); 181 | 182 | fn arg_str(arg: &str); 183 | fn arg_guid(arg: &GUID); 184 | fn arg_system_time(a: SystemTime); 185 | fn arg_filetime(a: FILETIME); 186 | 187 | #[event(level = "critical")] 188 | fn arg_u8_at_critical(a: u8); 189 | 190 | #[event(level = "info")] 191 | fn arg_u8_at_info(a: u8); 192 | 193 | #[event(level = "warn")] 194 | fn arg_u8_at_warn(a: u8); 195 | 196 | #[event(level = "error")] 197 | fn arg_u8_at_error(a: u8); 198 | 199 | #[event(level = "verbose")] 200 | fn arg_u8_at_verbose(a: u8); 201 | 202 | #[event(level = 8)] 203 | fn arg_u8_at_level_8(a: u8); 204 | 205 | #[event(task = 100)] 206 | fn arg_with_task(a: u8); 207 | 208 | #[event(opcode = 10)] 209 | fn arg_with_opcode(a: u8); 210 | 211 | fn arg_u32_hex(#[event(output = "hex")] a: u32); 212 | 213 | fn arg_hresult(a: HRESULT); 214 | fn arg_ntstatus(a: NTSTATUS); 215 | fn arg_win32error(a: WIN32ERROR); 216 | } 217 | 218 | #[trace_logging_provider] 219 | trait ProviderWithAutogeneratedGuid { 220 | fn arg_str(arg: &str); 221 | } 222 | 223 | #[trace_logging_provider(name = "Your.Provider.Name")] 224 | trait ProviderWithAutogeneratedGuidAndCustomProviderName { 225 | fn arg_str(arg: &str); 226 | } 227 | 228 | #[trace_logging_provider( 229 | name = "Another.Provider.Name", 230 | guid = "86fe5bee-dec7-40f2-b39b-a46c20633042" 231 | )] 232 | trait ProviderWithCustomGuidAndCustomProviderName { 233 | fn arg_str(arg: &str); 234 | } 235 | 236 | #[cfg(test)] 237 | mod tests { 238 | use super::{ 239 | ProviderWithAutogeneratedGuid, ProviderWithAutogeneratedGuidAndCustomProviderName, 240 | ProviderWithCustomGuidAndCustomProviderName, 241 | }; 242 | use win_etw_provider::guid; 243 | 244 | #[test] 245 | fn provider_with_autogenerated_guid() { 246 | assert_eq!( 247 | "ProviderWithAutogeneratedGuid", 248 | ProviderWithAutogeneratedGuid::PROVIDER_NAME 249 | ); 250 | 251 | assert_eq!( 252 | guid!( 253 | 0x0d31f5cc, 254 | 0xfb84, 255 | 0x50db, 256 | [0xa6, 0x02, 0x8c, 0x7b, 0xed, 0x9c, 0x5b, 0x8b] 257 | ), 258 | ProviderWithAutogeneratedGuid::PROVIDER_GUID, 259 | ); 260 | } 261 | 262 | #[test] 263 | fn provider_with_autogenerated_guid_and_custom_provider_name() { 264 | assert_eq!( 265 | "Your.Provider.Name", 266 | ProviderWithAutogeneratedGuidAndCustomProviderName::PROVIDER_NAME 267 | ); 268 | 269 | assert_eq!( 270 | guid!( 271 | 0x0cec4c9d, 272 | 0xcaa7, 273 | 0x5d85, 274 | [0xb3, 0xbd, 0xc7, 0x35, 0x77, 0xf0, 0x3f, 0xd8] 275 | ), 276 | ProviderWithAutogeneratedGuidAndCustomProviderName::PROVIDER_GUID 277 | ); 278 | } 279 | 280 | #[test] 281 | fn provider_with_custom_guid_and_custom_provider_name() { 282 | assert_eq!( 283 | "Another.Provider.Name", 284 | ProviderWithCustomGuidAndCustomProviderName::PROVIDER_NAME 285 | ); 286 | 287 | assert_eq!( 288 | guid!( 289 | 0x86fe5bee, 290 | 0xdec7, 291 | 0x40f2, 292 | [0xb3, 0x9b, 0xa4, 0x6c, 0x20, 0x63, 0x30, 0x42] 293 | ), 294 | ProviderWithCustomGuidAndCustomProviderName::PROVIDER_GUID 295 | ); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust support for Event Tracing for Windows (ETW) 2 | 3 | Provides the `#[trace_logging_provider]` macro, which allows you to define a 4 | [Trace Logging Provider](https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing#providers) 5 | for use with the [Event Tracing for Windows (ETW)](https://docs.microsoft.com/en-us/windows/win32/etw/event-tracing-portal) 6 | framework. 7 | 8 | This macro is intended for use only when targeting Windows. When targeting other platforms, 9 | this macro will still work, but will generate code that does nothing. 10 | 11 | This framework allows applications to log schematized events, rather than textual strings. 12 | ETW analysis tools can reliably identify fields within your events, and treat them as 13 | strongly-typed data, rather than text strings. 14 | 15 | To use [tracing](https://tracing.rs) with ETW, see [tracing-etw](https://github.com/microsoft/tracing-etw). 16 | 17 | ## How to create and use an event provider 18 | 19 | In ETW, an _event provider_ is a software object that generates events. _Event controllers_ 20 | set up event logging sessions, and _event consumers_ read and interpret event data. This crate 21 | focuses on enabling applications to create _event providers_. 22 | 23 | ### Add crate dependencies 24 | 25 | Add these dependencies to your `Cargo.toml` file: 26 | 27 | ```text 28 | [dependencies] 29 | win_etw_macros = "0.1.*" 30 | win_etw_provider = "0.1.*" 31 | ``` 32 | 33 | `win_etw_macros` contains the procedural macro that generates eventing code. 34 | `win_etw_provider` contains library code that is called by the code that is generated by 35 | `win_etw_macros`. 36 | 37 | ### Define the event provider and its events 38 | 39 | Add a trait definition to your source code and annotate it with the `#[trace_logging_provider]` 40 | macro. The `#[trace_logging_provider]` macro consumes the trait definition and produces a `struct` 41 | definition with the same name and the same method signatures. (The trait is _not_ available for 42 | use as an ordinary trait.): 43 | 44 | ```rust 45 | #[trace_logging_provider] 46 | pub trait MyAppEvents {} 47 | ``` 48 | 49 | [Each event provider _must_ have a unique name _and_ GUID.](https://learn.microsoft.com/en-us/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-tracelogging_define_provider#provider-name-and-id) 50 | ETW uses this GUID to identify events that are generated by your provider. Windows contains many 51 | event providers, so it is important to be able to select only the events generated by your 52 | application. This GUID is also used internally by ETW to identify event metadata (field types), so 53 | it is important that your GUID be unique. Otherwise, events from conflicting sources that use the 54 | same GUID may be incorrectly interpreted. 55 | 56 | #### Specify your provider name 57 | 58 | Unless overridden, `#[trace_logging_provider]` uses the name of your trait definition as the name of ETW provider ("MyAppEvents" in the example aboved). 59 | To specify a different name, specify the name with `#[trace_logging_provider(name = "MyCompany.MyComponent")]`. 60 | 61 | #### Generate a GUID for your event provider 62 | 63 | The `#[trace_logging_provider]` macro will generate a .NET `EventSource`-compatible name-based GUID if you do not 64 | specify a `guid` parameter. The generated GUID is identical to the one generated by the following PowerShell code: 65 | `[System.Diagnostics.Tracing.EventSource]::new("MyCompany.MyComponent").Guid`. The GUID is accessible from your Rust code via the associated constant named `PROVIDER_GUID` (e.g., `MyAppEvents::PROVIDER_GUID`). 66 | 67 | If you are not interested in using a name-based GUID, you can generate a GUID using a tool like 68 | `uuidgen` (available from Visual Studio command line, or an Ubuntu shell) and specify it with 69 | `#[trace_logging_provider(guid = "... your guid here ...")]`. 70 | 71 | #### Add events to your provider 72 | 73 | In the trait definition, add method signatures. Each method signature defines an _event type_. 74 | The parameters of each method define the fields of the event type. Only a limited set of field 75 | types are supported (enumerated below). 76 | 77 | ```rust 78 | use win_etw_macros::trace_logging_provider; 79 | #[trace_logging_provider(name = "MyCompany.MyComponent")] 80 | pub trait MyAppEvents { 81 | fn http_request(client_address: &SockAddr, is_https: bool, status_code: u32, status: &str); 82 | fn database_connection_created(connection_id: u64, server: &str); 83 | fn database_connection_closed(connection_id: u64); 84 | // ... 85 | } 86 | ``` 87 | 88 | ### Create an instance of the event provider 89 | 90 | At initialization time (in your `fn main()`, etc.), create an instance of the event provider: 91 | 92 | ```rust 93 | let my_app_events = MyAppEvents::new(); 94 | ``` 95 | 96 | Your application should only create a single instance of each event provider, per process. 97 | That is, you should create a single instance of your event provider and share it across your 98 | process. Typically, an instance is stored in static variable, using a lazy / atomic assignment. 99 | There are many crates and types which can support this usage pattern. 100 | 101 | ### Call event methods to report events 102 | 103 | To report an event, call one of the methods defined on the event provider. The method will 104 | call into ETW to report the event, but there is no guarantee that the event is stored or 105 | forwarded; events can be dropped if event buffer resources are scarce. 106 | 107 | ```rust 108 | my_app_events.client_connected(None, &"192.168.0.42:6667".parse(), false, 100, "OK"); 109 | ``` 110 | 111 | Note that all generated event methods have an added first parameters, 112 | `options: Option<&EventOptions>`. This parameter allows you to override per-event parameters, 113 | such as the event level and event correlation IDs. In most cases, you should pass `None`. 114 | 115 | ## Supported field types 116 | 117 | Only a limited set of field types are supported. 118 | 119 | * Integer primitives up to 64 bits: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64` 120 | * Floating point primitives: `f32`, `f64` 121 | * Architecture-dependent sizes: `usize`, `isize`. 122 | * Boolean: `bool` 123 | * Slices of all of the supported primitives: `&[u8]`, `&[u16]`, etc. 124 | * Windows [`FILETIME`](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime). 125 | The type must be declared _exactly_ as `FILETIME`; type aliases or fully-qualified paths 126 | (such as `windows_sys::Win32::Foundation::FILETIME`) _will not work_. The parameter type in the 127 | generated code will be `win_etw_provider::FILETIME`, which is a newtype over `u64`. 128 | * `std::time::SystemTime` is supported, but it must be declared _exactly_ as `SystemTime`; 129 | type aliases or fully-qualified paths (such as `std::time::SystemTime`) _will not work_. 130 | * `SockAddr`, `SockAddrV4`, and `SockAddrV6` are supported. They must be declared exactly as 131 | shown, not using fully-qualified names or type aliases. 132 | 133 | ## How to capture and view events 134 | 135 | There are a variety of tools which can be used to capture and view ETW events. 136 | The simplest tool is the `TraceView` tool from the Windows SDK. Typically it is installed 137 | at this path: `C:\Program Files (x86)\Windows Kits\10\bin\10.0..0\x64\traceview.exe`, 138 | where `` is the release number of the Windows SDK. 139 | 140 | Run `TraceView`, then select "File", then "Create New Log Session". Select "Manually Entered 141 | GUID or Hashed Name" and enter the GUID that you have assigned to your event provider. Click OK. 142 | The next dialog will prompt you to choose a source of WPP format information; select Auto 143 | and click OK. 144 | 145 | At this point, `TraceView` should be capturing events (for your assigned GUID) and displaying 146 | them in real time, regardless of which process reported the events. 147 | 148 | These tools can also be used to capture ETW events: 149 | 150 | * [Windows Performance Recorder](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) 151 | This tool is intended for capturing system-wide event streams. It is not useful for capturing 152 | events for a specific event provider. 153 | * [logman](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/logman) 154 | is a command-line tool for managing events. 155 | * [Tracelog](https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/tracelog) 156 | 157 | There are other tools, such as the Windows Performance Recorder, which can capture ETW events. 158 | 159 | ## Ideas for improvement 160 | 161 | * Better handling of per-event overrides, rather than using `Option<&EventOptions>`. 162 | 163 | ## References 164 | 165 | * [Event Tracing for Windows (ETW) Simplified](https://support.microsoft.com/en-us/help/2593157/event-tracing-for-windows-etw-simplified) 166 | * [TraceLogging for Event Tracing for Windows (ETW)](https://docs.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal) 167 | * [Record and View TraceLogging Events](https://docs.microsoft.com/en-us/windows/win32/tracelogging/tracelogging-record-and-display-tracelogging-events) 168 | * [TraceLoggingOptionGroup](https://docs.microsoft.com/en-us/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-traceloggingoptiongroup) 169 | * [Provider Traits](https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits) 170 | * [TRACELOGGING_DEFINE_PROVIDER macro](https://learn.microsoft.com/en-us/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-tracelogging_define_provider) 171 | 172 | ## Contributing 173 | 174 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 175 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 176 | the rights to use your contribution. For details, visit . 177 | 178 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 179 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 180 | provided by the bot. You will only need to do this once across all repos using our CLA. 181 | 182 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 183 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 184 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 185 | -------------------------------------------------------------------------------- /win_etw_metadata/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Definitions for metadata used by 2 | //! [Event Tracing for Windows](https://docs.microsoft.com/en-us/windows/win32/etw/event-tracing-portal). 3 | //! 4 | //! These definitions are used by the `win_etw_macros` crate to describe the schema of events. 5 | //! Most applications will not need to use these definitions directly. 6 | 7 | #![no_std] 8 | #![deny(missing_docs)] 9 | 10 | use bitflags::bitflags; 11 | 12 | /// This structure describes the start of the ETW metadata section. A single static instance of 13 | /// this structure is placed in PE/COFF modules, and it identifies the start of the ETW metadata 14 | /// section. In this implementation, that single instance is `ETW_TRACE_LOGGING_METADATA`. 15 | #[repr(C)] 16 | pub struct TraceLoggingMetadata { 17 | signature: u32, // = _tlg_MetadataSignature = "ETW0" 18 | size: u16, // = sizeof(_TraceLoggingMetadata_t) 19 | version: u8, // = _tlg_MetadataVersion 20 | flags: u8, // = _tlg_MetadataFlags 21 | magic: u64, // = _tlg_MetadataMagic 22 | } 23 | 24 | /// The value stored in `TraceLoggingMetadata::signature`. 25 | /// In little-endian ASCII, this is "ETW0". 26 | pub const METADATA_SIGNATURE: u32 = 0x30_57_54_45; 27 | 28 | /// The value stored in `TraceLoggingMetadata::magic`. 29 | pub const METADATA_MAGIC: u64 = 0xBB8A_052B_8804_0E86; 30 | 31 | /// The version of the metadata emitted. Currently, there is only one version. 32 | pub const METADATA_VERSION: u8 = 0; 33 | 34 | /// The bit flag which indicates the size of pointers on the target architecture. 35 | /// The value of this constant depends on the target architecture. 36 | #[cfg(target_pointer_width = "64")] 37 | pub const METADATA_FLAGS_POINTER_WIDTH: u8 = 1; 38 | 39 | /// The bit flag which indicates the size of pointers on the target architecture. 40 | /// The value of this constant depends on the target architecture. 41 | #[cfg(not(target_pointer_width = "64"))] 42 | pub const METADATA_FLAGS_POINTER_WIDTH: u8 = 0; 43 | 44 | #[cfg(feature = "metadata_headers")] 45 | #[link_section = ".rdata$etw0"] 46 | #[used] 47 | #[no_mangle] 48 | static ETW_TRACE_LOGGING_METADATA: TraceLoggingMetadata = TraceLoggingMetadata { 49 | signature: METADATA_SIGNATURE, 50 | size: core::mem::size_of::() as u16, 51 | version: METADATA_VERSION, 52 | flags: METADATA_FLAGS_POINTER_WIDTH, 53 | magic: METADATA_MAGIC, 54 | }; 55 | 56 | /// Predefined event tracing levels 57 | #[repr(transparent)] 58 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] 59 | pub struct Level(pub u8); 60 | 61 | impl Level { 62 | /// Tracing is not on 63 | pub const NONE: Level = Level(0); 64 | /// Abnormal exit or termination 65 | pub const CRITICAL: Level = Level(1); 66 | /// Deprecated name for Abnormal exit or termination 67 | pub const FATAL: Level = Level(1); 68 | /// Severe errors that need logging 69 | pub const ERROR: Level = Level(2); 70 | /// Warnings such as allocation failure 71 | pub const WARN: Level = Level(3); 72 | /// Includes non-error cases(e.g.,Entry-Exit) 73 | pub const INFO: Level = Level(4); 74 | /// Detailed traces from intermediate steps 75 | pub const VERBOSE: Level = Level(5); 76 | } 77 | 78 | bitflags! { 79 | /// Defines the input type of a field. 80 | /// In traceloggingprovider.h, this is the 'TlgIn_t` enumerated type. 81 | #[repr(transparent)] 82 | pub struct InFlag: u8 { 83 | /// No value at all 84 | const NULL = 0; 85 | /// A wide string (UTF-16), corresponding to `PCWSTR` in Win32. 86 | const UNICODE_STRING = 1; 87 | /// An ANSI string, corresponding to `PCSTR` in Win32. 88 | /// The character set can be specified as UTF-8 by using `OutFlag::UTF8`. 89 | const ANSI_STRING = 2; 90 | /// `i8` 91 | const INT8 = 3; 92 | /// `u8` 93 | const UINT8 = 4; 94 | /// `i16`, stored in little-endian form. 95 | const INT16 = 5; 96 | /// `u16`, stored in little-endian form. 97 | const UINT16 = 6; 98 | /// `i16`, stored in little-endian form. 99 | const INT32 = 7; 100 | /// `u32`, stored in little-endian form. 101 | const UINT32 = 8; 102 | /// `i64`, stored in little-endian form. 103 | const INT64 = 9; 104 | /// `u64`, stored in little-endian form. 105 | const UINT64 = 10; 106 | /// `f32`, stored in little-endian form. 107 | const FLOAT = 11; 108 | /// `f64`, stored in little-endian form. 109 | const DOUBLE = 12; 110 | /// A Win32 'BOOL' value, which is `i32`, stored in little-endian form. 111 | const BOOL32 = 13; 112 | /// An array of bytes, stored in little-endian form. 113 | const BINARY = 14; 114 | /// A `GUID`, stored in canonical byte-oriented representation. The fields within the `GUID` 115 | /// are stored in big-endian form. 116 | const GUID = 15; 117 | // POINTER (16) is not supported 118 | /// A Win32 [`FILETIME`](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime) 119 | /// value. `FILETIME` values are `u64` values, stored in little-endian form, counting 100ns 120 | /// intervals from the `FILETIME` epoch. 121 | const FILETIME = 17; 122 | /// A Win32 [`SYSTEMTIME`](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime) 123 | /// value, with fields encoded in little-endian form. 124 | const SYSTEMTIME = 18; 125 | /// A Win32 [`SID`](https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid). 126 | const SID = 19; 127 | /// An `i32` value, encoded in little-endian form, displayed in hexadecimal. 128 | const HEXINT32 = 20; 129 | /// An `i64` value, encoded in little-endian form, displayed in hexadecimal. 130 | const HEXINT64 = 21; 131 | /// A counted wide string (UTF-16), corresponding to `UNICODE_STRING` in Win32. 132 | /// This type uses two data descriptor slots. The first is a `u16` value, giving the 133 | /// length of the string data in WCHAR units (not bytes). The second points to the 134 | /// character data. 135 | const COUNTED_UNICODE_STRING = 22; 136 | /// A counted ANSI string, corresponding to `STRING` in Win32. 137 | /// The character set can be specified as UTF-8 by using `OutFlag::UTF8`. 138 | /// This type uses two data descriptor slots. The first is a `u16` value, giving the 139 | /// length of the string data in WCHAR units (not bytes). The second points to the 140 | /// character data. 141 | const COUNTED_ANSI_STRING = 23; 142 | /// A flag which indicates that this field is an array of constant length. 143 | /// If this field is present, then the metadata contains an additional `u16` field, which 144 | /// is the constant length. 145 | const CCOUNT_FLAG = 0x20; 146 | /// A flag which indicates that this field has a dynamic length. The field uses two 147 | /// data descriptors. The first is a `u16` field specifying the length of the array. 148 | /// The second points to the array data. 149 | const VCOUNT_FLAG = 0x40; 150 | /// A flag which indicates that this field metadata also includes an `OutFlag`. The 151 | /// `OutFlag` byte immediately follows the `InFlag` byte. 152 | const CHAIN_FLAG = 0b1000_0000; 153 | /// A flag which indicates that the field uses a custom serializer. 154 | const CUSTOM_FLAG = 0b0110_0000; 155 | /// A mask of the field type flags. 156 | const TYPE_MASK = 0b0001_1111; 157 | /// A mask of the field length flags (`VCOUNT_FLAG`, `CCOUNT_FLAG`, `CUSTOM_FLAG`). 158 | const COUNT_MASK = 0b0110_0000; 159 | /// A mask over all of the flags (all bits excluding the type bits). 160 | const FLAG_MASK = 0b1110_0000; 161 | } 162 | } 163 | 164 | impl InFlag { 165 | /// An alias for the architecture-dependent `USIZE` (pointer-sized word) `InFlag`. 166 | #[cfg(target_pointer_width = "32")] 167 | pub const USIZE: InFlag = InFlag::UINT32; 168 | 169 | /// An alias for the architecture-dependent `ISIZE` (pointer-sized word) `InFlag`. 170 | #[cfg(target_pointer_width = "32")] 171 | pub const ISIZE: InFlag = InFlag::INT32; 172 | 173 | /// An alias for the architecture-dependent `USIZE` (pointer-sized word) `InFlag`. 174 | #[cfg(target_pointer_width = "64")] 175 | pub const USIZE: InFlag = InFlag::UINT64; 176 | 177 | /// An alias for the architecture-dependent `ISIZE` (pointer-sized word) `InFlag`. 178 | #[cfg(target_pointer_width = "64")] 179 | pub const ISIZE: InFlag = InFlag::INT64; 180 | } 181 | 182 | bitflags! { 183 | /// Specifies how a field should be interpreted or displayed. 184 | #[repr(transparent)] 185 | pub struct OutFlag: u8 { 186 | /// No display at all. 187 | const NULL = 0; 188 | /// No display at all. 189 | const NOPRINT = 1; 190 | /// Contains text. 191 | const STRING = 2; 192 | /// Is a boolean. This can only be used with `InFlag::INT8`. 193 | const BOOLEAN = 3; 194 | /// Display in hexadecimal. Can be used with any integer type. 195 | const HEX = 4; 196 | /// The field is a Win32 process ID. 197 | const PID = 5; 198 | /// The field is a Win32 thread ID. 199 | const TID = 6; 200 | /// The field is a TCP/IP or UDP/IP port, in big-endian encoding. 201 | const PORT = 7; 202 | /// The field is an IP v4 address, in big-endian encoding. 203 | const IPV4 = 8; 204 | /// The field is an IP v6 address, in big-endian encoding. 205 | const IPV6 = 9; 206 | /// The field is a `SOCKADDR`. See `[SOCKADDR](https://docs.microsoft.com/en-us/windows/win32/api/winsock/ns-winsock-sockaddr)`. 207 | const SOCKETADDRESS = 10; 208 | /// The field is text, and should be interpreted as XML. 209 | const XML = 11; 210 | /// The field is text, and should be interpreted as JSON. 211 | const JSON = 12; 212 | /// The field is a `Win32` error code. The field type should be `InFlag::UINT32`. 213 | const WIN32ERROR = 13; 214 | /// The field is an `NTSTATUS` error code. The field type should be `InFlag::UINT32`. 215 | const NTSTATUS = 14; 216 | /// The field is an `HRESULT` error code. The field type should be `InFlag::INT32`. 217 | const HRESULT = 15; 218 | /// The field is a Win32 `FILETIME` value. 219 | const FILETIME = 16; 220 | /// Display field as signed. 221 | const SIGNED = 17; 222 | /// Display field as unsigned. 223 | const UNSIGNED = 18; 224 | /// For strings, indicates that the string encoding is UTF-8. 225 | const UTF8 = 35; 226 | /// Used with `InFlag::BINARY`. 227 | const PKCS7_WITH_TYPE_INFO = 36; 228 | // const CODE_POINTER = 37; 229 | 230 | /// Indicates that the timezone for a time value is UTC. 231 | /// This can be used with `InFlag::FILETIME` or `InFlag::SYSTEMTIME`. 232 | const DATETIME_UTC = 38; 233 | } 234 | } 235 | 236 | // Event categories specified via keywords 237 | /// Event category for critical data (specified via keyword) 238 | pub const MICROSOFT_KEYWORD_CRITICAL_DATA: u64 = 0x0000_8000_0000_0000; 239 | /// Event category for measures (specified via keyword) 240 | pub const MICROSOFT_KEYWORD_MEASURES: u64 = 0x0000_4000_0000_0000; 241 | /// Event category for telemetry (specified via keyword) 242 | pub const MICROSOFT_KEYWORD_TELEMETRY: u64 = 0x0000_2000_0000_0000; 243 | 244 | // Event categories specified via event tags 245 | /// Event category defined by WIL 246 | pub const MICROSOFT_EVENTTAG_DROP_USER_IDS: u32 = 0x0000_8000; 247 | /// Event category defined by WIL 248 | pub const MICROSOFT_EVENTTAG_AGGREGATE: u32 = 0x0001_0000; 249 | /// Event category defined by WIL 250 | pub const MICROSOFT_EVENTTAG_DROP_PII_EXCEPT_IP: u32 = 0x0002_0000; 251 | /// Event category defined by WIL 252 | pub const MICROSOFT_EVENTTAG_COSTDEFERRED_LATENCY: u32 = 0x0004_0000; 253 | /// Event category defined by WIL 254 | pub const MICROSOFT_EVENTTAG_CORE_DATA: u32 = 0x0008_0000; 255 | /// Event category defined by WIL 256 | pub const MICROSOFT_EVENTTAG_INJECT_XTOKEN: u32 = 0x0010_0000; 257 | /// Event category defined by WIL 258 | pub const MICROSOFT_EVENTTAG_REALTIME_LATENCY: u32 = 0x0020_0000; 259 | /// Event category defined by WIL 260 | pub const MICROSOFT_EVENTTAG_NORMAL_LATENCY: u32 = 0x0040_0000; 261 | /// Event category defined by WIL 262 | pub const MICROSOFT_EVENTTAG_CRITICAL_PERSISTENCE: u32 = 0x0080_0000; 263 | /// Event category defined by WIL 264 | pub const MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE: u32 = 0x0100_0000; 265 | /// Event category defined by WIL 266 | pub const MICROSOFT_EVENTTAG_DROP_PII: u32 = 0x0200_0000; 267 | /// Event category defined by WIL 268 | pub const MICROSOFT_EVENTTAG_HASH_PII: u32 = 0x0400_0000; 269 | /// Event category defined by WIL 270 | pub const MICROSOFT_EVENTTAG_MARK_PII: u32 = 0x0800_0000; 271 | -------------------------------------------------------------------------------- /win_etw_provider/src/provider.rs: -------------------------------------------------------------------------------- 1 | use crate::guid::GUID; 2 | use crate::Level; 3 | use crate::{Error, EventDataDescriptor}; 4 | use alloc::boxed::Box; 5 | use core::convert::TryFrom; 6 | use core::pin::Pin; 7 | use core::ptr::null; 8 | use core::sync::atomic::{AtomicU8, Ordering::SeqCst}; 9 | #[cfg(target_os = "windows")] 10 | use windows_sys::Win32::System::Diagnostics::Etw::{EventProviderSetTraits, REGHANDLE}; 11 | 12 | #[cfg(target_os = "windows")] 13 | use win_support::*; 14 | 15 | /// Generates a new activity ID. 16 | /// 17 | /// This function is only implemented on Windows. On other platforms, it will always return `Err`. 18 | pub fn new_activity_id() -> Result { 19 | #[cfg(target_os = "windows")] 20 | { 21 | win_support::new_activity_id() 22 | } 23 | 24 | #[cfg(not(target_os = "windows"))] 25 | { 26 | Err(Error::NotSupported) 27 | } 28 | } 29 | 30 | /// Gets the current activity ID. 31 | /// 32 | /// This function is only implemented on Windows. On other platforms, it will always return `Err`. 33 | pub fn get_current_thread_activity_id() -> Result { 34 | #[cfg(target_os = "windows")] 35 | { 36 | unsafe { 37 | let mut guid: windows_sys::core::GUID = core::mem::zeroed(); 38 | let error = EventActivityIdControl(EVENT_ACTIVITY_CTRL_GET_ID, &mut guid); 39 | if error == 0 { 40 | Ok(guid.into()) 41 | } else { 42 | Err(Error::WindowsError(error)) 43 | } 44 | } 45 | } 46 | 47 | #[cfg(not(target_os = "windows"))] 48 | { 49 | Err(Error::NotSupported) 50 | } 51 | } 52 | 53 | /// Describes the functions needed for an event provider backend. This is an implementation 54 | /// detail, and should not be used directly by applications. 55 | pub trait Provider { 56 | /// Writes one event. 57 | fn write( 58 | &self, 59 | options: Option<&crate::EventOptions>, 60 | descriptor: &EventDescriptor, 61 | data: &[EventDataDescriptor<'_>], 62 | ); 63 | 64 | /// Checks whether the event provider is enabled. 65 | fn is_enabled(&self, level: u8, keyword: u64) -> bool; 66 | 67 | /// Checks whether a specific event is enabled. 68 | fn is_event_enabled(&self, event_descriptor: &EventDescriptor) -> bool; 69 | } 70 | 71 | /// Implements `Provider` by discarding all events. 72 | pub struct NullProvider; 73 | 74 | impl Provider for NullProvider { 75 | fn write( 76 | &self, 77 | _options: Option<&crate::EventOptions>, 78 | _descriptor: &EventDescriptor, 79 | _data: &[EventDataDescriptor<'_>], 80 | ) { 81 | } 82 | 83 | fn is_enabled(&self, _level: u8, _keyword: u64) -> bool { 84 | false 85 | } 86 | fn is_event_enabled(&self, _event_descriptor: &EventDescriptor) -> bool { 87 | false 88 | } 89 | } 90 | 91 | impl Provider for Option { 92 | fn write( 93 | &self, 94 | options: Option<&crate::EventOptions>, 95 | descriptor: &EventDescriptor, 96 | data: &[EventDataDescriptor<'_>], 97 | ) { 98 | if let Some(p) = self { 99 | p.write(options, descriptor, data); 100 | } 101 | } 102 | 103 | fn is_enabled(&self, level: u8, keyword: u64) -> bool { 104 | match self { 105 | Some(p) => p.is_enabled(level, keyword), 106 | None => false, 107 | } 108 | } 109 | fn is_event_enabled(&self, event_descriptor: &EventDescriptor) -> bool { 110 | match self { 111 | Some(p) => p.is_event_enabled(event_descriptor), 112 | None => false, 113 | } 114 | } 115 | } 116 | 117 | /// Implements `Provider` by registering with ETW. 118 | pub struct EtwProvider { 119 | #[cfg(target_os = "windows")] 120 | handle: REGHANDLE, 121 | 122 | #[cfg(target_os = "windows")] 123 | // #[allow(dead_code)] // Needed for lifetime control 124 | stable: Pin>, 125 | } 126 | 127 | impl Provider for EtwProvider { 128 | #[inline(always)] 129 | fn write( 130 | &self, 131 | options: Option<&crate::EventOptions>, 132 | descriptor: &EventDescriptor, 133 | data: &[EventDataDescriptor<'_>], 134 | ) { 135 | #[cfg(target_os = "windows")] 136 | { 137 | unsafe { 138 | let mut activity_id_ptr = null(); 139 | let mut related_activity_id_ptr = null(); 140 | 141 | let mut event_descriptor = EVENT_DESCRIPTOR { 142 | Id: descriptor.id, 143 | Version: descriptor.version, 144 | Channel: descriptor.channel, 145 | Level: descriptor.level.0, 146 | Opcode: descriptor.opcode, 147 | Task: descriptor.task, 148 | Keyword: descriptor.keyword, 149 | }; 150 | 151 | if let Some(options) = options { 152 | if let Some(id) = options.activity_id.as_ref() { 153 | activity_id_ptr = id as *const GUID as *const windows_sys::core::GUID; 154 | } 155 | if let Some(id) = options.related_activity_id.as_ref() { 156 | related_activity_id_ptr = 157 | id as *const GUID as *const windows_sys::core::GUID; 158 | } 159 | if let Some(level) = options.level { 160 | event_descriptor.Level = level.0; 161 | } 162 | } 163 | 164 | let error = EventWriteEx( 165 | self.handle, 166 | &event_descriptor, 167 | 0, // filter 168 | 0, // flags 169 | activity_id_ptr, 170 | related_activity_id_ptr, 171 | data.len() as u32, 172 | data.as_ptr() as *const EVENT_DATA_DESCRIPTOR, 173 | ); 174 | if error != 0 { 175 | write_failed(error) 176 | } 177 | } 178 | } 179 | } 180 | 181 | // write_ex 182 | // write_transfer 183 | 184 | fn is_enabled(&self, level: u8, keyword: u64) -> bool { 185 | #[cfg(target_os = "windows")] 186 | { 187 | unsafe { EventProviderEnabled(self.handle, level, keyword) } 188 | } 189 | #[cfg(not(target_os = "windows"))] 190 | { 191 | false 192 | } 193 | } 194 | 195 | fn is_event_enabled(&self, event_descriptor: &EventDescriptor) -> bool { 196 | #[cfg(target_os = "windows")] 197 | { 198 | if false { 199 | unsafe { 200 | EventEnabled( 201 | self.handle, 202 | event_descriptor as *const _ as *const EVENT_DESCRIPTOR, 203 | ) 204 | } 205 | } else { 206 | let max_level = self.stable.as_ref().max_level.load(SeqCst); 207 | event_descriptor.level.0 <= max_level 208 | } 209 | } 210 | #[cfg(not(target_os = "windows"))] 211 | { 212 | false 213 | } 214 | } 215 | } 216 | 217 | #[inline(never)] 218 | fn write_failed(_error: u32) { 219 | #[cfg(feature = "dev")] 220 | { 221 | eprintln!("EventWrite failed: {}", _error); 222 | } 223 | } 224 | 225 | #[cfg(target_os = "windows")] 226 | mod win_support { 227 | pub use windows_sys::Win32::Foundation::ERROR_SUCCESS; 228 | pub use windows_sys::Win32::System::Diagnostics::Etw::{ 229 | EventActivityIdControl, EventEnabled, EventProviderEnabled, EventRegister, 230 | EventSetInformation, EventUnregister, EventWriteEx, ENABLECALLBACK_ENABLED_STATE, 231 | EVENT_ACTIVITY_CTRL_CREATE_ID, EVENT_ACTIVITY_CTRL_CREATE_SET_ID, 232 | EVENT_ACTIVITY_CTRL_GET_ID, EVENT_ACTIVITY_CTRL_SET_ID, EVENT_CONTROL_CODE_CAPTURE_STATE, 233 | EVENT_CONTROL_CODE_DISABLE_PROVIDER, EVENT_CONTROL_CODE_ENABLE_PROVIDER, 234 | EVENT_DATA_DESCRIPTOR, EVENT_DESCRIPTOR, EVENT_FILTER_DESCRIPTOR, 235 | }; 236 | 237 | use super::*; 238 | 239 | /// This data is stored in a Box, so that it has a stable address. 240 | /// It is used to coordinate with ETW; ETW runs callbacks that need a stable pointer. 241 | /// See `EventRegister` and the "enable callback". 242 | pub(crate) struct StableProviderData { 243 | pub(crate) max_level: AtomicU8, 244 | } 245 | 246 | /// See [PENABLECALLBACK](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nc-evntprov-penablecallback). 247 | #[allow(non_snake_case)] 248 | pub(crate) unsafe extern "system" fn enable_callback( 249 | _source_id: *const windows_sys::core::GUID, 250 | is_enabled_code: ENABLECALLBACK_ENABLED_STATE, 251 | level: u8, 252 | _match_any_keyword: u64, 253 | _match_all_keyword: u64, 254 | _filter_data: *const EVENT_FILTER_DESCRIPTOR, 255 | context: *mut core::ffi::c_void, 256 | ) { 257 | // This should never happen. 258 | if context.is_null() { 259 | return; 260 | } 261 | let stable_data: &StableProviderData = &*(context as *const _ as *const StableProviderData); 262 | 263 | let _source_id: GUID = if _source_id.is_null() { 264 | GUID::default() 265 | } else { 266 | (*(_source_id as *const GUID)).clone() 267 | }; 268 | #[cfg(feature = "dev")] 269 | { 270 | eprintln!( 271 | "enable_callback: source_id {} is_enabled {}, level {}, any {:#x} all {:#x} filter? {:?}", 272 | _source_id, is_enabled_code, level, _match_any_keyword, _match_all_keyword, 273 | !_filter_data.is_null() 274 | ); 275 | } 276 | 277 | match is_enabled_code { 278 | EVENT_CONTROL_CODE_ENABLE_PROVIDER => { 279 | #[cfg(feature = "dev")] 280 | { 281 | eprintln!("ETW is ENABLING this provider. setting level: {}", level); 282 | } 283 | stable_data.max_level.store(level, SeqCst); 284 | } 285 | EVENT_CONTROL_CODE_DISABLE_PROVIDER => { 286 | #[cfg(feature = "dev")] 287 | { 288 | eprintln!("ETW is DISABLING this provider. setting level: {}", level); 289 | } 290 | stable_data.max_level.store(level, SeqCst); 291 | } 292 | EVENT_CONTROL_CODE_CAPTURE_STATE => { 293 | // ETW is requesting that the provider log its state information. The meaning of this 294 | // is provider-dependent. Currently, this functionality is not exposed to Rust apps. 295 | #[cfg(feature = "dev")] 296 | { 297 | eprintln!("EVENT_CONTROL_CODE_CAPTURE_STATE"); 298 | } 299 | } 300 | _ => { 301 | // The control code is unrecognized. 302 | #[cfg(feature = "dev")] 303 | { 304 | eprintln!( 305 | "enable_callback: control code {} is not recognized", 306 | is_enabled_code 307 | ); 308 | } 309 | } 310 | } 311 | } 312 | 313 | pub fn new_activity_id() -> Result { 314 | unsafe { 315 | let mut guid: windows_sys::core::GUID = core::mem::zeroed(); 316 | let error = EventActivityIdControl(EVENT_ACTIVITY_CTRL_CREATE_ID, &mut guid); 317 | if error == 0 { 318 | Ok(guid.into()) 319 | } else { 320 | Err(Error::WindowsError(error)) 321 | } 322 | } 323 | } 324 | } 325 | 326 | impl EtwProvider { 327 | /// Registers an event provider with ETW. 328 | /// 329 | /// The implementation uses `[EventWriteEx](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventwriteex)`. 330 | pub fn new(provider_id: &GUID) -> Result { 331 | #[cfg(target_os = "windows")] 332 | { 333 | unsafe { 334 | let mut stable = Box::pin(StableProviderData { 335 | max_level: AtomicU8::new(0), 336 | }); 337 | let mut handle: REGHANDLE = 0; 338 | let stable_ptr: &mut StableProviderData = &mut stable; 339 | let error = EventRegister( 340 | provider_id as *const _ as *const windows_sys::core::GUID, 341 | Some(enable_callback), 342 | stable_ptr as *mut StableProviderData as *mut core::ffi::c_void, 343 | &mut handle, 344 | ); 345 | if error != 0 { 346 | Err(Error::WindowsError(error)) 347 | } else { 348 | Ok(EtwProvider { handle, stable }) 349 | } 350 | } 351 | } 352 | #[cfg(not(target_os = "windows"))] 353 | { 354 | Ok(EtwProvider {}) 355 | } 356 | } 357 | 358 | /// See TraceLoggingRegisterEx in traceloggingprovider.h. 359 | /// This registers provider metadata. 360 | pub fn register_provider_metadata(&mut self, provider_metadata: &[u8]) -> Result<(), Error> { 361 | #[cfg(target_os = "windows")] 362 | { 363 | unsafe { 364 | let error = EventSetInformation( 365 | self.handle, 366 | EventProviderSetTraits, 367 | provider_metadata.as_ptr() as *const core::ffi::c_void, 368 | u32::try_from(provider_metadata.len()).unwrap(), 369 | ); 370 | if error != 0 { 371 | Err(Error::WindowsError(error)) 372 | } else { 373 | #[cfg(feature = "dev")] 374 | { 375 | eprintln!("register_provider_metadata: succeeded"); 376 | } 377 | Ok(()) 378 | } 379 | } 380 | } 381 | #[cfg(not(target_os = "windows"))] 382 | { 383 | Ok(()) 384 | } 385 | } 386 | 387 | /// Registers provider traits for a provider. 388 | /// 389 | /// ETW providers should not call this function directly. It is automatically 390 | /// called by the provider code that is generated by `win_etw_macros`. 391 | /// 392 | /// See [Provider Traits](https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits). 393 | pub fn set_provider_traits(&mut self, provider_traits: &[u8]) -> Result<(), Error> { 394 | #[cfg(target_os = "windows")] 395 | { 396 | unsafe { 397 | let error = EventSetInformation( 398 | self.handle, 399 | EventProviderSetTraits, 400 | provider_traits.as_ptr() as *const core::ffi::c_void, 401 | u32::try_from(provider_traits.len()).unwrap(), 402 | ); 403 | if error != 0 { 404 | #[cfg(feature = "dev")] 405 | { 406 | eprintln!("EventSetInformation failed for provider traits"); 407 | } 408 | return Err(Error::WindowsError(error)); 409 | } 410 | } 411 | Ok(()) 412 | } 413 | #[cfg(not(target_os = "windows"))] 414 | { 415 | Ok(()) 416 | } 417 | } 418 | } 419 | 420 | impl Drop for EtwProvider { 421 | fn drop(&mut self) { 422 | #[cfg(target_os = "windows")] 423 | { 424 | unsafe { 425 | EventUnregister(self.handle); 426 | } 427 | } 428 | } 429 | } 430 | 431 | unsafe impl Send for EtwProvider {} 432 | unsafe impl Sync for EtwProvider {} 433 | 434 | /// Describes parameters for an event. This is an implementation detail, and should not be directly 435 | /// used by applications. 436 | #[repr(C)] 437 | #[allow(missing_docs)] 438 | pub struct EventDescriptor { 439 | pub id: u16, 440 | pub version: u8, 441 | pub channel: u8, 442 | pub level: Level, 443 | pub opcode: u8, 444 | pub task: u16, 445 | pub keyword: u64, 446 | } 447 | 448 | /// Allows an application to enter a nested activity scope. This creates a new activity ID, 449 | /// sets this activity ID as the current activity ID of the current thread, and then runs the 450 | /// provided function. After the function finishes, it restores the activity ID of the calling 451 | /// thread (even if a panic occurs). 452 | /// 453 | /// See `[EventActivityIdControl](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventactivityidcontrol)`. 454 | #[inline(always)] 455 | pub fn with_activity R, R>(f: F) -> R { 456 | #[cfg(target_os = "windows")] 457 | { 458 | let mut previous_activity_id: GUID = Default::default(); 459 | 460 | let mut restore = RestoreActivityHolder { 461 | previous_activity_id: None, 462 | }; 463 | 464 | unsafe { 465 | let result = EventActivityIdControl( 466 | EVENT_ACTIVITY_CTRL_CREATE_SET_ID, 467 | &mut previous_activity_id as *mut _ as *mut windows_sys::core::GUID, 468 | ); 469 | if result == ERROR_SUCCESS { 470 | restore.previous_activity_id = Some(previous_activity_id); 471 | } else { 472 | // Failed to create/replace the activity ID. There is not much we can do about this. 473 | } 474 | } 475 | 476 | let result = f(); 477 | // RestoreActivityHolder::drop() will run, even if f() panics, and will restore the 478 | // activity ID of the current thread. 479 | drop(restore); 480 | result 481 | } 482 | 483 | #[cfg(not(target_os = "windows"))] 484 | { 485 | f() 486 | } 487 | } 488 | 489 | struct RestoreActivityHolder { 490 | previous_activity_id: Option, 491 | } 492 | 493 | impl Drop for RestoreActivityHolder { 494 | fn drop(&mut self) { 495 | #[cfg(target_os = "windows")] 496 | { 497 | unsafe { 498 | if let Some(previous_activity_id) = self.previous_activity_id.as_ref() { 499 | EventActivityIdControl( 500 | EVENT_ACTIVITY_CTRL_SET_ID, 501 | previous_activity_id as *const GUID as *const windows_sys::core::GUID 502 | as *mut _, 503 | ); 504 | } 505 | } 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /win_etw_macros/src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | struct CompileErrors { 4 | errors: Vec, 5 | } 6 | 7 | use proc_macro2::TokenTree; 8 | use syn::buffer::Cursor; 9 | 10 | impl syn::parse::Parse for CompileErrors { 11 | fn parse(s: syn::parse::ParseStream) -> syn::Result { 12 | s.step(|c| { 13 | let mut c: Cursor = *c; 14 | 15 | let mut errors = Vec::new(); 16 | 17 | while !c.eof() { 18 | if let Some((i, next)) = c.ident() { 19 | if i == "compile_error" { 20 | if let Some((p, next)) = next.punct() { 21 | if p.as_char() == '!' { 22 | if let Some((TokenTree::Group(args), next)) = next.token_tree() { 23 | // println!("found compile_error!(...): {:?}", args); 24 | let real_args: syn::LitStr = syn::parse2(args.stream())?; 25 | // println!("real_args: {:?}", real_args); 26 | errors.push(real_args.value()); 27 | // errors.push(args); 28 | c = next; 29 | continue; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | // Didn't recognize it. 36 | if let Some((_ignored, next)) = c.token_tree() { 37 | c = next; 38 | } else { 39 | println!("cursor is positioned on something that is not a token tree!"); 40 | break; 41 | } 42 | } 43 | 44 | Ok((Self { errors }, Cursor::empty())) 45 | }) 46 | } 47 | } 48 | 49 | fn test_worker(attrs: TokenStream, input: TokenStream, expected_errors: &[&'static str]) { 50 | let output = trace_logging_events_core(attrs, input); 51 | 52 | // Set WIN_ETW_SHOW_OUTPUT=1 (or = anything at all) to see the output of 53 | // the trace_logging_provider macro for unit tests. This is useful during 54 | // development. 55 | if std::env::var("WIN_ETW_SHOW_OUTPUT").is_ok() { 56 | let output_str = format!("{}", output); 57 | use std::io::Write; 58 | use std::process::{Command, Stdio}; 59 | let mut rustfmt_cmd = Command::new("rustfmt") 60 | .stdin(Stdio::piped()) 61 | .spawn() 62 | .expect("rustfmt failed"); 63 | let mut child_stdin = rustfmt_cmd.stdin.take().unwrap(); 64 | child_stdin.write_all(output_str.as_bytes()).unwrap(); 65 | drop(child_stdin); 66 | rustfmt_cmd.wait().unwrap(); 67 | } 68 | 69 | // Scan 'output' for errors. 70 | let errors: CompileErrors = syn::parse2(output).unwrap(); 71 | if expected_errors.is_empty() { 72 | assert!( 73 | errors.errors.is_empty(), 74 | "Macro produced errors:\n{:#?}", 75 | errors.errors 76 | ); 77 | } else { 78 | // For each of the errors in expected_errors, scan the list of actual errors. 79 | // Do a simple substring search. 80 | for &expected_error in expected_errors.iter() { 81 | if errors.errors.iter().any(|e| { 82 | // println!("checking in {:?}", e); 83 | e.contains(expected_error) 84 | }) { 85 | // println!("found expected error {:?}", expected_error); 86 | } else { 87 | panic!( 88 | "Did not find expected error {:?} in list:\n{:#?}", 89 | expected_error, errors.errors 90 | ); 91 | } 92 | } 93 | } 94 | 95 | // Make sure all errors found in compilation is expected. 96 | for error in errors.errors { 97 | if expected_errors.iter().any(|e| { 98 | // println!("checking in {:?}", e); 99 | let s = String::from(*e); 100 | error.contains(&s) 101 | }) { 102 | // println!("found expected error {:?}", expected_error); 103 | } else { 104 | panic!( 105 | "Did not match error {:?} with expected error in list:\n{:#?}", 106 | error, expected_errors 107 | ); 108 | } 109 | } 110 | } 111 | 112 | macro_rules! test_case { 113 | ( 114 | #[test] 115 | fn $test_case_name:ident(); 116 | 117 | input: { 118 | #[trace_logging_provider ( $( $attrs:tt )* )] 119 | $( $input:tt )* 120 | } 121 | 122 | expected_errors: [ 123 | $( $error:expr, )* 124 | ] 125 | 126 | ) => { 127 | #[test] 128 | fn $test_case_name() { 129 | let attrs = quote!{ $( $attrs )* }; 130 | 131 | let input = quote!{ $( $input )* }; 132 | let expected_errors = [ $( $error, )* ]; 133 | test_worker(attrs, input, &expected_errors); 134 | 135 | } 136 | } 137 | } 138 | 139 | test_case! { 140 | #[test] 141 | fn test_many_types(); 142 | input: { 143 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 144 | trait Events { 145 | fn arg_none(); 146 | 147 | fn arg_bool(a: bool); 148 | fn arg_u8(a: u8); 149 | fn arg_u16(a: u16); 150 | fn arg_u32(a: u32); 151 | fn arg_u64(a: u64); 152 | fn arg_i8(a: i8); 153 | fn arg_i16(a: i16); 154 | fn arg_i32(a: i32); 155 | fn arg_i64(a: i64); 156 | fn arg_f32(a: f32); 157 | fn arg_f64(a: f64); 158 | fn arg_usize(a: usize); 159 | fn arg_isize(a: isize); 160 | 161 | fn arg_slice_bool(a: &[bool]); 162 | fn arg_slice_u8(a: &[u8]); 163 | fn arg_slice_u16(a: &[u16]); 164 | fn arg_slice_u32(a: &[u32]); 165 | fn arg_slice_u64(a: &[u64]); 166 | fn arg_slice_i8(a: &[i8]); 167 | fn arg_slice_i16(a: &[i16]); 168 | fn arg_slice_i32(a: &[i32]); 169 | fn arg_slice_i64(a: &[i64]); 170 | fn arg_slice_f32(a: &[f32]); 171 | fn arg_slice_f64(a: &[f64]); 172 | fn arg_slice_usize(a: &[usize]); 173 | fn arg_slice_isize(a: &[isize]); 174 | 175 | fn arg_str(arg: &str); 176 | fn arg_u16cstr(arg: &U16CStr); 177 | fn arg_guid(arg: &GUID); 178 | fn arg_system_time(a: SystemTime); 179 | fn arg_filetime(a: FILETIME); 180 | 181 | #[event(level = "info")] 182 | fn arg_u8_at_info(a: u8); 183 | 184 | #[event(level = "warn")] 185 | fn arg_u8_at_warn(a: u8); 186 | 187 | #[event(level = "error")] 188 | fn arg_u8_at_error(a: u8); 189 | 190 | #[event(level = "verbose")] 191 | fn arg_u8_at_verbose(a: u8); 192 | 193 | #[event(level = 8)] 194 | fn arg_u8_at_level_8(a: u8); 195 | 196 | #[event(task = 100)] 197 | fn arg_with_task(a: u8); 198 | 199 | #[event(opcode = 10)] 200 | fn arg_with_opcode(a: u8); 201 | 202 | fn arg_u32_hex(#[event(output = "hex")] a: u32); 203 | 204 | fn arg_hresult(a: HRESULT); 205 | fn arg_ntstatus(a: NTSTATUS); 206 | fn arg_win32error(a: WIN32ERROR); 207 | } 208 | } 209 | expected_errors: [] 210 | } 211 | 212 | test_case! { 213 | #[test] 214 | fn test_unsupported_field_types(); 215 | input: { 216 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 217 | trait Events { 218 | fn event(a: ()); 219 | } 220 | } 221 | expected_errors: [ 222 | "This type is not supported for event parameters.", 223 | ] 224 | } 225 | 226 | test_case! { 227 | #[test] 228 | fn test_event_return_type(); 229 | input: { 230 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 231 | trait Events { 232 | fn event() -> String; 233 | } 234 | } 235 | expected_errors: [ 236 | "Event methods must not return data.", 237 | ] 238 | } 239 | 240 | test_case! { 241 | #[test] 242 | fn test_event_default_implementation(); 243 | input: { 244 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 245 | trait Events { 246 | fn event() { } 247 | } 248 | } 249 | expected_errors: [ 250 | "Event methods must not contain an implementation.", 251 | ] 252 | } 253 | 254 | test_case! { 255 | #[test] 256 | fn test_event_generic(); 257 | input: { 258 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 259 | trait Events { 260 | fn event(); 261 | } 262 | } 263 | expected_errors: [ 264 | "Generic event methods are not supported.", 265 | ] 266 | } 267 | 268 | test_case! { 269 | #[test] 270 | fn test_event_generic_lifetime(); 271 | input: { 272 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 273 | trait Events { 274 | fn event<'a>(); 275 | } 276 | } 277 | expected_errors: [ 278 | "Generic event methods are not supported.", 279 | ] 280 | } 281 | 282 | test_case! { 283 | #[test] 284 | fn test_wrong_self_ref(); 285 | input: { 286 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 287 | trait Events { 288 | fn event(&self); 289 | } 290 | } 291 | expected_errors: [ 292 | "Event methods should not provide any receiver arguments", 293 | ] 294 | } 295 | 296 | test_case! { 297 | #[test] 298 | fn test_wrong_self_mut(); 299 | input: { 300 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 301 | trait Events { 302 | fn event(&mut self); 303 | } 304 | } 305 | expected_errors: [ 306 | "Event methods should not provide any receiver arguments", 307 | ] 308 | } 309 | 310 | test_case! { 311 | #[test] 312 | fn test_wrong_self_move(); 313 | input: { 314 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 315 | trait Events { 316 | fn event(self); 317 | } 318 | } 319 | expected_errors: [ 320 | "Event methods should not provide any receiver arguments", 321 | ] 322 | } 323 | 324 | test_case! { 325 | #[test] 326 | fn test_bad_guid_literal(); 327 | input: { 328 | #[trace_logging_provider(guid = 0)] 329 | trait Events {} 330 | } 331 | expected_errors: [ 332 | "The attribute value is required to be a GUID in string form.", 333 | ] 334 | } 335 | 336 | test_case! { 337 | #[test] 338 | fn test_all_event_ids(); 339 | input: { 340 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 341 | trait Events { 342 | #[event(id = 1)] 343 | fn event_one(); 344 | 345 | #[event(id = 2)] 346 | fn event_two(); 347 | } 348 | } 349 | expected_errors: [ 350 | ] 351 | } 352 | 353 | test_case! { 354 | #[test] 355 | fn test_mixed_event_ids_fail(); 356 | input: { 357 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 358 | trait Events { 359 | #[event(id = 1)] 360 | fn event_one(); 361 | 362 | fn event_two(); 363 | } 364 | } 365 | 366 | expected_errors: [ 367 | "Event ids must be set for all events, or for none.", 368 | ] 369 | } 370 | 371 | test_case! { 372 | #[test] 373 | fn test_duplicate_event_ids_fail(); 374 | input: { 375 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 376 | trait Events { 377 | #[event(id = 1, id = 2)] 378 | fn event_one(); 379 | 380 | #[event(id = 3)] 381 | fn event_two(); 382 | } 383 | } 384 | 385 | expected_errors: [ 386 | "Event id has already been defined.", 387 | ] 388 | } 389 | 390 | test_case! { 391 | #[test] 392 | fn test_single_repeated_event_ids_fail(); 393 | input: { 394 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 395 | trait Events { 396 | #[event(id = 1)] 397 | fn event_one(); 398 | 399 | #[event(id = 1)] 400 | fn event_two(); 401 | } 402 | } 403 | 404 | expected_errors: [ 405 | "Event id 1 has already been defined on event_one.", 406 | ] 407 | } 408 | 409 | test_case! { 410 | #[test] 411 | fn test_multiple_repeated_event_ids_fail(); 412 | input: { 413 | #[trace_logging_provider(guid = "610259b8-9270-46f2-ad94-2f805721b287")] 414 | trait Events { 415 | #[event(id = 1)] 416 | fn event_one(); 417 | 418 | #[event(id = 2)] 419 | fn event_two(); 420 | 421 | #[event(id = 1)] 422 | fn event_three(); 423 | 424 | #[event(id = 2)] 425 | fn event_four(); 426 | } 427 | } 428 | 429 | expected_errors: [ 430 | "Event id 1 has already been defined on event_one.", 431 | "Event id 2 has already been defined on event_two.", 432 | ] 433 | } 434 | 435 | test_case! { 436 | #[test] 437 | fn test_bad_multiple_errors(); 438 | input: { 439 | #[trace_logging_provider(guid = "bad guid")] 440 | trait Events { 441 | fn bad_arg(a: ()); 442 | } 443 | } 444 | expected_errors: [ 445 | "The attribute value is required to be a valid GUID.", 446 | "This type is not supported for event parameters.", 447 | ] 448 | } 449 | 450 | test_case! { 451 | #[test] 452 | fn test_invalid_event_attributes(); 453 | input: { 454 | #[trace_logging_provider(guid = "00000000-0000-0000-0000-000000000001")] 455 | trait Events { 456 | #[event(bad_name = "bad_value")] 457 | fn event(); 458 | } 459 | } 460 | expected_errors: [ 461 | "Unrecognized attribute.", 462 | ] 463 | } 464 | 465 | test_case! { 466 | #[test] 467 | fn test_event_attributes_others_forbidden(); 468 | input: { 469 | #[trace_logging_provider(guid = "00000000-0000-0000-0000-000000000001")] 470 | trait Events { 471 | #[some_other_attribute] 472 | fn event(); 473 | } 474 | } 475 | expected_errors: [ 476 | "The only attributes allowed on event methods are #[doc] and #[event(...)] attributes.", 477 | ] 478 | } 479 | 480 | test_case! { 481 | #[test] 482 | fn wrong_item_kind(); 483 | input: { 484 | #[trace_logging_provider()] 485 | fn wrong_item_kind() {} 486 | } 487 | expected_errors: [ 488 | "The #[trace_logging_provider] attribute cannot be used with this kind of item.", 489 | ] 490 | } 491 | 492 | test_case! { 493 | #[test] 494 | fn multiple_errors_detected(); 495 | input: { 496 | #[trace_logging_provider()] 497 | trait Events 498 | { 499 | #[some_other_attribute] 500 | fn event_one(); 501 | 502 | #[event(id = 1)] 503 | fn event_two(); 504 | 505 | #[event(id = 2)] 506 | fn event_three(); 507 | 508 | #[event(id = 1)] 509 | fn event_four(); 510 | } 511 | 512 | } 513 | expected_errors: [ 514 | "The only attributes allowed on event methods are #[doc] and #[event(...)] attributes.", 515 | "Event ids must be set for all events, or for none.", 516 | "Event id 1 has already been defined on event_two.", 517 | ] 518 | } 519 | 520 | test_case! { 521 | #[test] 522 | fn provider_groups(); 523 | input: { 524 | #[trace_logging_provider( 525 | guid = "00000000-0000-0000-0000-000000000001", 526 | provider_group_guid = "00000000-0000-0000-0000-000000000002", 527 | )] 528 | trait Events 529 | { 530 | fn foo(); 531 | } 532 | } 533 | expected_errors: [ 534 | ] 535 | } 536 | 537 | test_case! { 538 | #[test] 539 | fn provider_attributes_multiple_provider_groups(); 540 | input: { 541 | #[trace_logging_provider( 542 | guid = "00000000-0000-0000-0000-000000000001", 543 | provider_group_guid = "00000000-0000-0000-0000-000000000002", 544 | provider_group_guid = "00000000-0000-0000-0000-000000000003", 545 | )] 546 | trait Events 547 | { 548 | fn foo(); 549 | } 550 | } 551 | expected_errors: [ 552 | "The 'provider_group_guid' attribute key cannot be specified more than once.", 553 | ] 554 | } 555 | 556 | test_case! { 557 | #[test] 558 | fn event_with_keyword(); 559 | input: { 560 | #[trace_logging_provider(guid = "00000000-0000-0000-0000-000000000001")] 561 | trait Events 562 | { 563 | #[event(keyword = 100)] 564 | fn foo(); 565 | } 566 | } 567 | expected_errors: [] 568 | } 569 | 570 | test_case! { 571 | #[test] 572 | fn error_multiple_keyword(); 573 | input: { 574 | #[trace_logging_provider(guid = "00000000-0000-0000-0000-000000000001")] 575 | trait Events 576 | { 577 | #[event(keyword = 100)] 578 | fn foo(); 579 | } 580 | } 581 | expected_errors: [] 582 | } 583 | 584 | use quote::quote; 585 | 586 | fn test_provider_attributes_error(input: TokenStream, expected_errors: &[&str]) { 587 | match syn::parse2::(input) { 588 | Ok(parsed) => { 589 | panic!("Expected parsing of input to fail. Output: {:?}", parsed); 590 | } 591 | Err(combined_error) => { 592 | check_errors(&combined_error, expected_errors); 593 | } 594 | } 595 | } 596 | 597 | #[test] 598 | fn provider_attributes_invalid_meta() { 599 | // We do not check the error details for this, because they are not under our control. 600 | // This is a failure to parse the comma-separated syn::Meta list. 601 | let result = syn::parse2::(quote! { bad bad bad }); 602 | assert!(result.is_err()); 603 | } 604 | 605 | #[test] 606 | fn provider_attributes_unrecognized_key() { 607 | test_provider_attributes_error( 608 | quote!(bad_name = "bad_value"), 609 | &["Unrecognized attribute key."], 610 | ); 611 | } 612 | 613 | #[test] 614 | fn provider_attributes_nil_guid() { 615 | test_provider_attributes_error( 616 | quote!(guid = "00000000-0000-0000-0000-000000000000"), 617 | &["The GUID cannot be the NIL (all-zeroes) GUID."], 618 | ); 619 | } 620 | 621 | #[test] 622 | fn provider_attributes_invalid_guid() { 623 | test_provider_attributes_error( 624 | quote!(guid = "xxx"), 625 | &["The attribute value is required to be a valid GUID."], 626 | ); 627 | } 628 | 629 | #[test] 630 | fn provider_attributes_dup_guid() { 631 | test_provider_attributes_error( 632 | quote!( 633 | guid = "610259b8-9270-46f2-ad94-2f805721b287", 634 | guid = "610259b8-9270-46f2-ad94-2f805721b287" 635 | ), 636 | &["The 'guid' attribute key cannot be specified more than once."], 637 | ); 638 | } 639 | 640 | #[test] 641 | fn provider_attributes_valid() { 642 | let result = syn::parse2::(quote! { 643 | guid = "610259b8-9270-46f2-ad94-2f805721b287" 644 | }); 645 | assert!(result.is_ok(), "Result: {:?}", result); 646 | } 647 | 648 | #[test] 649 | fn provider_attributes_valid_static() { 650 | let result = syn::parse2::(quote! { 651 | guid = "610259b8-9270-46f2-ad94-2f805721b287", static_mode 652 | }); 653 | assert!(result.is_ok(), "Result: {:?}", result); 654 | } 655 | 656 | fn check_errors(error: &Error, expected_errors: &[&str]) { 657 | let mut error_strings: Vec = error.into_iter().map(|e| format!("{}", e)).collect(); 658 | 659 | // Go through all the expected errors and ensure we find them in the output. 660 | for expected_error in expected_errors.iter() { 661 | let position = error_strings 662 | .iter() 663 | .position(|e| e.contains(expected_error)); 664 | if let Some(idx) = position { 665 | error_strings.remove(idx); 666 | // good 667 | } else { 668 | eprintln!("\nDid not find this error in list: {:?}", expected_error); 669 | eprintln!("Actual errors:"); 670 | for e in error_strings.iter() { 671 | eprintln!(" {:?}", e); 672 | } 673 | panic!("Error strings did not match."); 674 | } 675 | } 676 | 677 | for error_string in error_strings.iter() { 678 | panic!("Unexpected error: {}", error_string); 679 | } 680 | } 681 | 682 | #[test] 683 | fn test_etw_name_based_guid() { 684 | // the expected values below were generated by passing the provider name to 685 | // `[System.Diagnostics.Tracing.EventSource]::new("YourProviderName").Guid` 686 | use uuid::uuid; 687 | assert_eq!( 688 | uuid!("5fefebda-b28e-5a81-d371-cebf3d3ddb41"), 689 | etw_event_source_guid("YourProviderName") 690 | ); 691 | assert_eq!( 692 | uuid!("6f8eac67-f87f-598a-71a0-67e48d8c468d"), 693 | etw_event_source_guid("YourProviderNameYourProviderName") 694 | ); 695 | assert_eq!( 696 | uuid!("5b1bddda-c110-53c9-b030-17aea3bfabd9"), 697 | etw_event_source_guid("YourProviderNameYourProviderNameYourProviderNameYourProviderName") 698 | ); 699 | assert_eq!( 700 | uuid!("0cec4c9d-caa7-5d85-b3bd-c73577f03fd8"), 701 | etw_event_source_guid("Your.Provider.Name") 702 | ); 703 | assert_eq!( 704 | uuid!("32805bf5-e0cc-522a-5f56-20c3e359ed93"), 705 | etw_event_source_guid("Your.Provider.Name.Your.Provider.Name") 706 | ); 707 | assert_eq!( 708 | uuid!("ce5fa4ea-ab00-5402-8b76-9f76ac858fb5"), 709 | etw_event_source_guid("MyCompany.MyComponent") 710 | ); 711 | assert_eq!( 712 | uuid!("0d31f5cc-fb84-50db-a602-8c7bed9c5b8b"), 713 | etw_event_source_guid("ProviderWithAutogeneratedGuid") 714 | ); 715 | } 716 | -------------------------------------------------------------------------------- /win_etw_tracing/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Microsoft Corporation. All rights reserved. 2 | 3 | //! Subscriber for tracing events that emits Windows ETW tracelogging events. 4 | #![cfg(windows)] 5 | #![forbid(unsafe_code)] 6 | 7 | use bytes::BufMut; 8 | use core::fmt; 9 | use std::io::Write; 10 | use tracing::field::Field; 11 | use tracing::field::Visit; 12 | use tracing::span::Attributes; 13 | use tracing::span::Record; 14 | use tracing::Event; 15 | use tracing::Id; 16 | use tracing::Metadata; 17 | use tracing::Subscriber; 18 | use tracing_subscriber::layer::Context; 19 | use tracing_subscriber::registry::LookupSpan; 20 | use tracing_subscriber::Layer; 21 | use win_etw_metadata::InFlag; 22 | use win_etw_metadata::OutFlag; 23 | use win_etw_provider::Error; 24 | use win_etw_provider::EtwProvider; 25 | use win_etw_provider::EventDataDescriptor; 26 | use win_etw_provider::EventDescriptor; 27 | use win_etw_provider::EventOptions; 28 | use win_etw_provider::Provider; 29 | use win_etw_provider::GUID; 30 | 31 | /// An implementation for [`tracing_subscriber::Layer`] that emits tracelogging 32 | /// events. 33 | pub struct TracelogSubscriber { 34 | provider: EtwProvider, 35 | keyword_mask: u64, 36 | global_fields: EventData, 37 | trace_keyword: u64, 38 | } 39 | 40 | impl TracelogSubscriber { 41 | /// Creates a new subscriber with provider ID `id` and provider name `name`. 42 | pub fn new(id: impl Into, name: &str) -> Result { 43 | let mut provider_metadata = Vec::new(); 44 | provider_metadata.put_u16_le( 45 | (2 + name.len() + 1) 46 | .try_into() 47 | .expect("provider name too long"), 48 | ); 49 | provider_metadata.put_slice(name.as_bytes()); 50 | provider_metadata.put_u8(0); 51 | 52 | let mut provider = EtwProvider::new(&id.into())?; 53 | provider.register_provider_metadata(provider_metadata.as_slice())?; 54 | Ok(Self { 55 | provider, 56 | keyword_mask: !0_u64, 57 | global_fields: EventData { 58 | metadata: Vec::new(), 59 | data: Vec::new(), 60 | }, 61 | trace_keyword: 0, 62 | }) 63 | } 64 | 65 | // If some events are by default marked with telemetry keywords, this allows an opt out. 66 | pub fn enable_telemetry_events(&mut self, enabled: bool) { 67 | self.keyword_mask = if enabled { 68 | !0_u64 69 | } else { 70 | !(win_etw_metadata::MICROSOFT_KEYWORD_CRITICAL_DATA 71 | | win_etw_metadata::MICROSOFT_KEYWORD_MEASURES 72 | | win_etw_metadata::MICROSOFT_KEYWORD_TELEMETRY) 73 | }; 74 | } 75 | 76 | pub fn filter_keyword(&self, keyword: u64) -> u64 { 77 | keyword & self.keyword_mask 78 | } 79 | 80 | /// Global fields are automatically included in all events emitted by this 81 | /// layer. They can be set at the time of layer creation, or by using 82 | /// [`tracing_subscriber::reload`] to dynamically reconfigure a registered 83 | /// layer. Note that if the subscriber is registered as the [global 84 | /// default](tracing::dispatcher#setting-the-default-subscriber), thesee 85 | /// fields will be global to the entire process. 86 | /// 87 | /// # Example 88 | /// ``` 89 | /// # use win_etw_tracing::TracelogSubscriber; 90 | /// # use win_etw_provider::GUID; 91 | /// # let provider_guid = GUID { 92 | /// # data1: 0xe1c71d95, 93 | /// # data2: 0x7bbc, 94 | /// # data3: 0x5f48, 95 | /// # data4: [0xa9, 0x2b, 0x8a, 0xaa, 0x0b, 0x52, 0x91, 0x58], 96 | /// # }; 97 | /// let mut layer = TracelogSubscriber::new(provider_guid, "provider_name").unwrap(); 98 | /// let globals = vec![("field name", "my value")]; 99 | /// layer.set_global_fields(&globals); 100 | /// ``` 101 | pub fn set_global_fields(&mut self, fields: &[(&str, &str)]) { 102 | self.global_fields.metadata.clear(); 103 | self.global_fields.data.clear(); 104 | for &(name, value) in fields.iter() { 105 | self.global_fields.record_global(name, value); 106 | } 107 | } 108 | 109 | /// Sets the keyword to use for events logged at [`tracing::Level::TRACE`] 110 | /// level. 111 | /// 112 | /// Because ETW only provides one level below [`win_etw_metadata::Level::INFO`], 113 | /// both [`tracing::Level::DEBUG`] and [`tracing::Level::TRACE`] events are 114 | /// mapped to [`win_etw_metadata::Level::VERBOSE`]. This method allows 115 | /// distinguishing between the two levels by assigning a specific keyword 116 | /// used only for [`tracing::Level::TRACE`] events. 117 | /// 118 | /// By default, this is set to `0`, meaning no keyword is applied. 119 | pub fn set_trace_keyword(&mut self, keyword: u64) { 120 | self.trace_keyword = keyword; 121 | } 122 | } 123 | 124 | impl TracelogSubscriber { 125 | fn write_event( 126 | &self, 127 | opcode: u8, 128 | options: &EventOptions, 129 | write_target: bool, 130 | meta: &Metadata<'_>, 131 | write_name: impl FnOnce(&mut Vec), 132 | record: impl FnOnce(&mut dyn Visit), 133 | ) { 134 | let mut keyword = 0; 135 | let level = match *meta.level() { 136 | tracing::Level::ERROR => win_etw_metadata::Level::ERROR, 137 | tracing::Level::WARN => win_etw_metadata::Level::WARN, 138 | tracing::Level::INFO => win_etw_metadata::Level::INFO, 139 | tracing::Level::DEBUG => win_etw_metadata::Level::VERBOSE, 140 | tracing::Level::TRACE => { 141 | keyword = self.trace_keyword; 142 | win_etw_metadata::Level::VERBOSE 143 | } 144 | }; 145 | 146 | let event_descriptor = EventDescriptor { 147 | id: 0, 148 | version: 0, 149 | channel: 11, // this value tells older versions of ETW that this is a tracelogging event 150 | level, 151 | opcode, 152 | task: 0, 153 | keyword: self.filter_keyword(keyword), 154 | }; 155 | 156 | if !self.provider.is_event_enabled(&event_descriptor) { 157 | return; 158 | } 159 | 160 | let mut event_data = EventData { 161 | metadata: Vec::new(), 162 | data: Vec::new(), 163 | }; 164 | event_data.metadata.put_u16_le(0); // reserve space for the size 165 | event_data.metadata.put_u8(0); // no extensions 166 | write_name(&mut event_data.metadata); 167 | event_data.metadata.put_u8(0); // null terminator 168 | 169 | let target_len = if write_target { 170 | // Target field 171 | event_data.metadata.put_slice(b"target\0"); 172 | event_data 173 | .metadata 174 | .put_u8((InFlag::COUNTED_ANSI_STRING | InFlag::CHAIN_FLAG).bits()); 175 | event_data.metadata.put_u8(OutFlag::UTF8.bits()); 176 | meta.target().len() as u16 177 | } else { 178 | 0 179 | }; 180 | 181 | event_data 182 | .metadata 183 | .put_slice(self.global_fields.metadata.as_slice()); 184 | event_data 185 | .data 186 | .put_slice(self.global_fields.data.as_slice()); 187 | record(&mut event_data); 188 | 189 | // Update the length. 190 | let event_metadata_len = event_data.metadata.len() as u16; 191 | (&mut event_data.metadata[0..2]).put_u16_le(event_metadata_len); 192 | 193 | // N.B. Since we pre-registered the provider information when creating 194 | // the provider, there is no need to log it again here. 195 | let (data_descriptors_with_target, data_descriptors_without_target); 196 | let data_descriptors = if write_target { 197 | data_descriptors_with_target = [ 198 | EventDataDescriptor::for_event_metadata(event_data.metadata.as_slice()), 199 | EventDataDescriptor::from(&target_len), 200 | EventDataDescriptor::from(meta.target()), 201 | EventDataDescriptor::for_bytes(&event_data.data), 202 | ]; 203 | &data_descriptors_with_target[..] 204 | } else { 205 | data_descriptors_without_target = [ 206 | EventDataDescriptor::for_event_metadata(event_data.metadata.as_slice()), 207 | EventDataDescriptor::for_bytes(&event_data.data), 208 | ]; 209 | &data_descriptors_without_target[..] 210 | }; 211 | self.provider 212 | .write(Some(options), &event_descriptor, data_descriptors); 213 | } 214 | } 215 | 216 | #[derive(Debug, Clone, Default)] 217 | struct ActivityId(GUID); 218 | 219 | impl ActivityId { 220 | #[allow(dead_code)] 221 | fn new() -> Result { 222 | Ok(Self(win_etw_provider::new_activity_id()?)) 223 | } 224 | 225 | fn from_current_thread() -> Result { 226 | Ok(Self(win_etw_provider::get_current_thread_activity_id()?)) 227 | } 228 | } 229 | 230 | struct ActivityIdVisitor { 231 | value: Option, 232 | } 233 | 234 | impl Visit for ActivityIdVisitor { 235 | fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { 236 | if field.name() == "activity_id" { 237 | // Parse the GUID debug string 238 | let debug_str = format!("{:?}", value); 239 | self.value = GUID::try_from(debug_str.as_str()).ok(); 240 | } 241 | } 242 | } 243 | 244 | /// Extracts the "activity_id" field from span attributes. 245 | /// Returns None if the field is missing. 246 | fn extract_activity_id_attr(attrs: &Attributes<'_>) -> Option { 247 | let mut visitor = ActivityIdVisitor { value: None }; 248 | attrs.record(&mut visitor); 249 | visitor.value 250 | } 251 | 252 | const WINEVENT_OPCODE_INFO: u8 = 0; 253 | const WINEVENT_OPCODE_START: u8 = 1; 254 | const WINEVENT_OPCODE_STOP: u8 = 2; 255 | 256 | impl Layer for TracelogSubscriber 257 | where 258 | S: for<'a> LookupSpan<'a>, 259 | { 260 | fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { 261 | // Extract "activity_id" from attributes 262 | let activity_id = extract_activity_id_attr(attrs) 263 | .map(ActivityId) 264 | .unwrap_or_else(|| { 265 | // If not provided, get the current thread's activity ID 266 | ActivityId::from_current_thread().unwrap_or_default() 267 | }); 268 | 269 | let related_activity_id = { 270 | if attrs.is_contextual() { 271 | ctx.current_span().id().cloned() 272 | } else { 273 | attrs.parent().cloned() 274 | } 275 | .and_then(|id| { 276 | ctx.span(&id) 277 | .unwrap() 278 | .extensions() 279 | .get::() 280 | .cloned() 281 | }) 282 | .map(|x| x.0) 283 | }; 284 | 285 | // Store the activity ID on the span to look up later. 286 | ctx.span(id) 287 | .unwrap() 288 | .extensions_mut() 289 | .insert(activity_id.clone()); 290 | 291 | self.write_event( 292 | WINEVENT_OPCODE_START, 293 | &EventOptions { 294 | activity_id: Some(activity_id.0), 295 | related_activity_id, 296 | ..Default::default() 297 | }, 298 | true, 299 | attrs.metadata(), 300 | |metadata| metadata.extend(attrs.metadata().name().as_bytes()), 301 | |visit| attrs.record(visit), 302 | ); 303 | } 304 | 305 | fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) { 306 | // Defer the recorded value until on_close is called. Ideally we would 307 | // just log the additional data as another event and the data would be 308 | // aggregated with the rest of the activity's data, but WPA and other 309 | // analysis tools don't actually handle this. 310 | let span = ctx.span(id).unwrap(); 311 | let mut extensions = span.extensions_mut(); 312 | let deferred = if let Some(deferred) = extensions.get_mut::() { 313 | deferred 314 | } else { 315 | extensions.insert(DeferredValues::default()); 316 | extensions.get_mut().unwrap() 317 | }; 318 | values.record(deferred); 319 | } 320 | 321 | fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { 322 | #[cfg(feature = "tracing-log")] 323 | let normalized_meta = tracing_log::NormalizeEvent::normalized_metadata(event); 324 | #[cfg(feature = "tracing-log")] 325 | let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); 326 | #[cfg(not(feature = "tracing-log"))] 327 | let meta = event.metadata(); 328 | 329 | let activity_id = ctx 330 | .event_span(event) 331 | .and_then(|span| span.extensions().get::().cloned().map(|x| x.0)); 332 | 333 | self.write_event( 334 | WINEVENT_OPCODE_INFO, 335 | &EventOptions { 336 | activity_id, 337 | ..Default::default() 338 | }, 339 | true, 340 | meta, 341 | // Write the message as the event name. This will not be ideal for 342 | // events with dynamic names, but it should work well for structured 343 | // events, and it follows the precedent set by the tracing-opentelemetry 344 | // crate. 345 | |metadata| event.record(&mut EventName(metadata)), 346 | |visit| event.record(visit), 347 | ); 348 | } 349 | 350 | fn on_close(&self, id: Id, ctx: Context<'_, S>) { 351 | let span = ctx.span(&id).unwrap(); 352 | let extensions = span.extensions(); 353 | let ActivityId(activity_id) = extensions.get::().cloned().unwrap(); 354 | let values = extensions.get::(); 355 | self.write_event( 356 | WINEVENT_OPCODE_STOP, 357 | &EventOptions { 358 | activity_id: Some(activity_id), 359 | ..Default::default() 360 | }, 361 | false, 362 | span.metadata(), 363 | |metadata| metadata.extend(span.metadata().name().as_bytes()), 364 | |visit| { 365 | if let Some(values) = values { 366 | values.record(visit) 367 | }; 368 | }, 369 | ); 370 | } 371 | } 372 | 373 | /// Collection of deferred values to log when the span is closed. 374 | #[derive(Default)] 375 | struct DeferredValues { 376 | values: Vec<(Field, DeferredValue)>, 377 | } 378 | 379 | impl DeferredValues { 380 | fn update(&mut self, field: &Field, value: DeferredValue) { 381 | for (f, v) in &mut self.values { 382 | if f == field { 383 | *v = value; 384 | return; 385 | } 386 | } 387 | self.values.push((field.clone(), value)); 388 | } 389 | 390 | fn record(&self, visit: &mut dyn Visit) { 391 | for (field, v) in &self.values { 392 | match v { 393 | DeferredValue::Unsigned(v) => visit.record_u64(field, *v), 394 | DeferredValue::Signed(v) => visit.record_i64(field, *v), 395 | DeferredValue::Boolean(v) => visit.record_bool(field, *v), 396 | DeferredValue::String(v) => visit.record_str(field, v), 397 | } 398 | } 399 | } 400 | } 401 | 402 | impl Visit for DeferredValues { 403 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { 404 | self.update(field, DeferredValue::String(format!("{value:?}"))); 405 | } 406 | 407 | fn record_i64(&mut self, field: &Field, value: i64) { 408 | self.update(field, DeferredValue::Signed(value)); 409 | } 410 | 411 | fn record_u64(&mut self, field: &Field, value: u64) { 412 | self.update(field, DeferredValue::Unsigned(value)); 413 | } 414 | 415 | fn record_bool(&mut self, field: &Field, value: bool) { 416 | self.update(field, DeferredValue::Boolean(value)); 417 | } 418 | 419 | fn record_str(&mut self, field: &Field, value: &str) { 420 | self.update(field, DeferredValue::String(value.to_string())); 421 | } 422 | } 423 | 424 | enum DeferredValue { 425 | Unsigned(u64), 426 | Signed(i64), 427 | Boolean(bool), 428 | String(String), 429 | } 430 | 431 | struct EventName<'a>(&'a mut Vec); 432 | 433 | impl Visit for EventName<'_> { 434 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { 435 | if field.name() == "message" { 436 | let _ = write!(self.0, "{value:?}"); 437 | } 438 | } 439 | } 440 | 441 | struct EventData { 442 | metadata: Vec, 443 | data: Vec, 444 | } 445 | 446 | impl EventData { 447 | fn write_name(&mut self, name: &str) -> bool { 448 | // Skip the message (used as the event name) as well as any log crate 449 | // metadata (already consumed). 450 | if name == "message" || (cfg!(feature = "tracing-log") && name.starts_with("log.")) { 451 | return false; 452 | } 453 | self.metadata.put_slice(name.as_bytes()); 454 | self.metadata.put_u8(0); // null terminator 455 | true 456 | } 457 | 458 | fn record_global(&mut self, name: &str, value: &str) { 459 | if self.write_name(name) { 460 | self.metadata 461 | .put_u8((InFlag::ANSI_STRING | InFlag::CHAIN_FLAG).bits()); 462 | self.metadata.put_u8(OutFlag::UTF8.bits()); 463 | self.data.extend(value.as_bytes()); 464 | self.data.put_u8(0); // null terminator 465 | } 466 | } 467 | } 468 | 469 | impl Visit for EventData { 470 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { 471 | if self.write_name(field.name()) { 472 | self.metadata 473 | .put_u8((InFlag::ANSI_STRING | InFlag::CHAIN_FLAG).bits()); 474 | self.metadata.put_u8(OutFlag::UTF8.bits()); 475 | let _ = write!(&mut self.data, "{value:?}\0"); 476 | } 477 | } 478 | 479 | fn record_i64(&mut self, field: &Field, value: i64) { 480 | if self.write_name(field.name()) { 481 | self.metadata.put_u8(InFlag::INT64.bits()); 482 | self.data.put_i64_le(value); 483 | } 484 | } 485 | 486 | fn record_u64(&mut self, field: &Field, value: u64) { 487 | if self.write_name(field.name()) { 488 | self.metadata 489 | .put_u8((InFlag::UINT64 | InFlag::CHAIN_FLAG).bits()); 490 | self.metadata.put_u8(OutFlag::HEX.bits()); 491 | self.data.put_u64_le(value); 492 | } 493 | } 494 | 495 | fn record_bool(&mut self, field: &Field, value: bool) { 496 | if self.write_name(field.name()) { 497 | self.metadata.put_u8(InFlag::UINT8.bits()); 498 | self.metadata.put_u8(OutFlag::BOOLEAN.bits()); 499 | self.data.put_u8(value.into()); 500 | } 501 | } 502 | 503 | fn record_str(&mut self, field: &Field, value: &str) { 504 | if self.write_name(field.name()) { 505 | self.metadata 506 | .put_u8((InFlag::ANSI_STRING | InFlag::CHAIN_FLAG).bits()); 507 | self.metadata.put_u8(OutFlag::UTF8.bits()); 508 | self.data.extend(value.as_bytes()); 509 | self.data.put_u8(0); // null terminator 510 | } 511 | } 512 | 513 | fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { 514 | if self.write_name(field.name()) { 515 | self.metadata 516 | .put_u8((InFlag::ANSI_STRING | InFlag::CHAIN_FLAG).bits()); 517 | self.metadata.put_u8(OutFlag::UTF8.bits()); 518 | let _ = write!(&mut self.data, "{value}"); 519 | let mut source = value.source(); 520 | while let Some(v) = source.take() { 521 | let _ = write!(&mut self.data, ": {v}"); 522 | source = v.source(); 523 | } 524 | self.data.put_u8(0); // null terminator 525 | } 526 | } 527 | } 528 | 529 | #[cfg(test)] 530 | mod tests { 531 | use crate::TracelogSubscriber; 532 | use tracing_subscriber::prelude::*; 533 | use tracing_subscriber::reload; 534 | use tracing_subscriber::Registry; 535 | use win_etw_provider::GUID; 536 | 537 | static PROVIDER_GUID: GUID = GUID { 538 | data1: 0xe1c71d95, 539 | data2: 0x7bbc, 540 | data3: 0x5f48, 541 | data4: [0xa9, 0x2b, 0x8a, 0xaa, 0x0b, 0x52, 0x91, 0x58], 542 | }; 543 | 544 | static PROVIDER_NAME: &str = "rust-test-provider"; 545 | 546 | #[test] 547 | fn basic() { 548 | let layer = TracelogSubscriber::new(PROVIDER_GUID.clone(), PROVIDER_NAME).unwrap(); 549 | let _x = Registry::default().with(layer).set_default(); 550 | tracing::info!(foo = 123, bar = 456, "hi {baz}", baz = "what"); 551 | tracing::error!(foo = true, bar = ?PROVIDER_GUID); 552 | let err = anyhow::anyhow!("failed") 553 | .context("really failed") 554 | .context("this thing failed"); 555 | tracing::error!(error = &*err as &dyn std::error::Error, "disaster"); 556 | } 557 | 558 | #[test] 559 | fn span() { 560 | let layer = TracelogSubscriber::new(PROVIDER_GUID.clone(), PROVIDER_NAME).unwrap(); 561 | let _x = Registry::default().with(layer).set_default(); 562 | tracing::info_span!("geo", bar = 456).in_scope(|| { 563 | let span = tracing::info_span!("dude", baz = 789, later = tracing::field::Empty); 564 | span.in_scope(|| { 565 | tracing::info!("test"); 566 | span.record("later", true); 567 | span.record("later", "wait no it's a string now"); 568 | }); 569 | }); 570 | } 571 | 572 | #[test] 573 | fn global() { 574 | let (layer, reload_handle) = reload::Layer::new( 575 | TracelogSubscriber::new(PROVIDER_GUID.clone(), PROVIDER_NAME).unwrap(), 576 | ); 577 | let _x = Registry::default().with(layer).set_default(); 578 | tracing::info!(a_field = 123, "test globals"); 579 | let global = vec![("global", "some value")]; 580 | reload_handle 581 | .modify(|layer| layer.set_global_fields(&global)) 582 | .unwrap(); 583 | tracing::info!(a_field = 456, "test globals modify"); 584 | let _s = tracing::info_span!("span with globals", span_field = "abc").entered(); 585 | let global = vec![("global", "new value"), ("global2", "value")]; 586 | reload_handle 587 | .modify(|layer| layer.set_global_fields(&global)) 588 | .unwrap(); 589 | tracing::info!(a_field = 789, "test globals modify again"); 590 | } 591 | } 592 | -------------------------------------------------------------------------------- /win_etw_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides the `#[trace_logging_provider]` macro, which allows you to define a 2 | //! [Trace Logging Provider](https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing#providers) 3 | //! for use with the [Event Tracing for Windows (ETW)](https://docs.microsoft.com/en-us/windows/win32/etw/event-tracing-portal) 4 | //! framework. 5 | //! 6 | //! This macro is intended for use only when targeting Windows. When targeting other platforms, 7 | //! this macro will still work, but will generate code that does nothing. 8 | //! 9 | //! This framework allows applications to log schematized events, rather than textual strings. 10 | //! ETW analysis tools can reliably identify fields within your events, and treat them as 11 | //! strongly-typed data, rather than text strings. 12 | //! 13 | //! ## How to create and use an event provider 14 | //! 15 | //! In ETW, an _event provider_ is a software object that generates events. _Event controllers_ 16 | //! set up event logging sessions, and _event consumers_ read and interpret event data. This crate 17 | //! focuses on enabling applications to create _event providers_. 18 | //! 19 | //! ## Add crate dependencies 20 | //! Add these dependencies to your `Cargo.toml` file: 21 | //! 22 | //! ```text 23 | //! [dependencies] 24 | //! win_etw_macros = "0.1.*" 25 | //! win_etw_provider = "0.1.*" 26 | //! ``` 27 | //! 28 | //! `win_etw_macros` contains the procedural macro that generates eventing code. 29 | //! `win_etw_provider` contains library code that is called by the code that is generated by 30 | //! `win_etw_macros`. 31 | //! 32 | //! ### Define the event provider and its events 33 | //! 34 | //! Add a trait definition to your source code and annotate it with the `#[trace_logging_provider]` 35 | //! macro. The `#[trace_logging_provider]` macro consumes the trait definition and produces a `struct` 36 | //! definition with the same name and the same method signatures. (The trait is _not_ available for 37 | //! use as an ordinary trait.): 38 | //! 39 | //! ```rust,ignore 40 | //! #[trace_logging_provider] 41 | //! pub trait MyAppEvents {} 42 | //! ``` 43 | //! 44 | //! [Each event provider _must_ have a unique name _and_ GUID.](https://learn.microsoft.com/en-us/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-tracelogging_define_provider#provider-name-and-id) 45 | //! ETW uses this GUID to identify events that are generated by your provider. Windows contains many 46 | //! event providers, so it is important to be able to select only the events generated by your 47 | //! application. This GUID is also used internally by ETW to identify event metadata (field types), so 48 | //! it is important that your GUID be unique. Otherwise, events from conflicting sources that use the 49 | //! same GUID may be incorrectly interpreted. 50 | //! 51 | //! #### Specify your provider name 52 | //! 53 | //! Unless overridden, `#[trace_logging_provider]` uses the name of your trait definition as the name of ETW provider ("MyAppEvents" in the example aboved). 54 | //! To specify a different name, specify the name with `#[trace_logging_provider(name = "MyCompany.MyComponent")]`. 55 | //! 56 | //! #### Generate a GUID for your event provider 57 | //! 58 | //! The `#[trace_logging_provider]` macro will generate a .NET `EventSource`-compatible name-based GUID if you do not 59 | //! specify a `guid` parameter. The generated GUID is identical to the one generated by the following PowerShell code: 60 | //! `[System.Diagnostics.Tracing.EventSource]::new("MyCompany.MyComponent").Guid`. The GUID is accessible from your Rust code via the associated constant named `PROVIDER_GUID` (e.g., `MyAppEvents::PROVIDER_GUID`). 61 | //! 62 | //! If you are not interested in using a name-based GUID, you can generate a GUID using a tool like 63 | //! `uuidgen` (available from Visual Studio command line, or an Ubuntu shell) and specify it with 64 | //! `#[trace_logging_provider(guid = "... your guid here ...")]`. 65 | //! 66 | //! #### Add events to your provider 67 | //! 68 | //! In the trait definition, add method signatures. Each method signature defines an _event type_. 69 | //! The parameters of each method define the fields of the event type. Only a limited set of field 70 | //! types are supported (enumerated below). 71 | //! 72 | //! ```rust,ignore 73 | //! use win_etw_macros::trace_logging_provider; 74 | //! #[trace_logging_provider(name = "MyCompany.MyComponent")] 75 | //! pub trait MyAppEvents { 76 | //! fn http_request(client_address: &SockAddr, is_https: bool, status_code: u32, status: &str); 77 | //! fn database_connection_created(connection_id: u64, server: &str); 78 | //! fn database_connection_closed(connection_id: u64); 79 | //! // ... 80 | //! } 81 | //! ``` 82 | //! 83 | //! ## Create an instance of the event provider 84 | //! At initialization time (in your `fn main()`, etc.), create an instance of the event provider: 85 | //! 86 | //! ```rust,ignore 87 | //! let my_app_events = MyAppEvents::new(); 88 | //! ``` 89 | //! 90 | //! Your application should only create a single instance of each event provider, per process. 91 | //! That is, you should create a single instance of your event provider and share it across your 92 | //! process. Typically, an instance is stored in static variable, using a lazy / atomic assignment. 93 | //! There are many crates and types which can support this usage pattern. 94 | //! 95 | //! ## Call event methods to report events 96 | //! To report an event, call one of the methods defined on the event provider. The method will 97 | //! call into ETW to report the event, but there is no guarantee that the event is stored or 98 | //! forwarded; events can be dropped if event buffer resources are scarce. 99 | //! 100 | //! ```rust,ignore 101 | //! my_app_events.client_connected(&"192.168.0.42:6667".parse(), false, 100, "OK"); 102 | //! ``` 103 | //! 104 | //! # Supported field types 105 | //! Only a limited set of field types are supported. 106 | //! 107 | //! * Integer primitives up to 64 bits: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64` 108 | //! * Floating point primitives: `f32`, `f64` 109 | //! * Architecture-dependent sizes: `usize`, `isize`. 110 | //! * Boolean: `bool` 111 | //! * Slices of all of the supported primitives, except for bool: `&[u8]`, `&[u16]`, etc. 112 | //! `&[bool]` is not supported because `bool` does not have a guaranteed stable representation. 113 | //! * Windows `[FILETIME](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime)`. 114 | //! The type must be declared _exactly_ as `FILETIME`; type aliases or fully-qualified paths 115 | //! (such as `windows_sys::Win32::Foundation::FILETIME`) _will not work_. The parameter type in the 116 | //! generated code will be `win_etw_provider::FILETIME`, which is a newtype over `u64`. 117 | //! * `std::time::SystemTime` is supported, but it must be declared _exactly_ as `SystemTime`; 118 | //! type aliases or fully-qualified paths (such as `std::time::SystemTime`) _will not work_. 119 | //! * `SockAddr`, `SockAddrV4`, and `SockAddrV6` are supported. They must be declared exactly as 120 | //! shown, not using fully-qualified names or type aliases. 121 | //! 122 | //! # Provider groups 123 | //! 124 | //! When creating an ETW provider, you can place ETW providers into _provider groups_. A provider 125 | //! group can be enabled or disabled as a unit. To do so, specify the GUID of the provider group 126 | //! when declaring the ETW provider. For example: 127 | //! 128 | //! ```no_test 129 | //! [trace_logging_provider( 130 | //! guid = "...", // GUID of this provider 131 | //! provider_group_guid = "..." // GUID of the provider group that this provider belongs to 132 | //! )] 133 | //! pub trait MyEvents { 134 | //! // ... 135 | //! } 136 | //! ``` 137 | //! 138 | //! ## The `#[event]` attribute 139 | //! 140 | //! The `#[event]` atttribute allows you to control various aspects of each event type. 141 | //! It is not necessary to use the `#[event]` attribute; if it is not specified, then 142 | //! reasonable defaults will be chosen. You can use the `#[event]` attribute to control 143 | //! these aspects of each event type: 144 | //! 145 | //! * `#[event(id = NN)]` - Specifies the event ID. All event types declared on a specific 146 | //! event provider must have unique event IDs. See [EVENT_DESCRIPTOR]::Id. 147 | //! * `#[event(level = NN)]` or `#[event(level = "...")]` - Specifies the event level. 148 | //! See [EVENT_DESCRIPTOR]::Level. 149 | //! This can either be a numeric value, or one of the following literal strings: 150 | //! `"critical"`, `"error"`, `"warn"`, `"info"`, `"verbose"`. 151 | //! * `#[event(opcode = NN)]` - Specifies the [EVENT_DESCRIPTOR]::Opcode field. 152 | //! * `#[event(task = NN)` - Specifies the [EVENT_DESCRIPTOR]::Task field. 153 | //! * `#[event(keyword = NN)` - Specifies the [EVENT_DESCRIPTOR]::Keyword field. 154 | //! 155 | //! [EVENT_DESCRIPTOR]: https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_descriptor 156 | //! 157 | //! You can use a single `#[event]` attribute with multiple values, or you can use 158 | //! multiple `#[event]` attributes. 159 | //! 160 | //! # How to capture and view events 161 | //! 162 | //! There are a variety of tools which can be used to capture and view ETW events. 163 | //! The simplest tool is the `TraceView` tool from the Windows SDK. Typically it is installed 164 | //! at this path: `C:\Program Files (x86)\Windows Kits\10\bin\10.0..0\x64\traceview.exe`, 165 | //! where `` is the release number of the Windows SDK. 166 | //! 167 | //! Run `TraceView`, then select "File", then "Create New Log Session". Select "Manually Entered 168 | //! GUID or Hashed Name" and enter the GUID that you have assigned to your event provider. Click OK. 169 | //! The next dialog will prompt you to choose a source of WPP format information; select Auto 170 | //! and click OK. 171 | //! 172 | //! At this point, `TraceView` should be capturing events (for your assigned GUID) and displaying 173 | //! them in real time, regardless of which process reported the events. 174 | //! 175 | //! These tools can also be used to capture ETW events: 176 | //! * [Windows Performance Recorder](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) 177 | //! This tool is intended for capturing system-wide event streams. It is not useful for capturing 178 | //! events for a specific event provider. 179 | //! * [logman](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/logman) 180 | //! is a command-line tool for managing events. 181 | //! * [Tracelog](https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/tracelog) 182 | //! 183 | //! There are other tools, such as the Windows Performance Recorder, which can capture ETW events. 184 | //! 185 | //! # References 186 | //! * [Event Tracing for Windows (ETW) Simplified](https://support.microsoft.com/en-us/help/2593157/event-tracing-for-windows-etw-simplified) 187 | //! * [TraceLogging for Event Tracing for Windows (ETW)](https://docs.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal) 188 | //! * [Record and View TraceLogging Events](https://docs.microsoft.com/en-us/windows/win32/tracelogging/tracelogging-record-and-display-tracelogging-events) 189 | //! * [TraceLoggingOptionGroup](https://docs.microsoft.com/en-us/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-traceloggingoptiongroup) 190 | //! * [Provider Traits](https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits) 191 | //! * [TRACELOGGING_DEFINE_PROVIDER macro](https://learn.microsoft.com/en-us/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-tracelogging_define_provider) 192 | 193 | // https://doc.rust-lang.org/reference/procedural-macros.html 194 | 195 | #![deny(missing_docs)] 196 | #![allow(clippy::too_many_arguments)] 197 | #![allow(clippy::cognitive_complexity)] 198 | #![allow(clippy::single_match)] 199 | #![allow(clippy::upper_case_acronyms)] 200 | #![allow(clippy::vec_init_then_push)] 201 | 202 | extern crate proc_macro; 203 | 204 | mod well_known_types; 205 | 206 | use proc_macro2::TokenStream; 207 | use quote::{quote, quote_spanned}; 208 | use std::{collections::HashMap, iter::Extend}; 209 | use syn::spanned::Spanned; 210 | use syn::{parse_quote, Error, Expr, ExprLit, FnArg, Ident, Lit, Token}; 211 | use uuid::Uuid; 212 | use well_known_types::{WellKnownType, WellKnownTypes}; 213 | 214 | #[cfg(test)] 215 | mod tests; 216 | 217 | /// Allows you to create ETW Trace Logging Providers. See the module docs for more detailed 218 | /// instructions for this macro. 219 | #[proc_macro_attribute] 220 | pub fn trace_logging_provider( 221 | attr: proc_macro::TokenStream, 222 | input: proc_macro::TokenStream, 223 | ) -> proc_macro::TokenStream { 224 | // let logging_trait = parse_macro_input!(input as syn::ItemTrait); 225 | let output = trace_logging_events_core(attr.into(), input.into()); 226 | output.into() 227 | } 228 | 229 | fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> TokenStream { 230 | let mut errors: Vec = Vec::new(); 231 | 232 | let logging_trait: syn::ItemTrait = match syn::parse2(item_tokens) { 233 | Err(e) => { 234 | return e.to_compile_error(); 235 | } 236 | Ok(syn::Item::Trait(t)) => t, 237 | Ok(syn::Item::Mod(m)) => { 238 | return Error::new_spanned(&m, "Modules are not yet supported, but thanks for asking.") 239 | .to_compile_error(); 240 | } 241 | Ok(unrecognized) => { 242 | return Error::new_spanned( 243 | &unrecognized, 244 | "The #[trace_logging_provider] attribute cannot be used with this kind of item.", 245 | ) 246 | .to_compile_error(); 247 | } 248 | }; 249 | 250 | let provider_attrs: ProviderAttributes = match syn::parse2(attr) { 251 | Ok(p) => p, 252 | Err(e) => { 253 | errors.push(e); 254 | ProviderAttributes::default() 255 | } 256 | }; 257 | 258 | // provider_ident is the identifier used in Rust source code for the generated provider type. 259 | let provider_ident = &logging_trait.ident; 260 | let provider_ident_string = provider_ident.to_string(); 261 | 262 | let wk = WellKnownTypes::new(); 263 | 264 | let mut output = TokenStream::new(); 265 | 266 | let provider_metadata_ident = Ident::new( 267 | &format!("{provider_ident_string}_PROVIDER_METADATA"), 268 | provider_ident.span(), 269 | ); 270 | 271 | // Create the provider metadata. 272 | // provider_ident is the identifier used in Rust source code for the generated code. 273 | // When writing the provider metadata, we allow the user to specify a different name to ETW. 274 | let provider_name = provider_attrs 275 | .provider_name 276 | .as_ref() 277 | .unwrap_or(&provider_ident_string); 278 | output.extend(create_provider_metadata( 279 | provider_name, 280 | &provider_metadata_ident, 281 | )); 282 | 283 | // Definitions that go inside the "impl MyProvider { ... }" block. 284 | let mut provider_impl_items = TokenStream::new(); 285 | 286 | // To make this simple, we either require all events to be labeled 287 | // with an event id or autogenerated. 288 | let mut event_ids_auto_generated = true; 289 | let mut event_id_mappings = HashMap::new(); 290 | 291 | for (method_index, method) in logging_trait 292 | .items 293 | .iter() 294 | .filter_map(|item| { 295 | if let syn::TraitItem::Fn(m) = item { 296 | Some(m) 297 | } else { 298 | None 299 | } 300 | }) 301 | .enumerate() 302 | { 303 | let event_index = method_index as u16; 304 | 305 | // Check requirements for the method signature. If the requirements are not met, we 306 | // emit an error but keep going. This allows us to report as many errors as possible in 307 | // each build, rather than having errors "unlocked" one by one. 308 | if method.sig.asyncness.is_some() { 309 | errors.push(Error::new_spanned( 310 | method, 311 | "Async event methods are not supported.", 312 | )); 313 | } 314 | if method.sig.unsafety.is_some() { 315 | errors.push(Error::new_spanned( 316 | method, 317 | "Event methods should not be marked unsafe.", 318 | )); 319 | } 320 | if !method.sig.generics.params.is_empty() { 321 | errors.push(Error::new_spanned( 322 | method, 323 | "Generic event methods are not supported.", 324 | )); 325 | } 326 | match &method.sig.output { 327 | syn::ReturnType::Default => {} 328 | _ => { 329 | errors.push(Error::new_spanned( 330 | method, 331 | "Event methods must not return data.", 332 | )); 333 | } 334 | } 335 | if let Some(block) = method.default.as_ref() { 336 | errors.push(Error::new_spanned( 337 | block, 338 | "Event methods must not contain an implementation.", 339 | )); 340 | } 341 | 342 | let event_name: String = method.sig.ident.to_string(); 343 | 344 | // Here we build the data descriptor array. The data descriptor array is constructed on 345 | // the stack, and has a statically-known size. It contains pointers to data fields. The 346 | // event metadata describes the order and type of the data pointed-to by the data 347 | // descriptors. 348 | // 349 | // For self-describing events (TraceLogging), the first two items in the data descriptor 350 | // array identify the provider metadata and the event metadata. 351 | let mut data_descriptor_array = TokenStream::new(); 352 | 353 | // See comments in traceloggingprovider.h, around line 2300, which describe the 354 | // encoding of the event mdata. 355 | let mut event_metadata: Vec = Vec::new(); 356 | event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 0) 357 | event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 1) 358 | event_metadata.push(parse_quote! { 0 }); // no extensions 359 | append_utf8_str_chars(&mut event_metadata, &event_name); 360 | 361 | // Some fields require running some code before building the data descriptors, so we 362 | // collect statements here. 363 | let mut statements = TokenStream::new(); 364 | 365 | // Each parameter (except for &self) becomes an event field. 366 | let mut found_receiver = false; 367 | 368 | // sig is the function signature for the function that we will generate for this event. 369 | let mut sig = method.sig.clone(); 370 | 371 | for param in sig.inputs.iter_mut() { 372 | let param_span = param.span(); 373 | match param { 374 | FnArg::Receiver(_) => { 375 | errors.push(Error::new_spanned(param, "Event methods should not provide any receiver arguments (&self, &mut self, etc.).")); 376 | found_receiver = true; 377 | } 378 | 379 | FnArg::Typed(param_typed) => { 380 | let mut event_attr: Option = None; 381 | param_typed.attrs.retain(|a| { 382 | if a.path() == &parse_quote!(event) { 383 | event_attr = Some(a.clone()); 384 | false 385 | } else if a.path() == &parse_quote!(doc) { 386 | true 387 | } else { 388 | errors.push(Error::new_spanned( 389 | a, 390 | "This attribute is not permitted on event fields.", 391 | )); 392 | true 393 | } 394 | }); 395 | let param_name: &Ident = match &*param_typed.pat { 396 | syn::Pat::Ident(ref name) => &name.ident, 397 | _ => { 398 | errors.push(Error::new( 399 | param.span(), 400 | "Only ordinary parameter patterns are supported on event methods.", 401 | )); 402 | continue; 403 | } 404 | }; 405 | 406 | if parse_event_field( 407 | &mut errors, 408 | &wk, 409 | event_attr.as_ref(), 410 | param_span, 411 | param_name, 412 | &mut param_typed.ty, 413 | &mut data_descriptor_array, 414 | &mut event_metadata, 415 | &mut statements, 416 | ) 417 | .is_err() 418 | { 419 | errors.push(Error::new_spanned( 420 | ¶m, 421 | "This type is not supported for event parameters.", 422 | )); 423 | } 424 | } 425 | } 426 | } 427 | 428 | // We require that every function declare a '&self' receiver parameter. 429 | if !found_receiver { 430 | sig.inputs.insert(0, parse_quote!(&self)); 431 | } 432 | 433 | // Insert the "options" argument. 434 | sig.inputs.insert( 435 | 1, 436 | parse_quote!(options: core::option::Option<&::win_etw_provider::EventOptions>), 437 | ); 438 | 439 | // Now that we have processed all parameters ("fields"), we can finish constructing 440 | // the per-event metadata. 441 | let event_metadata_len = event_metadata.len(); 442 | if event_metadata_len > 0xffff { 443 | errors.push(Error::new( 444 | method.span(), 445 | "Event metadata is too large to encode; reduce the complexity of this event.", 446 | )); 447 | continue; 448 | } 449 | let event_metadata_len_b0 = (event_metadata_len & 0xff) as u8; 450 | let event_metadata_len_b1 = (event_metadata_len >> 8) as u8; 451 | event_metadata[0] = parse_quote! { #event_metadata_len_b0 }; 452 | event_metadata[1] = parse_quote! { #event_metadata_len_b1 }; 453 | 454 | let event_attrs = parse_event_attributes(&mut errors, &method.sig.ident, &method.attrs); 455 | 456 | // Generate the event descriptor for this event. 457 | // This is a static variable. The name is exactly the name of the event. 458 | let event_level = event_attrs.level; 459 | let event_opcode = event_attrs.opcode; 460 | let event_task = event_attrs.task; 461 | let potential_event_id = event_attrs.event_id; 462 | let event_keyword = event_attrs 463 | .keyword 464 | .as_ref() 465 | .cloned() 466 | .unwrap_or_else(|| parse_quote!(0)); 467 | 468 | // We use the first entry to see if we have user provided IDs 469 | // or we are generating one. 470 | if event_index == 0 { 471 | event_ids_auto_generated = potential_event_id.is_none(); 472 | } 473 | 474 | // We have some events with user provided id and some without. 475 | if event_ids_auto_generated != potential_event_id.is_none() { 476 | errors.push(Error::new( 477 | method.span(), 478 | "Event ids must be set for all events, or for none.", 479 | )); 480 | } 481 | 482 | let event_id = potential_event_id.unwrap_or(event_index); 483 | 484 | // Only care about #[event(id = #)] types so we don't get 485 | // confusing messages when forget to add an id for some 486 | // event. 487 | if potential_event_id.is_some() { 488 | let identifier = method.sig.ident.to_string(); 489 | if let Some(previous) = event_id_mappings.get(&event_id) { 490 | errors.push(Error::new( 491 | method.span(), 492 | format!("Event id {event_id} has already been defined on {previous}."), 493 | )); 494 | } 495 | event_id_mappings.insert(event_id, identifier); 496 | } 497 | 498 | // an expression which generates EventDescriptor 499 | let event_descriptor = quote! { 500 | ::win_etw_provider::EventDescriptor { 501 | id: #event_id, 502 | version: 0, 503 | channel: 11, 504 | level: #event_level, 505 | opcode: #event_opcode, 506 | task: #event_task, 507 | keyword: #event_keyword, 508 | }; 509 | }; 510 | 511 | let event_attrs_method_attrs = &event_attrs.method_attrs; 512 | 513 | // Generate the `${name}_is_enabled` function for this event. 514 | // We do not use ident_suffix() because this is not a private identifier. 515 | let event_is_enabled_name = Ident::new( 516 | &format!("{}_is_enabled", method.sig.ident), 517 | method.sig.ident.span(), 518 | ); 519 | 520 | // Build the method that implements this event. 521 | provider_impl_items.extend(quote!{ 522 | #( #event_attrs_method_attrs )* 523 | #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] 524 | pub #sig 525 | { 526 | #[cfg(target_os = "windows")] 527 | { 528 | use ::win_etw_provider::EventDataDescriptor; 529 | 530 | // This places the EVENT_METADATA into a read-only linker section, properly 531 | // ordered with respect to TRACE_LOGGING_METADATA and other related sections. 532 | #[link_section = ".rdata$etw1"] 533 | #[used] 534 | static EVENT_METADATA: [u8; #event_metadata_len] = [ #( #event_metadata, )* ]; 535 | 536 | let mut event_descriptor: ::win_etw_provider::EventDescriptor = #event_descriptor; 537 | 538 | if let Some(opts) = options { 539 | if let Some(level) = opts.level { 540 | event_descriptor.level = level; 541 | } 542 | } 543 | 544 | #statements 545 | 546 | let data_descriptors = [ 547 | EventDataDescriptor::for_provider_metadata(&#provider_metadata_ident[..]), 548 | EventDataDescriptor::for_event_metadata(&EVENT_METADATA[..]), 549 | #data_descriptor_array 550 | ]; 551 | ::win_etw_provider::Provider::write(&self.provider, 552 | options, 553 | &event_descriptor, 554 | &data_descriptors, 555 | ); 556 | } 557 | } 558 | 559 | pub fn #event_is_enabled_name(&self, level: ::core::option::Option<::win_etw_provider::Level>) -> bool { 560 | #[cfg(target_os = "windows")] 561 | { 562 | let mut event_descriptor: ::win_etw_provider::EventDescriptor = #event_descriptor; 563 | if let Some(level) = level { 564 | event_descriptor.level = level; 565 | } 566 | 567 | ::win_etw_provider::Provider::is_event_enabled( 568 | &self.provider, 569 | &event_descriptor) 570 | } 571 | #[cfg(not(target_os = "windows"))] 572 | { 573 | false 574 | } 575 | } 576 | }); 577 | } 578 | 579 | // We propagate the visibility of the trait definition to the structure definition. 580 | let vis = logging_trait.vis.clone(); 581 | let provider_guid = match provider_attrs.uuid { 582 | Some(uuid) => uuid, 583 | None => etw_event_source_guid(provider_name), 584 | }; 585 | let provider_guid_const = uuid_to_expr(&provider_guid); 586 | 587 | // If the input item has doc attributes, then carry them over to the output type. 588 | let doc_path: syn::Path = parse_quote!(doc); 589 | let provider_doc_attrs = logging_trait 590 | .attrs 591 | .iter() 592 | .filter(|a| a.path() == &doc_path) 593 | .collect::>(); 594 | 595 | // Build a code fragment that registers the provider traits. 596 | let register_traits: TokenStream = 597 | create_register_provider_traits(provider_name, provider_attrs.provider_group_guid.as_ref()); 598 | 599 | output.extend(quote! { 600 | #( #provider_doc_attrs )* 601 | #vis struct #provider_ident { 602 | provider: ::core::option::Option<::win_etw_provider::EtwProvider>, 603 | } 604 | 605 | impl #provider_ident { 606 | /// Creates (registers) a new instance of this provider. If registration fails, 607 | /// returns a "null" provider. This prevents problems with event logging from 608 | /// disrupting the normal operation of applications. 609 | /// 610 | /// On non-Windows platforms, this function always returns a null provider. 611 | /// 612 | /// Creating an event source is a costly operation, because it requires contacting the 613 | /// event manager, allocating event buffers, potentially receiving callbacks from 614 | /// event consumers, etc. Applications should only create event sources during process 615 | /// initialization, and should always reuse them, never re-creating them. 616 | pub fn new() -> Self { 617 | let provider = match ::win_etw_provider::EtwProvider::new(&Self::PROVIDER_GUID) { 618 | Ok(mut provider) => { 619 | #[cfg(target_os = "windows")] 620 | { 621 | #register_traits 622 | } 623 | 624 | Some(provider) 625 | } 626 | Err(_) => None, 627 | }; 628 | Self { provider } 629 | } 630 | 631 | /// Creates (registers) a new instance of this provider. If registration fails, then 632 | /// this method returns a "null" provider. 633 | /// 634 | /// On non-Windows platforms, this function always returns `Ok`, containing a null 635 | /// provider. 636 | /// 637 | /// Creating an event source is a costly operation, because it requires contacting the 638 | /// event manager, allocating event buffers, potentially receiving callbacks from 639 | /// event consumers, etc. Applications should only create event sources during process 640 | /// initialization, and should always reuse them, never re-creating them. 641 | pub fn new_err() -> ::core::result::Result { 642 | Ok(Self { 643 | provider: Some(::win_etw_provider::EtwProvider::new(&Self::PROVIDER_GUID)?), 644 | }) 645 | } 646 | 647 | /// Creates a new "null" instance of the provider. All events written to this provider 648 | /// are discarded. 649 | pub fn null() -> Self { 650 | Self { provider: None } 651 | } 652 | 653 | #[allow(unused_variable)] 654 | pub const PROVIDER_GUID: ::win_etw_provider::GUID = #provider_guid_const; 655 | pub const PROVIDER_NAME: &'static str = #provider_name; 656 | } 657 | 658 | // We intentionally generate identifiers that are not snake-case. 659 | #[allow(non_snake_case)] 660 | impl #provider_ident { 661 | #provider_impl_items 662 | } 663 | }); 664 | 665 | output.extend(errors.into_iter().map(|e| e.to_compile_error())); 666 | output 667 | } 668 | 669 | /// Creates a fragment of code (statements) which will register the 670 | /// provider traits for this provider. 671 | // 672 | // See https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits 673 | fn create_register_provider_traits( 674 | provider_name: &str, 675 | provider_group_guid: Option<&Uuid>, 676 | ) -> TokenStream { 677 | let mut traits_bytes: Vec = Vec::new(); 678 | traits_bytes.push(0); // reserve space for TraitsSize (u16) 679 | traits_bytes.push(0); 680 | 681 | traits_bytes.extend_from_slice(provider_name.as_bytes()); 682 | traits_bytes.push(0); 683 | 684 | if let Some(provider_group_guid) = provider_group_guid { 685 | // Add trait for provider guid 686 | let provider_guid_trait_offset = traits_bytes.len(); 687 | traits_bytes.push(0); // reserve space for TraitSize (u16) 688 | traits_bytes.push(0); 689 | traits_bytes.push(ETW_PROVIDER_TRAIT_TYPE_GROUP); 690 | traits_bytes.extend_from_slice(&provider_group_guid.to_bytes_le()); 691 | let provider_guid_trait_len = traits_bytes.len() - provider_guid_trait_offset; 692 | // Set TraitSize (u16) 693 | traits_bytes[provider_guid_trait_offset] = provider_guid_trait_len as u8; 694 | traits_bytes[provider_guid_trait_offset + 1] = (provider_guid_trait_len >> 8) as u8; 695 | } 696 | 697 | // Set TraitsSize (u16) 698 | traits_bytes[0] = traits_bytes.len() as u8; 699 | traits_bytes[1] = (traits_bytes.len() >> 8) as u8; 700 | 701 | let traits_bytes_len = traits_bytes.len(); 702 | 703 | quote! { 704 | static PROVIDER_TRAITS: [u8; #traits_bytes_len] = [ #(#traits_bytes),* ]; 705 | // We ignore the Result from calling set_provider_traits. 706 | // There is no good way to report it. 707 | let _ = provider.set_provider_traits(&PROVIDER_TRAITS); 708 | } 709 | } 710 | 711 | fn err_spanned(item: &T, msg: &str) -> TokenStream { 712 | Error::new_spanned(item, msg).to_compile_error() 713 | } 714 | 715 | // `provider_name` is not necessarily the same as the identifier used in source code. 716 | // It can be overridden using `#[trace_logging_provider(name = "some_name_here")]`. 717 | fn create_provider_metadata(provider_name: &str, provider_metadata_ident: &Ident) -> TokenStream { 718 | let mut provider_metadata: Vec = Vec::new(); 719 | 720 | let provider_metadata_len = 2 + provider_name.len() + 1; 721 | if provider_metadata_len > 0xffff { 722 | return err_spanned(&provider_name, "The provider name is too long."); 723 | } 724 | provider_metadata.push((provider_metadata_len & 0xff) as u8); 725 | provider_metadata.push((provider_metadata_len >> 8) as u8); 726 | provider_metadata.extend_from_slice(provider_name.as_bytes()); 727 | provider_metadata.push(0); 728 | 729 | quote! { 730 | #[link_section = ".rdata$etw2"] 731 | #[used] 732 | #[allow(non_upper_case_globals)] 733 | #[cfg(target_os = "windows")] 734 | static #provider_metadata_ident: [u8; #provider_metadata_len] = [ 735 | #( 736 | #provider_metadata, 737 | )* 738 | ]; 739 | } 740 | } 741 | 742 | fn append_utf8_str_chars(output: &mut Vec, s: &str) { 743 | for &b in s.as_bytes().iter() { 744 | output.push(parse_quote! { #b }); 745 | } 746 | // Add the NUL byte at the end. 747 | output.push(parse_quote! { 0 }); 748 | } 749 | 750 | struct UnsupportedField; 751 | 752 | /// Parses one event field. Event fields are declared as function parameters. 753 | /// 754 | /// * `event_metadata`: This builds the static [u8; N] array that contains the metadata for this 755 | /// event. It can contain literals, symbolic expressions, etc. 756 | fn parse_event_field( 757 | errors: &mut Vec, 758 | well_known_types: &WellKnownTypes, 759 | event_attr: Option<&syn::Attribute>, 760 | field_span: proc_macro2::Span, 761 | field_name: &Ident, 762 | field_ty: &mut syn::Type, 763 | data_descriptor_array: &mut TokenStream, 764 | event_metadata: &mut Vec, 765 | statements: &mut TokenStream, 766 | ) -> Result<(), UnsupportedField> { 767 | // Write the field metadata. 768 | // // FieldMetadata: 769 | // struct FieldMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. 770 | // { 771 | // char Name[]; // UTF-8 nul-terminated field name 772 | // UINT8 InType; // Values from the TlgIn enumeration. 773 | // UINT8 OutType; // TlgOut enumeration. Only present if (InType & 128) == 128. 774 | // UINT8 Extension[]; // Only present if OutType is present and (OutType & 128) == 128. Read until you hit a byte with high bit unset. 775 | // UINT16 ValueCount; // Only present if (InType & CountMask) == Ccount. 776 | // UINT16 TypeInfoSize; // Only present if (InType & CountMask) == Custom. 777 | // char TypeInfo[TypeInfoSize]; // Only present if (InType & CountMask) == Custom. 778 | // }; 779 | 780 | let param_name_string = field_name.to_string(); 781 | append_utf8_str_chars(event_metadata, ¶m_name_string); 782 | // We will append more data to event_metadata, below. 783 | 784 | // The user can annotate fields with #[event(...)] in order to specify output formats. 785 | let mut output_hex = false; 786 | if let Some(event_attr) = event_attr { 787 | if let Err(e) = event_attr.parse_nested_meta(|meta| { 788 | if meta.path.is_ident("output") { 789 | let value: syn::LitStr = meta.value()?.parse()?; 790 | match value.value().as_str() { 791 | "hex" => { 792 | output_hex = true; 793 | } 794 | _ => { 795 | return Err(meta.error("Output format is not recognized.")); 796 | } 797 | } 798 | } else { 799 | return Err(meta.error("This metadata key is not recognized.")); 800 | } 801 | Ok(()) 802 | }) { 803 | errors.push(e); 804 | } 805 | } 806 | 807 | let mut field_metadata_intype: Expr; 808 | let mut field_metadata_out_type: Option = None; 809 | 810 | if let Some(t) = well_known_types.find(&*field_ty) { 811 | field_metadata_intype = if let Some(in_type_expr) = t.opts.in_type_expr.as_ref() { 812 | in_type_expr.clone() 813 | } else { 814 | let in_type: u8 = t.in_type.bits(); 815 | parse_quote!(#in_type) 816 | }; 817 | 818 | if let Some(out_type) = &t.opts.out_type { 819 | let out_type: u8 = out_type.bits(); 820 | field_metadata_out_type = Some(parse_quote!(#out_type)); 821 | } else { 822 | field_metadata_out_type = None; 823 | } 824 | 825 | if let Some(r) = t.opts.replacement_type.as_ref() { 826 | *field_ty = r.clone(); 827 | } 828 | match t.code { 829 | WellKnownType::ref_str => { 830 | // We encode &str as COUNTEDANSISTRING (so that we do not need 831 | // a NUL-terminated string) and marking its output type as UTF-8. 832 | // This uses two EVENT_DATA_DESCRIPTOR slots. 833 | let field_len = ident_suffix(field_name, "len"); 834 | statements.extend(quote_spanned! { 835 | field_span => 836 | let #field_len: u16 = #field_name.len() as u16; 837 | }); 838 | data_descriptor_array.extend(quote! { 839 | EventDataDescriptor::from(&#field_len), 840 | EventDataDescriptor::from(#field_name), 841 | }); 842 | } 843 | WellKnownType::u16str => { 844 | // UCS-2 string without NUL terminator. 845 | let field_len = ident_suffix(field_name, "len"); 846 | statements.extend(quote! { 847 | let #field_len: usize = #field_name.len(); // length in code units 848 | // Since we're recording this as COUNTEDUNICODESTRING, we 849 | // want the length in bytes of the string, excluding the NUL. 850 | // Which is easy, because there is no NUL. 851 | let #field_len: u16 = (#field_len * 2).min(0xffff) as u16; 852 | }); 853 | data_descriptor_array.extend(quote! { 854 | EventDataDescriptor::from(&#field_len), 855 | EventDataDescriptor::from(#field_name), 856 | }); 857 | } 858 | WellKnownType::u16cstr => { 859 | // UCS-2 string without NUL terminator. 860 | let field_len = ident_suffix(field_name, "len"); 861 | statements.extend(quote! { 862 | let #field_len: usize = #field_name.len(); // length in code units 863 | // Since we're recording this as COUNTEDUNICODESTRING, we 864 | // want the length in bytes of the string, excluding the NUL. 865 | let #field_len: u16 = (#field_len * 2).min(0xffff) as u16; 866 | }); 867 | data_descriptor_array.extend(quote! { 868 | EventDataDescriptor::from(&#field_len), 869 | EventDataDescriptor::from(#field_name), 870 | }); 871 | } 872 | WellKnownType::osstr => { 873 | let field_len = ident_suffix(field_name, "len"); 874 | let field_desc = ident_suffix(field_name, "desc"); 875 | let field_u16cstring = ident_suffix(field_name, "u16cstring"); 876 | let field_u16cstr = ident_suffix(field_name, "u16cstr"); 877 | statements.extend(quote! { 878 | let #field_desc: EventDataDescriptor; 879 | let #field_len: u16; 880 | let #field_u16cstring: ::win_etw_provider::types::U16CString; 881 | let #field_u16cstr: &::win_etw_provider::types::U16CStr; 882 | match ::win_etw_provider::types::U16CString::from_os_str(#field_name) { 883 | Ok(s) => { 884 | #field_u16cstring = s; 885 | #field_u16cstr = #field_u16cstring.as_ref(); 886 | #field_desc = EventDataDescriptor::from(#field_u16cstr); 887 | #field_len = (#field_u16cstr.len() as u16 * 2); // compute length in bytes 888 | } 889 | Err(_) => { 890 | #field_desc = EventDataDescriptor::empty(); 891 | #field_len = 0; 892 | } 893 | } 894 | }); 895 | data_descriptor_array.extend(quote! { 896 | EventDataDescriptor::from(&#field_len), 897 | #field_desc, 898 | }); 899 | } 900 | WellKnownType::SocketAddrV4 => { 901 | // We cannot simply pass a copy of std::net::SocketAddrV4 to ETW because it does 902 | // not have a guaranteed memory layout. So we convert it to 903 | // win_etw_provider::SocketAddrV4, which does. 904 | let field_len = ident_suffix(field_name, "len"); 905 | statements.extend(quote_spanned! { 906 | field_span => 907 | let #field_name = ::win_etw_provider::SocketAddrV4::from(#field_name); 908 | let #field_len: u16 = (::core::mem::size_of::<::win_etw_provider::SocketAddrV4>()) as u16; 909 | }); 910 | data_descriptor_array.extend(quote! { 911 | EventDataDescriptor::from(&#field_len), 912 | EventDataDescriptor::from(&#field_name), 913 | }); 914 | } 915 | WellKnownType::SocketAddrV6 => { 916 | // We cannot simply pass a copy of std::net::SocketAddrV6 to ETW because it does 917 | // not have a guaranteed memory layout. So we convert it to 918 | // win_etw_provider::SocketAddrV6, which does. 919 | let field_len = ident_suffix(field_name, "len"); 920 | statements.extend(quote_spanned! { 921 | field_span => 922 | let #field_name = ::win_etw_provider::SocketAddrV6::from(#field_name); 923 | let #field_len: u16 = (::core::mem::size_of::<::win_etw_provider::SocketAddrV6>()) as u16; 924 | }); 925 | data_descriptor_array.extend(quote_spanned! { 926 | field_span => 927 | EventDataDescriptor::from(&#field_len), 928 | EventDataDescriptor::from(&#field_name), 929 | }); 930 | } 931 | WellKnownType::SocketAddr => { 932 | let field_desc = ident_suffix(field_name, "desc"); 933 | let field_v4 = ident_suffix(field_name, "v4"); 934 | let field_v6 = ident_suffix(field_name, "v6"); 935 | let field_len_ident = ident_suffix(field_name, "len"); 936 | statements.extend(quote_spanned! { 937 | field_span => 938 | let #field_v4; 939 | let #field_v6; 940 | let #field_len_ident; 941 | let #field_desc; 942 | match #field_name { 943 | ::std::net::SocketAddr::V4(ref address_v4) => { 944 | #field_v4 = ::win_etw_provider::SocketAddrV4::from(address_v4); 945 | #field_len_ident = ::core::mem::size_of::<::win_etw_provider::SocketAddrV4>() as u16; 946 | #field_desc = EventDataDescriptor::from(&#field_v4); 947 | } 948 | ::std::net::SocketAddr::V6(ref address_v6) => { 949 | #field_v6 = ::win_etw_provider::SocketAddrV6::from(address_v6); 950 | #field_len_ident = ::core::mem::size_of::<::win_etw_provider::SocketAddrV6>() as u16; 951 | #field_desc = EventDataDescriptor::from(&#field_v6); 952 | } 953 | } 954 | }); 955 | data_descriptor_array.extend(quote_spanned! { 956 | field_span => 957 | EventDataDescriptor::from(&#field_len_ident), 958 | #field_desc, 959 | }); 960 | } 961 | WellKnownType::SystemTime => { 962 | // If the SystemTime value cannot be converted to a FILETIME, then the data 963 | // descriptor for this field will be empty, rather than pointing to an invalid 964 | // or incorrect time value. 965 | statements.extend(quote_spanned!{ 966 | field_span => 967 | let #field_name = <::win_etw_provider::FILETIME as ::core::convert::TryFrom<::std::time::SystemTime>> 968 | ::try_from(#field_name); 969 | }); 970 | data_descriptor_array.extend(quote_spanned! { 971 | field_span => 972 | match &#field_name { 973 | Ok(ref t) => EventDataDescriptor::from(&t.0), 974 | Err(_) => EventDataDescriptor::empty(), 975 | } 976 | }); 977 | } 978 | WellKnownType::FILETIME => { 979 | data_descriptor_array.extend(quote_spanned! { 980 | field_span => 981 | EventDataDescriptor::from(&#field_name.0), 982 | }); 983 | } 984 | WellKnownType::bool => { 985 | statements.extend(quote_spanned! { 986 | field_span => 987 | let #field_name: i8 = #field_name as i8; 988 | }); 989 | data_descriptor_array.extend(quote! { 990 | EventDataDescriptor::from(&#field_name), 991 | }); 992 | } 993 | _ => { 994 | if t.is_ref { 995 | data_descriptor_array.extend(quote_spanned! { 996 | field_span => 997 | EventDataDescriptor::from(#field_name), 998 | }); 999 | } else { 1000 | data_descriptor_array.extend(quote_spanned! { 1001 | field_span => 1002 | EventDataDescriptor::from(&#field_name), 1003 | }); 1004 | } 1005 | } 1006 | } 1007 | } else { 1008 | match &*field_ty { 1009 | syn::Type::Reference(ref_ty) => { 1010 | match &*ref_ty.elem { 1011 | syn::Type::Slice(slice_ty) => { 1012 | if let Some(t) = well_known_types.find(&slice_ty.elem) { 1013 | if !t.primitive { 1014 | return Err(UnsupportedField); 1015 | } 1016 | // Slices are encoded using two data descriptors. 1017 | // The first is for the length field, the second for the data. 1018 | let field_len_ident = ident_suffix(field_name, "len"); 1019 | statements.extend(quote_spanned! { 1020 | field_span => 1021 | let #field_name = &#field_name[..#field_name.len().min(0xffff)]; 1022 | let #field_len_ident: u16 = #field_name.len() as u16; 1023 | }); 1024 | data_descriptor_array.extend(quote! { 1025 | EventDataDescriptor::from(&#field_len_ident), 1026 | EventDataDescriptor::from(#field_name), 1027 | }); 1028 | // 0x40 is VCOUNT flag 1029 | let in_type_u8 = t.in_type.bits(); 1030 | field_metadata_intype = parse_quote!(#in_type_u8); 1031 | field_metadata_intype = parse_quote!(#field_metadata_intype | ::win_etw_provider::metadata::InFlag::VCOUNT_FLAG.bits()); 1032 | } else { 1033 | return Err(UnsupportedField); 1034 | } 1035 | } 1036 | _ => { 1037 | return Err(UnsupportedField); 1038 | } 1039 | } 1040 | } 1041 | _ => { 1042 | return Err(UnsupportedField); 1043 | } 1044 | } 1045 | } 1046 | 1047 | if output_hex { 1048 | let hex: Expr = parse_quote!(::win_etw_provider::metadata::OutFlag::HEX.bits()); 1049 | field_metadata_out_type = Some(if let Some(out_type) = field_metadata_out_type { 1050 | parse_quote!(#out_type | #hex) 1051 | } else { 1052 | hex 1053 | }); 1054 | } 1055 | 1056 | if let Some(out_type) = field_metadata_out_type { 1057 | field_metadata_intype = parse_quote!(#field_metadata_intype | ::win_etw_provider::metadata::InFlag::CHAIN_FLAG.bits()); 1058 | event_metadata.push(field_metadata_intype); 1059 | event_metadata.push(out_type); 1060 | } else { 1061 | event_metadata.push(field_metadata_intype); 1062 | } 1063 | Ok(()) 1064 | } 1065 | 1066 | /// Represents the "attribute" parameter of the `#[trace_logging_provider]` proc macro. 1067 | #[derive(Default, Debug)] 1068 | struct ProviderAttributes { 1069 | uuid: Option, 1070 | provider_name: Option, 1071 | provider_group_guid: Option, 1072 | } 1073 | 1074 | impl syn::parse::Parse for ProviderAttributes { 1075 | fn parse(stream: syn::parse::ParseStream) -> syn::Result { 1076 | let mut uuid_opt = None; 1077 | let mut provider_group_guid: Option = None; 1078 | let mut provider_name = None; 1079 | 1080 | let parse_guid_value = |lit_str: &syn::LitStr| -> Result { 1081 | let guid_str = lit_str.value(); 1082 | if let Ok(value) = guid_str.parse::() { 1083 | if value == Uuid::nil() { 1084 | Err(syn::Error::new_spanned( 1085 | lit_str, 1086 | "The GUID cannot be the NIL (all-zeroes) GUID.", 1087 | )) 1088 | } else { 1089 | Ok(value) 1090 | } 1091 | } else { 1092 | Err(syn::Error::new_spanned( 1093 | lit_str, 1094 | "The attribute value is required to be a valid GUID.", 1095 | )) 1096 | } 1097 | }; 1098 | 1099 | let meta_list = 1100 | syn::punctuated::Punctuated::::parse_terminated(stream)?; 1101 | 1102 | for meta in meta_list.iter() { 1103 | match meta { 1104 | syn::Meta::NameValue(nv) => { 1105 | if nv.path.is_ident("guid") { 1106 | if let syn::Expr::Lit(syn::ExprLit { 1107 | lit: syn::Lit::Str(lit_str), 1108 | .. 1109 | }) = &nv.value 1110 | { 1111 | let uuid = parse_guid_value(lit_str)?; 1112 | if uuid_opt.is_some() { 1113 | return Err(syn::Error::new_spanned( 1114 | &nv.path, 1115 | "The 'guid' attribute key cannot be specified more than once.", 1116 | )); 1117 | } 1118 | uuid_opt = Some(uuid); 1119 | } else { 1120 | return Err(syn::Error::new_spanned( 1121 | &nv.value, 1122 | "The attribute value is required to be a GUID in string form.", 1123 | )); 1124 | } 1125 | } else if nv.path.is_ident("name") { 1126 | if let syn::Expr::Lit(syn::ExprLit { 1127 | lit: syn::Lit::Str(s), 1128 | .. 1129 | }) = &nv.value 1130 | { 1131 | if provider_name.is_none() { 1132 | provider_name = Some(s.value()); 1133 | } else { 1134 | return Err(syn::Error::new_spanned( 1135 | &nv.path, 1136 | "The 'name' attribute can only be specified once.", 1137 | )); 1138 | } 1139 | } else { 1140 | return Err(syn::Error::new_spanned( 1141 | &nv.value, 1142 | "The 'name' attribute key requires a string value.", 1143 | )); 1144 | } 1145 | } else if nv.path.is_ident("provider_group_guid") { 1146 | if let syn::Expr::Lit(syn::ExprLit { 1147 | lit: syn::Lit::Str(lit_str), 1148 | .. 1149 | }) = &nv.value 1150 | { 1151 | let uuid = parse_guid_value(lit_str)?; 1152 | if provider_group_guid.is_some() { 1153 | return Err(syn::Error::new_spanned(&nv.path, "The 'provider_group_guid' attribute key cannot be specified more than once.")); 1154 | } 1155 | provider_group_guid = Some(uuid); 1156 | } else { 1157 | return Err(syn::Error::new_spanned( 1158 | &nv.value, 1159 | "The attribute value is required to be a GUID in string form.", 1160 | )); 1161 | } 1162 | } else { 1163 | return Err(syn::Error::new_spanned( 1164 | &nv.path, 1165 | "Unrecognized attribute key.", 1166 | )); 1167 | } 1168 | } 1169 | syn::Meta::Path(path) if path.is_ident("static_mode") => { 1170 | // eprintln!("Found 'static'"); 1171 | } 1172 | _ => { 1173 | return Err(syn::Error::new_spanned( 1174 | meta, 1175 | "Unrecognized attribute item.", 1176 | )); 1177 | } 1178 | } 1179 | } 1180 | 1181 | Ok(ProviderAttributes { 1182 | uuid: uuid_opt, 1183 | provider_name, 1184 | provider_group_guid, 1185 | }) 1186 | } 1187 | } 1188 | 1189 | const ETW_EVENT_SOURCE_NAMESPACE: Uuid = uuid::uuid!("482c2db2-c390-47c8-87f8-1a15bfc130fb"); 1190 | 1191 | /// Generates a Uuid from a provider name using the same algorithm as .NET's EventSource class. 1192 | /// Many tools convert a provider name to a GUID using this algorithm. 1193 | fn etw_event_source_guid(provider_name: &str) -> Uuid { 1194 | use sha1_smol::Sha1; 1195 | 1196 | let provider_bytes: Vec = provider_name 1197 | .to_uppercase() 1198 | .encode_utf16() 1199 | .flat_map(|x| x.to_be_bytes()) 1200 | .collect(); 1201 | 1202 | let mut hasher = Sha1::new(); 1203 | hasher.update(ETW_EVENT_SOURCE_NAMESPACE.as_bytes()); 1204 | hasher.update(&provider_bytes); 1205 | 1206 | let mut bytes = [0; 16]; 1207 | bytes.copy_from_slice(&hasher.digest().bytes()[..16]); 1208 | 1209 | // .NET EventSource reads the bytes from the SHA-1 hash for the first 3 fields 1210 | // as little-endian, but does not set the "variant (aka type)" to the "Microsoft" value which 1211 | // indicates little-endian in a RFC4122-conforming UUID. The "variant" field 1212 | // ends up being the "random" value copied from the from the SHA-1 hash, instead. 1213 | uuid::Builder::from_bytes_le(bytes) 1214 | .with_version(uuid::Version::Sha1) 1215 | .into_uuid() 1216 | } 1217 | 1218 | fn uuid_to_expr(uuid: &Uuid) -> syn::Expr { 1219 | let bytes: &[u8; 16] = uuid.as_bytes(); 1220 | let data1: u32 = ((bytes[0] as u32) << 24) 1221 | | ((bytes[1] as u32) << 16) 1222 | | ((bytes[2] as u32) << 8) 1223 | | (bytes[3] as u32); 1224 | let data2: u16 = ((bytes[4] as u16) << 8) | (bytes[5] as u16); 1225 | let data3: u16 = ((bytes[6] as u16) << 8) | (bytes[7] as u16); 1226 | let data4_0 = bytes[8]; 1227 | let data4_1 = bytes[9]; 1228 | let data4_2 = bytes[10]; 1229 | let data4_3 = bytes[11]; 1230 | let data4_4 = bytes[12]; 1231 | let data4_5 = bytes[13]; 1232 | let data4_6 = bytes[14]; 1233 | let data4_7 = bytes[15]; 1234 | parse_quote! { 1235 | ::win_etw_provider::guid!( 1236 | #data1, 1237 | #data2, 1238 | #data3, 1239 | #data4_0, 1240 | #data4_1, 1241 | #data4_2, 1242 | #data4_3, 1243 | #data4_4, 1244 | #data4_5, 1245 | #data4_6, 1246 | #data4_7 1247 | ) 1248 | } 1249 | } 1250 | 1251 | struct EventAttributes { 1252 | level: syn::Expr, 1253 | opcode: syn::Expr, 1254 | task: syn::Expr, 1255 | keyword: Option, 1256 | event_id: Option, 1257 | method_attrs: Vec, 1258 | } 1259 | 1260 | fn parse_event_attributes( 1261 | errors: &mut Vec, 1262 | method_ident: &Ident, 1263 | input_method_attrs: &[syn::Attribute], 1264 | ) -> EventAttributes { 1265 | let mut level: Expr = parse_quote!(::win_etw_provider::Level::VERBOSE); 1266 | let mut opcode: Expr = parse_quote!(0); 1267 | let mut task: Expr = parse_quote!(0); 1268 | let mut keyword: Option = None; 1269 | 1270 | // I am not aware of how to convert from Expr to actual value 1271 | // so going to handle this here. 1272 | let mut event_id: Option = None; 1273 | 1274 | let mut method_attrs: Vec = Vec::new(); 1275 | 1276 | let mut event_already_has_doc = false; 1277 | let enable_default_event_doc = false; 1278 | 1279 | for attr in input_method_attrs.iter() { 1280 | if attr.path() == &parse_quote!(doc) { 1281 | method_attrs.push(attr.clone()); 1282 | event_already_has_doc = true; 1283 | } else if attr.path().is_ident("event") { 1284 | // The #[event] attribute lets the application specify the level, opcode, task, 1285 | // keyword, etc. 1286 | if let Err(e) = attr.parse_nested_meta(|meta| { 1287 | if meta.path.is_ident("level") { 1288 | let value = &meta.value()?; 1289 | // Peek to see if it's a string or int literal 1290 | let lookahead = value.lookahead1(); 1291 | if lookahead.peek(syn::LitStr) { 1292 | let lit_str: syn::LitStr = value.parse()?; 1293 | let level_ident = match lit_str.value().as_str() { 1294 | "critical" => quote!(CRITICAL), 1295 | "error" => quote!(ERROR), 1296 | "warn" => quote!(WARN), 1297 | "info" => quote!(INFO), 1298 | "verbose" => quote!(VERBOSE), 1299 | _ => { 1300 | return Err(meta.error( 1301 | "The value specified for 'level' is not a valid string.", 1302 | )); 1303 | } 1304 | }; 1305 | level = parse_quote!(::win_etw_provider::Level::#level_ident); 1306 | } else { 1307 | // Parse as any literal for numeric levels 1308 | let lit: Lit = value.parse()?; 1309 | level = parse_quote!(::win_etw_provider::Level(#lit)); 1310 | } 1311 | } else if meta.path.is_ident("opcode") { 1312 | let lit: Lit = meta.value()?.parse()?; 1313 | opcode = Expr::Lit(ExprLit { 1314 | lit, 1315 | attrs: Vec::new(), 1316 | }); 1317 | } else if meta.path.is_ident("task") { 1318 | let lit: Lit = meta.value()?.parse()?; 1319 | task = Expr::Lit(ExprLit { 1320 | lit, 1321 | attrs: Vec::new(), 1322 | }); 1323 | } else if meta.path.is_ident("keyword") { 1324 | if keyword.is_some() { 1325 | return Err(meta 1326 | .error("The 'keyword' attribute cannot be specified more than once.")); 1327 | } 1328 | let lit: Lit = meta.value()?.parse()?; 1329 | keyword = Some(Expr::Lit(ExprLit { 1330 | lit, 1331 | attrs: Vec::new(), 1332 | })); 1333 | } else if meta.path.is_ident("id") { 1334 | if event_id.is_some() { 1335 | return Err(meta.error("Event id has already been defined.")); 1336 | } 1337 | let lit_int: syn::LitInt = meta.value()?.parse()?; 1338 | if let Ok(int_value) = lit_int.base10_parse() { 1339 | event_id = Some(int_value); 1340 | } else { 1341 | return Err(meta.error("Event id must be a u16.")); 1342 | } 1343 | } else { 1344 | return Err(meta.error("Unrecognized attribute.")); 1345 | } 1346 | Ok(()) 1347 | }) { 1348 | errors.push(e); 1349 | } 1350 | } else { 1351 | errors.push(Error::new_spanned( 1352 | attr, 1353 | "The only attributes allowed on event methods are #[doc] and #[event(...)] attributes.", 1354 | )); 1355 | } 1356 | } 1357 | 1358 | if !event_already_has_doc && enable_default_event_doc { 1359 | let method_doc = format!("Writes the `{method_ident}` event to the ETW log stream."); 1360 | method_attrs.push(parse_quote!( #![doc = #method_doc] )); 1361 | } 1362 | 1363 | EventAttributes { 1364 | method_attrs, 1365 | level, 1366 | opcode, 1367 | task, 1368 | event_id, 1369 | keyword, 1370 | } 1371 | } 1372 | 1373 | /// The separator we use to build dynamic identifiers, based on existing identifiers. 1374 | /// Ideally, we would use a string that will not cause collisions with user-provided 1375 | /// identifiers. Rust supports non-ASCII identifiers, which would allow us to 1376 | /// use something like `U+0394 GREEK CAPITAL LETTER DELTA`, but this is an unstable 1377 | /// feature. 1378 | /// 1379 | /// Instead, we use "__". Idiomatic identifiers should not contain "__" because this 1380 | /// does not meet Rust's definition of a snake-case name. So we add use this, and add 1381 | /// `#[allow(non_snake_case)]` to our generated code. 1382 | const IDENT_SEPARATOR: &str = "__"; 1383 | 1384 | /// Builds a new identifier, based on an existing identifier. 1385 | fn ident_suffix(ident: &Ident, suffix: &str) -> Ident { 1386 | Ident::new(&format!("{ident}{IDENT_SEPARATOR}{suffix}"), ident.span()) 1387 | } 1388 | 1389 | const ETW_PROVIDER_TRAIT_TYPE_GROUP: u8 = 1; 1390 | --------------------------------------------------------------------------------