Git Product home page Git Product logo

rust_win_etw's Introduction

Rust support for Event Tracing for Windows (ETW)

Provides the #[trace_logging_provider] macro, which allows you to define a Trace Logging Provider for use with the Event Tracing for Windows (ETW) framework.

This macro is intended for use only when targeting Windows. When targeting other platforms, this macro will still work, but will generate code that does nothing.

This framework allows applications to log schematized events, rather than textual strings. ETW analysis tools can reliably identify fields within your events, and treat them as strongly-typed data, rather than text strings.

To use tracing with ETW, see tracing-etw.

How to create and use an event provider

In ETW, an event provider is a software object that generates events. Event controllers set up event logging sessions, and event consumers read and interpret event data. This crate focuses on enabling applications to create event providers.

Add crate dependencies

Add these dependencies to your Cargo.toml file:

[dependencies]
win_etw_macros = "0.1.*"
win_etw_provider = "0.1.*"

win_etw_macros contains the procedural macro that generates eventing code. win_etw_provider contains library code that is called by the code that is generated by win_etw_macros.

Define the event provider and its events

Add a trait definition to your source code and annotate it with the #[trace_logging_provider] macro. The #[trace_logging_provider] macro consumes the trait definition and produces a struct definition with the same name and the same method signatures. (The trait is not available for use as an ordinary trait.):

#[trace_logging_provider]
pub trait MyAppEvents {}

Each event provider must have a unique name and GUID. ETW uses this GUID to identify events that are generated by your provider. Windows contains many event providers, so it is important to be able to select only the events generated by your application. This GUID is also used internally by ETW to identify event metadata (field types), so it is important that your GUID be unique. Otherwise, events from conflicting sources that use the same GUID may be incorrectly interpreted.

Specify your provider name

Unless overridden, #[trace_logging_provider] uses the name of your trait definition as the name of ETW provider ("MyAppEvents" in the example aboved). To specify a different name, specify the name with #[trace_logging_provider(name = "MyCompany.MyComponent")].

Generate a GUID for your event provider

The #[trace_logging_provider] macro will generate a .NET EventSource-compatible name-based GUID if you do not specify a guid parameter. The generated GUID is identical to the one generated by the following PowerShell code: [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).

If you are not interested in using a name-based GUID, you can generate a GUID using a tool like uuidgen (available from Visual Studio command line, or an Ubuntu shell) and specify it with #[trace_logging_provider(guid = "... your guid here ...")].

Add events to your provider

In the trait definition, add method signatures. Each method signature defines an event type. The parameters of each method define the fields of the event type. Only a limited set of field types are supported (enumerated below).

use win_etw_macros::trace_logging_provider;
#[trace_logging_provider(name = "MyCompany.MyComponent")]
pub trait MyAppEvents {
    fn http_request(client_address: &SockAddr, is_https: bool, status_code: u32, status: &str);
    fn database_connection_created(connection_id: u64, server: &str);
    fn database_connection_closed(connection_id: u64);
    // ...
}

Create an instance of the event provider

At initialization time (in your fn main(), etc.), create an instance of the event provider:

let my_app_events = MyAppEvents::new();

Your application should only create a single instance of each event provider, per process. That is, you should create a single instance of your event provider and share it across your process. Typically, an instance is stored in static variable, using a lazy / atomic assignment. There are many crates and types which can support this usage pattern.

Call event methods to report events

To report an event, call one of the methods defined on the event provider. The method will call into ETW to report the event, but there is no guarantee that the event is stored or forwarded; events can be dropped if event buffer resources are scarce.

my_app_events.client_connected(None, &"192.168.0.42:6667".parse(), false, 100, "OK");

Note that all generated event methods have an added first parameters, options: Option<&EventOptions>. This parameter allows you to override per-event parameters, such as the event level and event correlation IDs. In most cases, you should pass None.

Supported field types

