Creating an ETW Provider Step by Step

Event Tracing for Windows is a strongly-typed, high-volume, low-latency tracing framework for Windows. It’s also frustratingly difficult to use. MSDN has a ton of documentation but no simple ‘Hello World’ end-to-end example. Hopefully this post will help you get started writing your own ETW events from your application.

Creating an ETW provider requires the following steps. Each step is described in detail below.

  1. Create the event manifest.
  2. Compile the manifest to produce headers and resources.
  3. Register application as a provider and write events.
  4. Register the event manifest with ETW.

Background

Before diving in, you will need to know a few basics about ETW. First of all ETW is not a very friendly API. It’s fast and extensible but producing and consuming events is challenging. Just getting a program set up to receive events takes almost 100 lines of C code (mostly esoteric flags and parameters). Strangely, producing events is much easier than reading them.

In C# you can use the EventSource and EventListener classes in System.Diagnostics.Tracing but those libraries don’t produce events that are easily read by existing tools like logman or Message Analyzer (which Microsoft deprecated). These libraries aren’t an option from native code so you’re stuck with the C API.

Before you continue, take a look over the following articles to get acquainted with ETW jargon.

Creating the Event Manifest

ETW events are structured – they have a strongly typed schema. This means that you need to plan for what you will be logging before you get started. You could simply define an event with a single string field and use it to log strings (there’s actually a special case for that), but you’d be missing out on one of the best features of ETW.

Define the events

The first thing you need to do is decide what your events should look like. For this tutorial we’ll build a system that logs an event that carries a single integer metric and a string describing the metric.

// simple metric event
struct metric {
    const wchar_t*  metric_name
    int             metric_value;
};

NOTE: this code is not required, this just shows the shape of the data to emit

You can use this event in your code to emit metrics like the number of items processed or the number of milliseconds spent in a call. It’s very simple but it allows you to add arbitrary metrics in new code without recompiling the manifest and you can write a consumer application that can monitor these metrics in real time.

Create the manifest file

Once you know the shape of the data you want to log, you will need to write a manifest. The manifest provides the following information about events.

  • The provider Id – this is unique on the system and is used to identify your provider.
  • The resource file name – this is the absolute path to the location where you will eventually put the binary (DLL or exe) that contains the schema for your events. Yes, you have to include a path to the location where you will put the DLL that contains the compiled version of the manifest in the manifest!
  • The event Id – this Id is unique within the provider
  • The event name and description – these provide human-readable strings that describe the event. These strings can be localized.
  • The event template – this is basically a schema but it can be reused across multiple events. It’s optional if you don’t have any additional data to send.

Or as MSDN puts it:

Metadata fields are combined for each event and defined in an tag that is uniquely associated with an event ID. Event layouts are specified with a tag which describes user-specified context data for each event. The layout can specify data fields such as strings and integers, or other more complex data structures. Template information does not have to be specified for all events; however, if unspecified for a particular event, it will have no user context data.

There are many other things that can be defined in the manifest file but these are the only things we need to know for now.

The manifest file is an XML document that has a pretty complex structure. You can download an XSD and wing it in your favorite editor or you can use ECManGen.exe which provides a questionably (try it, you’ll see) nicer experience for creating and editing manifest files. ECManGen.exe comes with the Windows SDK and on my machine is found here: C:\Program Files (x86)\Windows Kits\8.1\bin\x64\ecmangen.exe. I’m not going to describe how to use this tool – although not beautiful, it’s pretty self-explanatory.

TIP: after creating your provider, create the template first – you will need to reference it when creating the event.

Here’s the finished manifest file. It defines a provider called Hello World Metric Provider with a symbol HELLO_METRIC. The symbol is the name given to the GUID value in the generated headers. The provider has one event called SIMPLE_METRIC with an event Id of 1 and a schema defined by template Simple Metric. The Simple Metric template has a Unicode string field called metric_name and an integer field called metric_value.

Notice the resource and message file names. helloworld.dll will need to be generated and added to the Windows folder so other applications will be able to find the schema.

helloworldmetric.man

<?xml version="1.0" encoding="UTF-16"?>
<instrumentationManifest
    xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events eventman.xsd"
    xmlns="http://schemas.microsoft.com/win/2004/08/events"
    xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:trace="http://schemas.microsoft.com/win/2004/08/events/trace">
    <instrumentation>
        <events>
            <provider
                name="Hello World Metric Provider"
                guid="{06A2EA53-FC6C-42E5-9176-18749AB2CA13}"
                symbol="HELLO_METRIC"
                resourceFileName="c:\Windows\helloworld.dll"
                messageFileName="c:\Windows\helloworld.dll">
                <events>
                    <event
                        symbol="SIMPLE_METRIC"
                        value="1"
                        version="1"
                        template="Simple Metric"
                        message="$(string.Hello World Metric Provider.event.1.message)">
                    </event>
                </events>
                <templates>
                    <template tid="Simple Metric">
                        <data
                            name="metric_name"
                            inType="win:UnicodeString"
                            outType="xs:string">
                        </data>
                        <data
                            name="metric_value"
                            inType="win:Int32"
                            outType="xs:int">
                        </data>
                    </template>
                </templates>
            </provider>
        </events>
    </instrumentation>
    <localization>
        <resources culture="en-US">
            <stringTable>
                <string
                    id="Hello World Metric Provider.event.1.message"
                    value="A simple metric">
                </string>
            </stringTable>
        </resources>
    </localization>
</instrumentationManifest>

Note: the file encoding must match the encoding specified in the XML tag.

Compile the Event Manifest

Once you have a manifest defined you need to compile it. This will produce a header file that has the provider GUID and event descriptors defined as well as a resource that needs to be included in a binary which will be registered with ETW as the source for schemas for schemas for events from your provider. You can also produce C# files that can log your events. The generated code is… functional (read: it’s gross).

To compile the manifest, you need the mc.exe Message Compiler tool. Message compiler is installed with Visual Studio and is found here on my machine: C:\Program Files (x86)\Windows Kits\8.1\bin\x64\mc.exe. Specify the -um parameter which will generate macros for logging your events. This is optional, but without it, you’ll have to manage registering the provider and logging events yourself.

mc.exe -um helloworldmetric.man

This will compile the manifest and generate the resources and headers that you need to include when generating the helloworld.dll.

  • helloworldmetric.h – defines the provider guid and event descriptors. If -um was specified, also contains generated helper macros to register/unregister the provider as well as log events.
  • helloworldmetric.rc – a resource script that includes the compiled bin files.
  • helloworldmetricTEMP.BIN – the compiled schema for your events. This is a binary file that the ETW system will use to unpack the events logged by your provider.
  • MSG00001.bin – contains the localized strings from the manifest.

From Visual Studio

You can include these files directly into a project or generate them during the build. The simplest way is to add a pre-build step that runs mc.exe to generate the files. You can specify parameters to mc that let you control where generated files are written. Then you’ll have to manually modify the project file to include the generated files. Just remember that the output of the build must eventually be written to the path specified in the manifest.

In my sample project, I use a batch file to run mc.exe and rc.exe to produce the files above and compile the resource file. In the linker options for the project, specify the .res file generated by rc.exe as an “Additional Dependency”. Once built, you can verify that your executable has the manifest embedded by opening the .dll or .exe in Visual Studio. In the resources you should see a resource name starting with WEVT_.

From command line

If you don’t want to set up a project, you have everything you need at this point to build the helloworld.dll. You need to compile the resource file using Resource Compiler found in the same directory as mc.exe. Finally, you’ll have to link the compiled resource into a DLL using link.exe (in the Visual Studio bin folder).

From a Visual Studio Developer Prompt (which has the PATH set up already)

mc.exe `helloworldmetric.man`
rc.exe `helloworldmetric.rc`
link.exe  /dll /noentry /machine:x64 helloworldmetric.res /OUT:helloworld.dll