Only a limited set of field types are supported.

  • Integer primitives up to 64 bits: i8, i16, i32, i64, u8, u16, u32, u64
  • Floating point primitives: f32, f64
  • Architecture-dependent sizes: usize, isize.
  • Boolean: bool
  • Slices of all of the supported primitives: &[u8], &[u16], etc.
  • Windows FILETIME. The type must be declared exactly as FILETIME; type aliases or fully-qualified paths (such as winapi::shared::minwindef::FILETIME) will not work. The parameter type in the generated code will be win_etw_provider::FILETIME, which is a newtype over u64.
  • std::time::SystemTime is supported, but it must be declared exactly as SystemTime; type aliases or fully-qualified paths (such as std::time::SystemTime) will not work.
  • SockAddr, SockAddrV4, and SockAddrV6 are supported. They must be declared exactly as shown, not using fully-qualified names or type aliases.

How to capture and view events

There are a variety of tools which can be used to capture and view ETW events. The simplest tool is the TraceView tool from the Windows SDK. Typically it is installed at this path: C:\Program Files (x86)\Windows Kits\10\bin\10.0.<xxxxx>.0\x64\traceview.exe, where <xxxxx> is the release number of the Windows SDK.

Run TraceView, then select "File", then "Create New Log Session". Select "Manually Entered GUID or Hashed Name" and enter the GUID that you have assigned to your event provider. Click OK. The next dialog will prompt you to choose a source of WPP format information; select Auto and click OK.

At this point, TraceView should be capturing events (for your assigned GUID) and displaying them in real time, regardless of which process reported the events.

These tools can also be used to capture ETW events:

  • Windows Performance Recorder This tool is intended for capturing system-wide event streams. It is not useful for capturing events for a specific event provider.
  • logman is a command-line tool for managing events.
  • Tracelog

There are other tools, such as the Windows Performance Recorder, which can capture ETW events.

Ideas for improvement

  • Better handling of per-event overrides, rather than using Option<&EventOptions>.

References

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

rust_win_etw's People

Contributors

0b01 avatar adotinthevoid avatar cijothomas avatar daprilik avatar dawid-nowak avatar dpaoliello avatar jstarks avatar kevpar avatar microsoftopensource avatar ms-jihua avatar nated-msft avatar rylev avatar sivadeilra avatar smalis-msft avatar tjones60 avatar zanedp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust_win_etw's Issues

ETW fields containing spaces

I am currently writing an ETW provider using this crate, and need to send an event, with a field that contains spaces.
I saw that the recommended way to send events, is using the trace_logging_provider macro, for example:

  #[trace_logging_provider(
            guid = "ee756ff4-11ee-4682-8e49-c833206e2d90",
            name = "MyTestProvider7"
        )]
        pub trait MyTestProvider7Events {
            fn Event1(data: i32);
        }

        let provider2 = MyTestProvider7Events::new();
       provider2.Event1(None, 1);

Is there any way, which I can have the event field (data in this example), contain spaces? So the actual ETW event that will fire, will have a field named "My data".

Permit fully qualified paths

The README highlights the fact that certain parameters must be exactly correct: e.g., SystemTime instead of std::time::SystemTime. We can support these well known fully qualified paths.

Events using &OsStr don't seem to serialize properly