Registering the Provider and Writing Events

Now that you have the generated header, you can register your program as a provider. This basically tells ETW that you’ll be writing events as this provider (based on its GUID). You might not have considered this, but because the program gets to pick which providers and events to log, it means that any program can log as any provider. This makes sense for things like .NET were any program can log the .NET events like garbage collections or context switches. It also means that you can write applications to simulate other providers emitting events which can be useful for testing or spoofing events.

Any program can log events as any provider.

You’ll need to include Windows.h, Evnprov.h and the header from the generated manifest in the code you want to log from.

#include 
#include 
#include "helloworldmetric.h"

void do_work(int i)
{
    // actually log an event
    EventWriteSIMPLE_METRIC(L"test event", i);
}

int main(int argc, char** argv)
{
    // register this program with ETW
    EventRegisterHello_World_Metric_Provider();

    for (int i = 0; i < 10; ++i) {
        do_work(i);
    }

    // unregister this program with ETW
    EventUnregisterHello_World_Metric_Provider(); 

    return 0;
}

Register the Manifest

Now that you have some code that writes an ETW event, you can start to listen to the provider. Run Message Analyzer and listen to the provider by specifying the GUID from the manifest (you’ll have to add a Custom Provider). When you run the test program, you’ll see your events but they won’t have a schema associated with them. This is because the manifest has not been registered with ETW.

To register the manifest, you’ll need to use WevtUtil.exe with the im parameter.

wevtutil.exe im helloworldmetric.man

Remember the helloworld.dll? This is when you actually need the file specified in the manifest to exist. The compiled resources themselves don’t actually contain the path so you can update the paths in the manifest before registering the event, the DLL just has to exist when you run wevtutil.exe.

Now if you run Message Analyzer, you’ll find ‘Hello World Metric Provider’ in the list of system providers. If you listen to that provider, and run the test program, you’ll see the events are received but have a schema now. Huzzah!

If you want to modify the manifest, you must unregister the manifest first using the um parameter before reinstalling.

wevtutil.exe um helloworldmetric.man

Wrap up

Getting set up to log and consume manifest events is complicated, but once you’ve done it a few times it’s not too bad. Using ETW allows you to log events that you can dump to files, the Windows Event Log or even consume the traces in real-time from other processes. You can build powerful watchdog/monitor systems this way or use existing tools like Message Analyzer to collect and analyze trace data.

UPDATE: Microsoft has deprecated the Message Analyzer. Their suggestion is to use something like WireShark. I haven’t tried that, but I have found a few extensions for WireShark which claim to process ETW. Your best bet for visualization may be to write your own tooling with KrabsETW or EventTrace in .NET.


Additional Resources