```rust
use win_etw_macros::trace_logging_provider;
use std::ffi::OsStr;

#[trace_logging_provider(guid = "89d6f527-86d0-4da1-8b4b-b050159a1d96")]
pub trait MyAppEvents {
    #[event(opcode = 1)]
    fn database_connection_created(connection_id: u64, server: &OsStr);
    #[event(opcode = 2)]
    fn database_connection_closed(connection_id: u64);
    // ...
}

fn main() {
    let events = MyAppEvents::new();
    events.database_connection_created(None, 35, OsStr::new("foer"));
    events.database_connection_closed(None, 35);

shows up as
00000022 MyAppEvents 14796 14352 1 0 05\22\2020-20:41:31:607 {"connection_id":35,"server":"fo","meta":{"provider":"MyAppEvents","event":"database_connection_created","time":"2020-05-22T20:41:31.607","cpu":1,"pid":14796,"tid":14352,"channel":11,"level":4,"opcode":1}}

in TraceView. Notice that server comes out as "fo" instead of "foer".

Originally posted here by @jrmuizel

Use .NET EventSource-compatible GUID if no GUID specified

When trace_logging_provider macro is not provided with a GUID, a .NET EventSource-compatible name-based GUID should be used.

Using the uuid crate with feature v5, this code will generate .NET EventSource-compatible GUIDs from the provider name:

use uuid::{uuid, Uuid};

const ETW_EVENT_SOURCE_NAMESPACE: Uuid = uuid!("482c2db2-c390-47c8-87f8-1a15bfc130fb");

fn etw_event_source_guid(provider_name: &str) -> Uuid {
    use sha1_smol::Sha1;

    let provider_bytes: Vec<u8> = provider_name
        .to_uppercase()
        .encode_utf16()
        .flat_map(|x| x.to_be_bytes())
        .collect();

    let mut hasher = Sha1::new();
    hasher.update(ETW_EVENT_SOURCE_NAMESPACE.as_bytes());
    hasher.update(&provider_bytes);

    let mut bytes = [0; 16];
    bytes.copy_from_slice(&hasher.digest().bytes()[..16]);

    // .NET EventSource reads the bytes from the SHA-1 hash as little-endian,
    // but does not set the "variant (aka type)" to the "Microsoft" value which
    // indicates little-endian in a RFC4122-conforming UUID.  The "variant" field
    // ends up being the "random" value copied from the from the SHA-1 hash, instead.
    uuid::Builder::from_bytes_le(bytes)
        .with_version(uuid::Version::Sha1)
        .into_uuid()
}

Some unit tests:

    assert_eq!(
        uuid!("5fefebda-b28e-5a81-d371-cebf3d3ddb41"),
        etw_event_source_guid("YourProviderName")
    );
    assert_eq!(
        uuid!("6f8eac67-f87f-598a-71a0-67e48d8c468d"),
        etw_event_source_guid("YourProviderNameYourProviderName")
    );
    assert_eq!(
        uuid!("5b1bddda-c110-53c9-b030-17aea3bfabd9"),
        etw_event_source_guid("YourProviderNameYourProviderNameYourProviderNameYourProviderName")
    );
    assert_eq!(
        uuid!("0cec4c9d-caa7-5d85-b3bd-c73577f03fd8"),
        etw_event_source_guid("Your.Provider.Name")
    );
    assert_eq!(
        uuid!("32805bf5-e0cc-522a-5f56-20c3e359ed93"),
        etw_event_source_guid("Your.Provider.Name.Your.Provider.Name")
    );
    assert_eq!(
        uuid!("ce5fa4ea-ab00-5402-8b76-9f76ac858fb5"),
        etw_event_source_guid("MyCompany.MyComponent")
    );

Requires two crates to be useable

There might be some use of just win_etw_macros or just win_etw_provider, but most likely you'll need both. It would be much nicer if we took a book from the serde playbook and had one crate reexport the macros (behind a feature flag).

[dependencies]
serde = { version = "1.0", features = ["derive"] }

Add readme for the logger crate

Being able to use this crate through the log facade is very useful, but it's not really documented. We can added a README to the win_etw_logger crate and then link to that from the main README.

UUID feature in 0.1.10 requires "std" feature to build.

Using v0.1.10 of the crate I get a build error in guid.rs when using only the "uuid" feature.
win_etw_provider = { version = "0.1.10", features = ["uuid"] }

Using "uuid" & "std" avoids the break,
win_etw_provider = { version = "0.1.10", features = ["std", "uuid"] }

but "std" was not required in v0.1.9
win_etw_provider = { version = "=0.1.9", features = ["uuid"] }

@NateD-MSFT, Quick looks finds #35 as a possible source for this.

Simple repro

cargo new provider_test
cd provider_test
cargo add win_etw_provider --features=uuid
cargo build

   Compiling win_etw_provider v0.1.10
error[E0599]: no method named `to_owned` found for reference `&[u8; 8]` in the current scope
   --> <snip...>\.cargo\registry\src\index.crates.io-6f17d22bba15001f\win_etw_provider-0.1.10\src\guid.rs:108:29
    |
108 |             data4: fields.3.to_owned(),
    |                             ^^^^^^^^ method not found in `&[u8; 8]`
    |
    = help: items from traits can only be used if the trait is in scope
help: trait `ToOwned` which provides `to_owned` is implemented but not in scope; perhaps you want to import it
    |
1   + use crate::alloc::borrow::ToOwned;
    |

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.