21 thoughts on “Creating an ETW Provider Step by Step

  1. Thank you very much. I did as your blog instructed and successfully got the event generated by the sample program. However I still have an question that the parameter you passed when generating event as follows:
    // actually log an event
    EventWriteSIMPLE_METRIC(L”test event”, i);
    I couldn’t find anything about “test event” or i in the resulting etl file opened by a windows performance analyzer.
    Where could I find information about the data we passed?

  2. I am using similar mechanism for writing events from my c application.
    And, have encountered an issue in developing my provider. My events are not seen in Windows event-viewer.

    Any help would be highly appreciated.

    I build my provider (console .exe) using Visual Studio 2017 through my visual-studio C++ project file (its a native C application with no .NET)

    I have troubleshooted several aspects, and seems to me like the resource is not properly linked into my exe.

    querying using wevtutil renders an error that the application doesn’t include the resource.
    See the reference output as below-
    > Wevtutil gp “Test Provider”
    name: Test Provider
    guid: 89xxxxxx-xxxx-xxxx-xxxx-8xxxxb9xxxxb
    Failed to get helpLink property. The specified image file did not contain a resource section.

    In order to embed the resource, I have tried these 2 approaches:

    1. Added “.rc” file in resource view of the project (when i build i do see the output res file in intermediate directories)

    2. compiled .res file as mentioned above using rc.exe and included it in the exe, by adding the ‘.res’ as a linker dependency in project-properties -> Linker -> Input -> Additional Dependencies

    Both the approaches seems to not include the resource as desired.

    • The easiest way to do it is to call rc.exe as a pre-link step to generate the .res file. In your project settings, add the .res file to your linker inputs (your #2).
      Linker > Input > Additional Dependencies > .\path\to\the\file.res

      I verified this works in VS2017 15.9 RTW.

  3. @Kallanreed you saved the day for me, thank you very much. For those of you who want to use VS2017 to do the whole thing here are the instructions.
    mc.exe –um  That will generate the header file
    Start he VS2017 as usual for the C file and add the header file and this what you have to do add the Manifest file
    > Project_Name Properties > Configuration Properties > Linker > Manifest File -> Generate Manifest set to No
    > Project_Name Properties > Configuration Properties > Manifest Tool > Embed Manifest set to Yes
    I used Windows Message Analyzer to check
    New Sessions > Live Trace > Add Providers > Add System Providers > In the search window type “Hello” or 06A the starting of the GUID and you will see it. @Kallanreed however I have not been able to see the actual event using either Message Analyzer or EventViewer. If you can share that detail that would be the icing on the cake.

    • Few thoughts:
      1) You have to set up MMA as an event consumer using a Live Trace _before_ you run the test program. The events don’t “live” anywhere. If nothing is listening, the events will just be dropped.
      2) Are you seeing ANY events at all? Your data won’t be in the main body of the events. There’s a “User Context” member of the main schema where your event data will go. You have to expand user context in order to see your data.
      3) Try using KrabsETW to create a little consumer app. I added a link in the post in the Additional Resources section. It’s easy to use and arguably the fastest way to get up and started with consuming events.

      • My ETW event did not come right in the posting hence sending it here too.

        Having read all the other comments and your answers I am in a better position to explain my question. I have two questions at the very end.

        There is a pre-existing registered provider which spits out ETW events and there is corresponding consumer which takes the event and produces ETL files. All of that is working fine. Now I want to spoof and put my own ETW event. So in your main() program I commented out the EventRegister() and EventUnregister() and in your Manifestation file changed the GUID to my registered Provider’s GUID. I am calling EventWriteSIMPLE_METRIC(L”Fox jumps over the fence”, i); 10 times and was expecting to see 10 new entries in the ETL files. You said if there are no consumers the ETW entries just get dropped down. But I had a active consumer, however the format of my Manifest file might be not compatible to what the consumer expects and they might be the reason why the 10 events did not get logged.

        I checked the current logged in ETW event entries in the ETL files and they have an entry called “Correlation ActivityID:” ( https://github.com/jonwagner/EventSourceProxy/wiki/Managing-Activity-&-Correlation-IDs) which is basically another GUID and that is missing in our Manifest. So that must be the reason the 10 events I logged did not make it to the ETL file. Here is an example of my EWT event.

        0

        0

        1

        0

        0

        0xffffffffffffffff

        24

        MyComputer

        15003

        abcdefgh

        Q1: Do you have an idea where and how to add the Correlation ActivityID in the Manifest?

        Q2: You mentioned to use Message Analyzer to see the events. Could you please give step-by-step as to what to click on the Message Analyzer to see the events?

        Meanwhile I will follow up on all of your recommendation. Thanks for all the prompt replies.

      • This is old, but I’ll try to answer for future’s sake.
        My guess is that your event match the manifest of the existing provider so when you log it, the consumer doesn’t know how to parse it.
        I’m not sure what your goal is here. You can certainly spoof events for another provider, but you’ll need to write the events correctly (using the other provider’s manifest.)

  4. @Kallanreed, some clarification please. Assume I have it like this
    EventWriteSIMPLE_METRIC(L”Hello World”, i); So this event is written to 10 times and then I close the provider. Now where did these event end up? Which ETL file? So if I were to use EvenViewer and look at that ETL file and search for your GUID or the words “Hello World” I should see 10 entries right?
    One more question please … Assume there is a Provider with a GUID guidxyz and there is a consumer who is looking for events from this provider with guidxyz. Now I want to create events targeted to the Consumer who is looking for events from guidxyz, how can I do that? I will use EventWrite(RegHandle, EventDescriptor, UserDataCount,UserData); Basically how do I get RegHandle? EventDescriptior I guess can be locally constructed using the ID just sequential numbers. RegHandle would refer the Provider whose GUID is guidxyz. I would be grateful if you can spare me the time and clarify. Thanks

  5. Having read all the other comments and your answers I am in a better position to explain my question. I have two questions at the very end.
    There is a pre-existing registered provider which spits out ETW events and there is corresponding consumer which takes the event and produces ETL files. All of that is working fine. Now I want to spoof and put my own ETW event. So in your main() program I commented out the EventRegister() and EventUnregister() and in your Manifestation file changed the GUID to my registered Provider’s GUID. I am calling EventWriteSIMPLE_METRIC(L”Fox jumps over the fence”, i); 10 times and was expecting to see 10 new entries in the ETL files. You said if there are no consumers the ETW entries just get dropped down. But I had a active consumer, however the format of my Manifest file might be not compatible to what the consumer expects and they might be the reason why the 10 events did not get logged.
    I checked the current logged in ETW event entries in the ETL files and they have an entry called “Correlation ActivityID:” (https://github.com/jonwagner/EventSourceProxy/wiki/Managing-Activity-&-Correlation-IDs) which is basically another GUID and that is missing in our Manifest. So that must be the reason the 10 events I logged did not make it to the ETL file. Here is an example of my EWT event.

    0
    0
    1
    0
    0
    0xffffffffffffffff

    24

    MyComputer

    15003

    abcdefgh

    Q1: Do you have an idea where and how to add the Correlation ActivityID in the Manifest?
    Q2: You mentioned to use Message Analyzer to see the events. Could you please give step-by-step as to what to click on the Message Analyzer to see the events?
    Meanwhile I will follow up on all of your recommendation. Thanks for all the prompt replies.

    • 1) You can’t log arbitrary events in ETW. You have a provider with a GUID. It has events and schemas registered with the system. You can only log a) events with IDs from the manifest b) for an given ID, the event schema must match the registered schema.
      What you’re trying to do doesn’t make sense. You set up something to listen to event A from provider XYZ and then expect it to work when you send it some random event. What are you trying to do at a higher level?

      2) There are ways to extract the manifest schema registered with a given provider. If you’re trying to do that, you should see about finding the schema for the events you’re trying to write and make your own manifest. If all you want to do is write your own events to a different provider, you can just create your events manually. Just create the event data that matches the expected schema (it’s just a memory buffer after all) and send it. You don’t even need to mess around with manifests or anything to do that.

      3) This post is _really_ old, I most likely won’t have time to work on an MMA tutorial for you. It’s not hard to figure out though. You just need to play with it.

    • You don’t have to change anything. ETW intentionally separates logging from what happens with the events. If you want an ETL file, use logman.exe or something similar and have it log the events from your provider guid to a file.

  6. I mean can I generate.etl file without using any external tool like logman or xperf etc. I basically want to use the event tracing stuff as my logger. Instead of outputting a log file, it outputs etl file.

  7. No. If you want to save the events to a file, you have to set up a trace session to receive the events. You can do this programmatically, but it’s outside of the scope of the blog post. Look up StartTrace and EnableTrace on MSDN.

  8. This is great, but I was having trouble understanding why the event log and message analyser were only providing binary data. Turns out if you don’t log to a ‘channel’, no MS app will try to parse the logged data. Adding a ‘channels’ element cribbed from the MSDN documentation, then adding a UserData referencing %1 and %2 let the message analyser show the data in the event, which makes everythng much more useful.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s