diff options
Diffstat (limited to 'src/jaegertracing/opentelemetry-cpp/exporters/etw')
20 files changed, 8046 insertions, 0 deletions
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/etw/BUILD new file mode 100644 index 000000000..c2328ed4e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/BUILD @@ -0,0 +1,69 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "etw_exporter", + hdrs = glob([ + "include/opentelemetry/exporters/etw/*.h", + ]), + includes = ["include"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = ["etw"], + deps = [ + "//api", + "//sdk/src/trace", + "@github_nlohmann_json//:json", + ], +) + +cc_test( + name = "etw_provider_test", + srcs = ["test/etw_provider_test.cc"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = [ + "etw", + "test", + ], + deps = [ + ":etw_exporter", + "@com_google_googletest//:gtest_main", + "@github_nlohmann_json//:json", + ], +) + +cc_test( + name = "etw_tracer_test", + srcs = ["test/etw_tracer_test.cc"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = [ + "etw", + "test", + ], + deps = [ + ":etw_exporter", + "@com_google_googletest//:gtest_main", + "@github_nlohmann_json//:json", + ], +) + +cc_test( + name = "etw_logger_test", + srcs = ["test/etw_logger_test.cc"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = [ + "etw", + "test", + ], + deps = [ + ":etw_exporter", + "@com_google_googletest//:gtest_main", + "@github_nlohmann_json//:json", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/etw/CMakeLists.txt new file mode 100644 index 000000000..47791b4ab --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/CMakeLists.txt @@ -0,0 +1,63 @@ +add_library(opentelemetry_exporter_etw INTERFACE) + +target_include_directories( + opentelemetry_exporter_etw + INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + +set_target_properties(opentelemetry_exporter_etw PROPERTIES EXPORT_NAME + etw_exporter) + +target_link_libraries(opentelemetry_exporter_etw + INTERFACE nlohmann_json::nlohmann_json) +if(nlohmann_json_clone) + add_dependencies(opentelemetry_exporter_etw nlohmann_json::nlohmann_json) +endif() + +install( + TARGETS opentelemetry_exporter_etw + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/etw + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h") + +if(BUILD_TESTING) + add_executable(etw_provider_test test/etw_provider_test.cc) + add_executable(etw_tracer_test test/etw_tracer_test.cc) + add_executable(etw_logger_test test/etw_logger_test.cc) + + add_executable(etw_perf_test test/etw_perf_test.cc) + + target_link_libraries(etw_provider_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + target_link_libraries(etw_tracer_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + target_link_libraries(etw_logger_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + target_link_libraries( + etw_perf_test benchmark::benchmark ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + gtest_add_tests( + TARGET etw_provider_test + TEST_PREFIX exporter. + TEST_LIST etw_provider_test) + gtest_add_tests( + TARGET etw_tracer_test + TEST_PREFIX exporter. + TEST_LIST etw_tracer_test) + gtest_add_tests( + TARGET etw_logger_test + TEST_PREFIX exporter. + TEST_LIST etw_logger_test) + +endif() # BUILD_TESTING diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/README.md b/src/jaegertracing/opentelemetry-cpp/exporters/etw/README.md new file mode 100644 index 000000000..29dc3de43 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/README.md @@ -0,0 +1,226 @@ +# Getting Started with OpenTelemetry C++ SDK and ETW exporter on Windows + +Event Tracing for Windows (ETW) is an efficient kernel-level tracing facility +that lets you log kernel or application-defined events to a log file. You can +consume the events in real time or from a log file and use them to debug an +application or to determine where performance issues are occurring in the +application. + +OpenTelemetry C++ SDK ETW exporter allows the code instrumented using +OpenTelemetry API to forward events to out-of-process ETW listener, for +subsequent data recording or forwarding to alternate pipelines and flows. +Windows Event Tracing infrastructure is available to any vendor or application +being deployed on Windows. + +## API support + +These are the features planned to be supported by ETW exporter: + +- [x] OpenTelemetry Tracing API and SDK headers are **stable** and moving + towards GA. +- [ ] OpenTelemetry Logging API is work-in-progress, pending implementation of + [Latest Logging API spec + here](https://github.com/open-telemetry/oteps/pull/150) +- [ ] OpenTelemetry Metrics API is not implemented yet. + +Implementation of OpenTelemetry C++ SDK ETW exporter on Windows OS is `header +only` : + +- full definitions of all macros, functions and classes comprising the library +are visible to the compiler in a header file form. +- implementation does not need to be separately compiled, packaged and installed + in order to be used. + +All that is required is to point the compiler at the location of the headers, +and then `#include` the header files into the application source. Compiler's +optimizer can do a much better job when all the library's source code is +available. Several options below may be turned on to optimize the code with the +usage of standard C++ library, Microsoft Guidelines Support library, Google +Abseil Variant library. Or enabling support for non-standard features, such as +8-bit byte arrays support that enables performance-efficient representation of +binary blobs on ETW wire. + +## Example project + +The following include directories are required, relative to the top-level source +tree of OpenTelemetry C++ repo: + +- api/include/ +- exporters/etw/include/ +- sdk/include/ + +Code that instantiates ETW TracerProvider, subsequently obtaining a Tracer bound +to `OpenTelemetry-ETW-Provider`, and emitting a span named `MySpan` with +attributes on it, as well as `MyEvent` within that span. + +```cpp + +#include <map> +#include <string> + +#include "opentelemetry/exporters/etw/etw_tracer_exporter.h" + +using namespace OPENTELEMETRY_NAMESPACE; +using namespace opentelemetry::exporter::etw; + +// Supply unique instrumentation name (ETW Provider Name) here: +std::string providerName = "OpenTelemetry-ETW-Provider"; + +exporter::etw::TracerProvider tp; + +int main(int argc, const char* argv[]) +{ + // Obtain a Tracer object for instrumentation name. + // Each Tracer is associated with unique TraceId. + auto tracer = tp.GetTracer(providerName, "TLD"); + + // Properties is a helper class in ETW namespace that is otherwise compatible + // with Key-Value Iterable accepted by OpenTelemetry API. Using Properties + // should enable more efficient data transfer without unnecessary memcpy. + + // Span attributes + Properties attribs = + { + {"attrib1", 1}, + {"attrib2", 2} + }; + + // Start Span with attributes + auto span = tracer->StartSpan("MySpan", attribs); + + // Emit an event on Span + std::string eventName = "MyEvent1"; + Properties event = + { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"} + }; + span->AddEvent(eventName, event); + + // End Span. + span->End(); + + // Close the Tracer on application stop. + tracer->CloseWithMicroseconds(0); + + return 0; +} +``` + +Note that different `Tracer` objects may be bound to different ETW destinations. + +## Build options and Compiler Defines + +While including OpenTelemetry C++ SDK with ETW exporter, the customers are in +complete control of what options they would like to enable for their project +using `Preprocessor Definitions`. + +These options affect how "embedded in application" OpenTelemetry C++ SDK code is +compiled: + +| Name | Description | +|---------------------|------------------------------------------------------------------------------------------------------------------------| +| HAVE_CPP_STDLIB | Use STL classes for API surface. This option requires at least C++17. C++20 is recommended. Some customers may benefit from STL library provided with the compiler instead of using custom OpenTelemetry `nostd::` implementation due to security and performance considerations. | +| HAVE_GSL | Use [Microsoft GSL](https://github.com/microsoft/GSL) for `gsl::span` implementation. Library must be in include path. Microsoft GSL claims to be the most feature-complete implementation of `std::span`. It may be used instead of `nostd::span` implementation in projects that statically link OpenTelemetry SDK. | +| HAVE_TLD | Use ETW/TraceLogging Dynamic protocol. This is the default implementation compatible with existing C# "listeners" / "decoders" of ETW events. This option requires an additional optional Microsoft MIT-licensed `TraceLoggingDynamic.h` header. | + +## Debugging + +### ETW TraceLogging Dynamic Events + +ETW supports a mode that is called "Dynamic Manifest", where event may contain +strongly-typed key-value pairs, with primitive types such as `integer`, +`double`, `string`, etc. This mode requires `TraceLoggingDynamic.h` header. +Please refer to upstream repository containining [Microsoft TraceLogging Dynamic +framework](https://github.com/microsoft/tracelogging-dynamic-windows) licensed +under [MIT +License](https://github.com/microsoft/tracelogging-dynamic-windows/blob/main/LICENSE). + +Complete [list of ETW +types](https://docs.microsoft.com/en-us/windows/win32/wes/eventmanifestschema-outputtype-complextype#remarks). + +OpenTelemetry C++ ETW exporter implements the following type mapping: + +| OpenTelemetry C++ API type | ETW type | +|----------------------------|-----------------| +| bool | xs:byte | +| int (32-bit) | xs:int | +| int (64-bit) | xs:long | +| uint (32-bit) | xs:unsignedInt | +| uint (64-bit) | xs:unsignedLong | +| double | xs:double | +| string | win:Utf8 | + +Support for arrays of primitive types is not implemented yet. + +Visual Studio 2019 allows to use `View -> Other Windows -> Diagnostic Events` to +capture events that are emitted by instrumented application and sent to ETW +provider in a live view. Instrumentation name passed to `GetTracer` API above +corresponds to `ETW Provider Name`. If Instrumentation name contains a GUID - +starts with a curly brace, e.g. `{deadbeef-fade-dead-c0de-cafebabefeed}` then +the parameter is assumed to be `ETW Provider GUID`. + +Click on `Settings` and add the provider to monitor either by its Name or by +GUID. In above example, the provider name is `OpenTelemetry-ETW-Provider`. +Please refer to Diagnostic Events usage instructions +[here](https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-diagnostics-how-to-monitor-and-diagnose-services-locally#view-service-fabric-system-events-in-visual-studio) +to learn more. Note that running ETW Listener in Visual Studio requires +Elevation, i.e. Visual Studio would prompt you to confirm that you accept to run +the ETW Listener process as Administrator. This is a limitation of ETW +Listeners, they must be run as privileged process. + +### ETW events encoded in MessagePack + +OpenTelemetry ETW exporter optionally allows to encode the incoming event +payload using [MessagePack](https://msgpack.org/index.html) compact binary +protocol. ETW/MsgPack encoding requires +[nlohmann/json](https://github.com/nlohmann/json) library to be included in the +build of OpenTelemetry ETW exporter. Any recent version of `nlohmann/json` is +compatible with ETW exporter. For example, the version included in +`third_party/nlohmann-json` directory may be used. + +There is currently **no built-in decoder available** for this format. However, +there is ongoing effort to include the ETW/MsgPack decoder in +[Azure/diagnostics-eventflow](https://github.com/Azure/diagnostics-eventflow) +project, which may be used as a side-car listener to forward incoming +ETW/MsgPack events to many other destinations, such as: + +- StdOutput (console output) +- HTTP (json via http) +- Application Insights +- Azure EventHub +- Elasticsearch +- Azure Monitor Logs + +And community-contributed exporters: + +- Google Big Query output +- SQL Server output +- ReflectInsight output +- Splunk output + +[This PR](https://github.com/Azure/diagnostics-eventflow/pull/382) implements +the `Input adapter` for OpenTelemetry ETW/MsgPack protocol encoded events for +Azure EventFlow. + +Other standard tools for processing ETW events on Windows OS, such as: + +- [PerfView](https://github.com/microsoft/perfview) +- [PerfViewJS](https://github.com/microsoft/perfview/tree/main/src/PerfViewJS) + +will be augmented in future with support for ETW/MsgPack encoding. + +## Addendum + +This document needs to be supplemented with additional information: + +- [ ] mapping between OpenTelemetry fields and concepts and their corresponding + ETW counterparts +- [ ] links to E2E instrumentation example and ETW listener +- [ ] Logging API example +- [ ] Metrics API example (once Metrics spec is finalized) +- [ ] example how ETW Listener may employ OpenTelemetry .NET SDK to 1-1 + transform from ETW events back to OpenTelemetry flow +- [ ] links to NuGet package that contains the source code of SDK that includes + OpenTelemetry SDK and ETW exporter diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/LICENSE b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/LICENSE new file mode 100644 index 000000000..cfc21fd2c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/LICENSE @@ -0,0 +1,23 @@ +TraceLogging Dynamic for Windows + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/TraceLoggingDynamic.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/TraceLoggingDynamic.h new file mode 100644 index 000000000..17ee108c6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/TraceLoggingDynamic.h @@ -0,0 +1,3458 @@ +/* ++ + +Licensed under the MIT License <http://opensource.org/licenses/MIT>. +SPDX-License-Identifier: MIT +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + TraceLoggingDynamic.h + +Abstract: + + Dynamic TraceLogging Provider API for C++. + +Environment: + + User mode. + +--*/ + +#pragma once +#include <windows.h> +#include <evntprov.h> +#include <stdlib.h> // byteswap + +namespace tld +{ +#pragma region Public interface + + /* + GENERAL: + + - This implementation of manifest-free ETW supports more functionality + than the implementation in TraceLoggingProvider.h, but it also has + higher runtime costs. This implementation is intended for use only when + the set of events is not known at compile-time. For example, + TraceLoggingDynamic.h might be used to implement a library providing + manifest-free ETW to a scripting language like JavaScript or Perl. + - This header is not optimized for direct use by developers adding events + to their code. There is no way to make an optimal solution that + works for all of the intended target users. Instead, this header + provides various pieces that you can build upon to create a user-friendly + implementation of manifest-free ETW tailored for a specific scenario. + + HIGH-LEVEL API: + + The high-level API provides an easy way to get up and running with + TraceLogging ETW events. + + Contents: + - Class: tld::Provider + - Class: tld::Event + - Class: tld::EventBuilder + - Enum: tld::Type + + Basic usage: + - Create a Provider object. + - Check provider.IsEnabled(...) so that you don't do the remaining steps + if nobody is listening for your event. + - Create an Event<std::vector<BYTE>> object. + - Add fields definitions (metadata) and values (data) using methods on + Event. (You are responsible for making sure that the metadata you add + matches the data you add -- the Event object does not validate this.) + - Some methods on the Event object return EventBuilder objects, which are + used to build the fields of nested structures. Note that Event inherits + from EventBuilder, so if you write a function that accepts an + EventBuilder&, it will also accept an Event&. + - Once you've added all data and metadata, call event.Write() to send the + event to ETW. + + LOW-LEVEL API: + + The low-level API provides components that you can mix and match to build + your own solution when the high-level components don't precisely meet your + needs. For example, you might use the high-level Provider class to manage + your REGHANDLE and the ETW callbacks, but you might use + EventMetadataBuilder and EventDataBuilder directly instead of using Event + so that you can cache event definitions. + + Contents: + - Function: tld::RegisterProvider + - Function: tld::UnregisterProvider + - Function: tld::GetGuidForName + - Function: tld::SetInformation + - Class: tld::ProviderMetadataBuilder + - Class: tld::EventMetadataBuilder + - Class: tld::EventDataBuilder + - Struct: tld::EventDescriptor + - Class: tld::ByteArrayWrapper + - Enum: tld::InType + - Enum: tld::OutType + - Enum: tld::ProviderTraitType + - Function: tld::WriteEvent + - Function: tld::WriteEventEx + - Function: tld::PushBackAsUtf8 + - Function: tld::MakeType + - Macro: TLD_HAVE_EVENT_SET_INFORMATION + - Macro: TLD_HAVE_EVENT_WRITE_EX + + Notes: + - EventDataDescCreate is a native ETW API from <evntprov.h>. It is not + part of this API, but you may want to use it to initialize + EVENT_DATA_DESCRIPTOR structures instead of doing it directly. + - If you directly initialize your EVENT_DATA_DESCRIPTOR structures instead + of using EventDataDescCreate, be sure to properly initialize the + EVENT_DATA_DESCRIPTOR.Reserved field (e.g. by setting it to 0). + Initializing the Reserved field is NOT optional. (EventDataDescCreate + does correctly initialize the Reserved field.) + - When the API asks for a byte-buffer, you can use std::vector<BYTE> or + another similar type. If you don't want to use a vector, the provided + ByteArrayWrapper type allows you to use your own allocation strategy for + the buffer. + - By default, TraceLogging events have Id=0 and Version=0, indicating + that the event does not have an assigned Id. However, events can have + Id and Version assigned (typically assigned manually). If you don't want + to manage event IDs, set both Id and Version to 0. If you do assign + IDs to your events, the Id must be non-zero and there should be a + one-to-one mapping between {provider.Id + event.Id + event.Version} and + {event metadata}. In other words, any event with a given non-zero + Id+Version combination must always have exactly the same event metadata. + (Note to decoders: this can be used as an optimization, but do not rely + on providers to follow this rule.) + - TraceLogging events default to channel 11 (WINEVENT_CHANNEL_TRACELOGGING). + This channel has no effect other than to mark the event as TraceLogging- + compatible. Other channels can be used, but channels other than 11 will + only work if the provider is running on a version of Windows that + understands TraceLogging (Windows 7sp1 with latest updates, Windows 8.1 + with latest updates, Windows 10, or later). If your provider is running + on a version of Windows that does not understand TraceLogging and you use + a channel other than 11, the resulting events will not decode correctly. + + Low-level provider management: + - Use tld::ProviderMetadataBuilder to build a provider metadata blob. + - If you don't have a specific provider GUID already selected, use + tld::GetGuidForName to compute your provider GUID. + - Use tld::RegisterProvider to open the REGHANDLE. + - Use the REGHANDLE in calls to tld::WriteEvent. + - Use tld::UnregisterProvider to close the REGHANDLE. + + Low-level event management: + - Use tld::EventMetadataBuilder to build an event metadata blob. + - Use tld::Type values to define field types, or create non-standard + types using tld::MakeType, tld::InType, and tld::OutType. + - Use tld::EventDataBuilder to build an event data blob. + - Create an EVENT_DESCRIPTOR for your event. + - Optionally use the tld::EventDescriptor wrapper class to simplify + initialization of EVENT_DESCRIPTOR structures. + - Allocate an array of EVENT_DATA_DESCRIPTORs. Size should be two more + than you need for your event payload (the first two are reserved for the + provider and event metadata). + - Use EventDataDescCreate to fill in the data descriptor array (skipping + the first two) to reference your event payload data. + - Use tld::WriteEvent to write your event. + */ + + /* + class Provider (high-level API): + + Manages an ETW REGHANDLE and the provider metadata blob. + - Constructor creates the provider metadata blob and registers the provider. + Note that this includes configuring ETW callbacks. + - Destructor unregisters the provider and frees the metadata blob. + - IsEnabled APIs provide very efficient checks for whether an event needs + to be written. + - Write APIs call WriteEvent, automatically providing the correct REGHANDLE + and provider metadata blobs. + + Typical usage: + + tld::Provider provider("ProviderName", ...); + if (provider.IsEnabled(eventLevel, eventKeywords)) + { + (prepare event); + provider.Write(...); + } + + It is not a problem to call provider.Write without checking + provider.IsEnabled(...), but it is more efficient to check + provider.IsEnabled(...) so that you can skip the process of preparing the + event if the event is not enabled. + */ + class Provider; + + /* + class Event (high-level API): + + Manages the data and metadata for an event. You create an Event object, + set properties, add field definitions, add field values, and then Write + the event. + + ByteVectorTy will usually be std::vector<BYTE>, though other similar + types could be used instead. + + Typical usage: + + if (provider.IsEnabled(eventLevel, eventKeywords)) + { + Event event(provider, szEventName, eventLevel, eventKeywords); + (add fields definitions and values); + event.Write(); + } + + You can reuse an Event object by calling event.Reset(...). + */ + template<class ByteVectorTy> + class Event; + + /* + class EventBuilder (high-level API): + + This class exposes an interface for adding field metadata/data to an event. + The Event class inherits from EventBuilder so that it can expose this + interface. In addition, each call to eventBuilder.AddStruct(...) returns a + new EventBuilder that can be used to add fields to a nested structure. + You won't directly create instances of this type -- you'll use instances + returned by Event.AddStruct or by EventBuilder.AddStruct. + + You can use EventBuilder& as the type of a parameter to a function, and + then pass either an Event or an EventBuilder to that function (allowing + for recursive processing of event fields). + + Note that order is important but not checked when using multiple Event or + EventBuilder objects. Fields are always added to the end of the overall + event, regardless of which Event or EventBuilder you use to add the field. + The separate Event and EventBuilder objects are used for the sole purpose + of tracking the number of fields in each struct. + + Assume that you have obtained EventBuilder object b from object a, i.e. + EventBuilder b = a.AddStruct(...). You must complete all operations using + b before resuming any operations using a. If this rule is not followed, + fields may end up nesting in unexpected ways. + */ + template<class ByteVectorTy> + class EventBuilder; + + /* + enum Type (high-level API and low-level API): + + Types for event fields, corresponding to a combination of TDH_INTYPE and + TDH_OUTTYPE values. The Type enumeration contains predefined combinations + of InType and OutType that are known to work well and are recognized by + decoders such as TDH and xperf. + + Advanced use: It is possible to create other Type values by combining + values from the InType and OutType enumerations using the tld::MakeType + function. (Valid Type values consist of one InType combined with one + OutType.) However, custom combinations not already present in the Type + enumeration are unlikely to be recognized by xperf or other decoders, + and will usually end up being decoded as if they had used OutTypeDefault. + + When providing payload for a field, the payload data must match the InType. + Payload is always packed tightly with no alignment or padding. + + **** + + How to pack single values: + + Primitive values (int, GUID) are packed directly, with no padding or + alignment. Just reserve sizeof(value) bytes in the buffer, then memcpy the + value into the buffer. Note that there should be no alignment or padding + between items -- the payload is always tightly-packed. + + NUL-terminated strings are also simply memcpy'ed into the buffer. Be sure + to include the NUL termination when reserving space and copying the value. + There is no special encoding reserved for a NULL string. The encoding + helpers in this header simply treat a NULL string the same as an empty + string, i.e. AddString((char*)NULL) is the same as AppendString(""). + + Binary, CountedString, and CountedAnsiString scalars are all encoded as a + UINT16 byte-count followed by the string data. In this case, no NUL + termination should be included. Remember that the size is a byte count, + not a character count. + + **** + + How to pack arrays: + + Assume you have a function Pack(payloadVec, item) that correctly appends an + item to the end of payloadVec. + + Packing a variable-length array of N items is done by appending N (encoded + as UINT16) to payloadVec followed by calling Pack(...) N times. + + Packing a constant-length array of N items is done by calling Pack(...) N + times. The value of N is encoded in the metadata (it was provided in the + field's declaration) so it does not need to be provided in the payload. + + Note: while this header allows you to create arrays of anything, you + should avoid creating arrays of the following, as they might not decode + correctly: + - TypeNone (or anything based on InTypeNull). + - TypeBinary (or anything based on InTypeBinary). + + It is ok to create an array of nested structures where fields in the + structure have TypeNone or TypeBinary. + */ + enum Type : UINT16; + + /* + Used for composing Type values. + Normally you'll use a precomposed Type... value, but you can compose a + custom Type... value by combining a value from InType with a value from + OutType using MakeType. + + The InType tells the pipeline how to encode the field's payload (primarily + how to determine payload size). In addition, each InType has an implicit + default formatting behavior that will be used if not overridden by an + OutType. + + The comments for each InType value indicate the payload encoding rules and + the OutTypes that are most likely to be usable with this InType. + */ + enum InType : UINT8; + + /* + Used for composing Type values. + Normally you'll use a precomposed Type... value, but you can compose a + custom Type... value by combining a value from InType with a value from + OutType using MakeType. + + The OutType gives the pipeline a formatting hint that the trace consumer + may use to override the InType's default formatting behavior. If no + OutType is specified (i.e. if OutTypeDefault is used) or if the trace + consumer does not recognize the specified InType+OutType combination, the + trace consumer will perform default formatting based solely on InType. + + The comments for each OutType indicate the InTypes for which the OutType + might have valid semantics. However, most trace consumer only recognize a + small subset of the "valid" InType+OutType combinations, so even if the + semantics are valid, the OutType might still be ignored by the trace + consumer. The most commonly-supported combinations are the combinations + with corresponding precomposed Type... values. + */ + enum OutType : UINT8; + + /* + enum ProviderTraitType (low-level API): + + The type of a provider trait. Used when building up provider metadata. + */ + enum ProviderTraitType : UINT8; + + /* + macro TLD_HAVE_EVENT_SET_INFORMATION (low-level API): + + Configuration macro for controlling the behavior of SetInformation. + + Not all versions of Windows support the EventSetInformation API. The + tld::SetInformation wrapper provides default behavior that works well in + most cases, but may not meet the needs of all users. The configuration + macros can be used to control how the tld::SetInformation function invokes + the EventSetInformation API. (Note that tld::SetInformation is called + automatically by the Provider class's constructor to register the + provider's traits with ETW.) + + When TLD_HAVE_EVENT_SET_INFORMATION is not defined and WINVER < 0x0602: + SetInformation uses GetModuleHandleExW+GetProcAddress to find the + EventSetInformation function. If found, SetInformation calls it and + returns whatever EventSetInformation returns. Otherwise, SetInformation + returns an error. + + When TLD_HAVE_EVENT_SET_INFORMATION is not defined and WINVER >= 0x0602: + SetInformation directly calls the EventSetInformation(...) function and + returns whatever EventSetInformation returns. + + If you set TLD_HAVE_EVENT_SET_INFORMATION: + - If TLD_HAVE_EVENT_SET_INFORMATION == 0, SetInformation always returns an + error. + - If TLD_HAVE_EVENT_SET_INFORMATION == 1, SetInformation calls + EventSetInformation. + - If TLD_HAVE_EVENT_SET_INFORMATION == 2, SetInformation locates + EventSetInformation via GetProcAddress. + */ +#ifndef TLD_HAVE_EVENT_SET_INFORMATION + #if WINVER < 0x0602 // If targeting Windows before Windows 8 + #define TLD_HAVE_EVENT_SET_INFORMATION 2 // Find "EventSetInformation" via GetModuleHandleExW/GetProcAddress + #else + #define TLD_HAVE_EVENT_SET_INFORMATION 1 // Directly invoke EventSetInformation(...) + #endif +#endif // TLD_HAVE_EVENT_SET_INFORMATION + + /* + macro TLD_HAVE_EVENT_WRITE_EX (low-level API): + + Configuration macro for enabling/disabling WriteEventEx, WriteEx. + + If TLD_HAVE_EVENT_WRITE_EX is not defined and WINVER < 0x0601, or + if TLD_HAVE_EVENT_WRITE_EX is defined as 0, + then the WriteEventEx, WriteEx, and related APIs will not be available. + + If TLD_HAVE_EVENT_WRITE_EX is not defined and WINVER >= 0x0601, or + if TLD_HAVE_EVENT_WRITE_EX is defined as 1, + then the WriteEventEx, WriteEx, and related APIs will be available. + */ +#ifndef TLD_HAVE_EVENT_WRITE_EX + #if WINVER < 0x0601 // If targeting Windows before Windows 7 + #define TLD_HAVE_EVENT_WRITE_EX 0 + #else + #define TLD_HAVE_EVENT_WRITE_EX 1 + #endif +#endif // TLD_HAVE_EVENT_WRITE_EX + + + /* + class ProviderMetadataBuilder (low-level API): + + Helper for building the provider metadata blob. + The type provided for ByteBufferTy must behave like a std::vector<BYTE>. + + Example usage: + + std::vector<BYTE> byteVector; + tld::ProviderMetadataBuilder<std::vector<BYTE>> builder(byteVector); + builder.Begin(szProviderName); // Note: calls byteVector.clear(). + if (builder.End()) // Returns false if the metadata is too large. + { + // byteVector now contains a valid provider metadata blob. + } + */ + template<class ByteBufferTy> + class ProviderMetadataBuilder; + + /* + class EventMetadataBuilder (low-level API): + + Helper for building the event metadata blob, including field definitions. + The type provided for ByteBufferTy must behave like a std::vector<BYTE>. + + Example usage: + + std::vector<BYTE> byteVector; + tld::EventMetadataBuilder<std::vector<BYTE>> builder(byteVector); + builder.Begin(eventName); // Note: calls byteVector.clear(). + builder.AddField("Field1", TypeBOOL32); // top-level field + auto struct1 = builder.AddStruct("Struct1"); + struct1.AddField("Nested1", TypeINT32) // nested field + struct1.AddField("Nested2", TypeANSISTRING); // nested field + if (builder.End()) // Returns false if the metadata is too large. + { + // byteVector now contains a valid event metadata blob. + } + */ + template<class ByteBufferTy> + class EventMetadataBuilder; + + /* + class EventDataBuilder (low-level API): + + Helpers for adding event data to an event data blob. + The type provided for ByteBufferTy must behave like a std::vector<BYTE>. + */ + template<class ByteBufferTy> + class EventDataBuilder; + + /* + class EventDescriptor (low-level API): + + Derives from EVENT_DESCRIPTOR. Adds convenient constructor and Reset + members. Contains an event's Id/Version/Level/Opcode/Task/Keyword + settings. Use of this class is optional - you can use this class if you + want to use the constructor or Reset methods, or you can use + EVENT_DESCRIPTOR directly if you don't need the convenience methods. + */ + struct EventDescriptor; + + /* + class ByteArrayWrapper (low-level API): + + Adapter that allows a regular BYTE array (stack-allocated or + manually-allocated) to be used as the buffer in MetadataBuilder + operations. If the underlying BYTE array is too small, or if no array is + provided at all, this adapter will stop writing data beyond the end of the + array, but will continue to "accept" data (and will increment it size + member accordingly). This allows you to determine the size of the required + array so that the operation can be retried with a correctly-sized array. + + Example usage: + + // Create a wrapper with a default (0-length) buffer. + tld::ByteArrayWrapper wrapper; + + // Create a metadata builder attached to the wrapper. + tld::ProviderMetadataBuilder<tld::ByteArrayWrapper> builder(wrapper); + + // Determine the size needed for provider metadata. + builder.Begin(providerName); + builder.End(); + + // Allocate a real buffer and reset the wrapper to use it. + BYTE* pBuffer = new BYTE[wrapper.size()]; + wrapper.reset(pBuffer, wrapper.size()); + + // Redo the operation with the real buffer. + builder.Begin(providerName); + if (builder.End()) + { + // pBuffer now contains a valid provider metadata blob. + } + */ + class ByteArrayWrapper; + + /* + function RegisterProvider (low-level API): + + Calls EventRegister. If EventRegister succeeds, calls EventSetInformation + to register your provider traits with ETW. Use this instead of calling + EventRegister directly to ensure that your provider traits are properly + registered. + */ + inline HRESULT RegisterProvider( + _Out_ REGHANDLE* phProvider, + _In_ GUID const* pProviderId, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_opt_ PENABLECALLBACK pEnableCallback = 0, + _In_opt_ void* pEnableCallbackContext = 0); + + /* + function UnregisterProvider (low-level API): + + Calls EventUnregister. + */ + inline HRESULT UnregisterProvider( + REGHANDLE hProvider); + + /* + function WriteEvent (low-level API): + + Calls EventWriteTransfer. You must provide 2 more data descriptors than + are required for your data. The first two elements in pDataDescriptors + will be used for provider and event metadata. The actual payload (if any) + is expected to be in the remaining elements of pDataDescriptors. + + Example usage: + + tld::EventDescriptor eventDescriptor( + level, + opcode, + task, + keywords); + EVENT_DATA_DESCRIPTOR pDataDescriptors[cFields + 2]; + for (int i = 0; i < cFields; i++) + { + EventDataDescCreate(&pDataDescriptors[i + 2], ...); + } + tld::WriteEvent( + hProvider, + eventDescriptor, + pProviderMetadata, + pEventMetadata, + cFields + 2, + pDataDescriptors); + */ + inline HRESULT WriteEvent( + _In_ REGHANDLE hProvider, + _In_ EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + _In_opt_ GUID const* pActivityId = 0, + _In_opt_ GUID const* pRelatedActivityId = 0); + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + /* + function WriteEventEx (low-level API): + Calls EventWriteEx. Otherwise works like the WriteEvent function. + */ + inline HRESULT WriteEventEx( + _In_ REGHANDLE hProvider, + _In_ EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + ULONG64 filter = 0, + ULONG flags = 0, + _In_opt_ GUID const* pActivityId = 0, + _In_opt_ GUID const* pRelatedActivityId = 0); + +#endif // TLD_HAVE_EVENT_WRITE_EX + + /* + function SetInformation (low-level API): + + Wrapper for ETW API EventSetInformation. + If TraceLoggingDynamic.cpp was compiled to require Win8 or later (as + determined by WINVER), this directly calls EventSetInformation. Otherwise, + this attempts to dynamically load the EventSetInformation API via + GetModuleHandleExW. + + The behavior of this function (e.g. to override the WINVER check) can be + adjusted by setting the TLD_HAVE_EVENT_SET_INFORMATION macro as described + below. + */ + inline HRESULT SetInformation( + _In_ REGHANDLE hProvider, + EVENT_INFO_CLASS informationClass, + _In_reads_bytes_opt_(cbInformation) void* pbInformation, + ULONG cbInformation); + + /* + function GetGuidForName (low-level API): + + Hashes a provider name to generate a GUID. Uses the same GUID generation + algorithm as System.Diagnostics.Tracing.EventSource (from .NET) and + Windows.Foundation.Diagnostics.LoggingChannel (from Windows Runtime). + */ + inline GUID GetGuidForName( + _In_z_ char const* szUtf8Name); + + /* + function GetGuidForName (low-level API): + + Hashes a provider name to generate a GUID. Uses the same GUID generation + algorithm as .NET System.Diagnostics.Tracing.EventSource and Windows + Runtime Windows.Foundation.Diagnostics.LoggingChannel. + */ + inline GUID GetGuidForName( + _In_z_ wchar_t const* szUtf16Name); + + /* + function PushBackAsUtf8 (low-level API): + + Transcodes a NUL-terminated UTF-16LE string to a NUL-terminated UTF-8 + string. Intended for use when appending provider/event/field names to + metadata buffers. + */ + template<class ByteBufferTy> + void PushBackAsUtf8( + ByteBufferTy& buffer, + _In_z_ wchar_t const* szUtf16); + +#pragma endregion + + namespace detail + { +#pragma region Internal macros + +#define _tld_MAKE_TYPE(inType, outType) \ + static_cast<::tld::Type>((inType) | (static_cast<int>(outType) << 8)) + +#ifndef TLD_DEBUG + #if (DBG || defined(DEBUG) || defined(_DEBUG)) && !defined(NDEBUG) + #define TLD_DEBUG 1 + #else // DBG + #define TLD_DEBUG 0 + #endif // DBG +#endif // TLD_DEBUG + +#ifndef _tld_ASSERT + #if TLD_DEBUG + #define _tld_ASSERT(exp, str) ((void)(!(exp) ? (__annotation(L"Debug", L"AssertFail", L"TraceLogging: " L#exp L" : " L##str), DbgRaiseAssertionFailure(), 0) : 0)) + #else // TLD_DEBUG + #define _tld_ASSERT(exp, str) ((void)0) + #endif // TLD_DEBUG +#endif // _tld_ASSERT + +#pragma endregion + +#pragma region MetadataBuilderBase + + template<class ByteBufferTy> + class MetadataBuilderBase + { + ByteBufferTy& m_buffer; + + MetadataBuilderBase(MetadataBuilderBase const&); // = delete + void operator=(MetadataBuilderBase const&); // = delete + + protected: + + explicit MetadataBuilderBase(ByteBufferTy& buffer) + : m_buffer(buffer) + { + return; + } + + void BaseBegin() + { + m_buffer.clear(); + AddU16(0); // Metadata size filled in by BaseEnd + } + + bool BaseEnd() + { + auto size = m_buffer.size(); + _tld_ASSERT(2 <= size, "Begin was not called"); + SetU16(0, static_cast<UINT16>(size)); + return size < 32768; + } + + void AddBytes( + _In_reads_bytes_(cb) void const* p, + unsigned cb) + { + auto pb = static_cast<UINT8 const*>(p); + for (unsigned i = 0; i != cb; i++) + { + m_buffer.push_back(pb[i]); + } + } + + void AddString( + _In_z_ char const* szUtf8) + { + for (unsigned i = 0;; i++) + { + m_buffer.push_back(szUtf8[i]); + if (szUtf8[i] == 0) + { + break; + } + } + } + + void AddString( + _In_z_ wchar_t const* szUtf16) + { + PushBackAsUtf8(m_buffer, szUtf16); + } + + void AddU8( + UINT8 val) + { + m_buffer.push_back(val);; + } + + void AddU16( + UINT16 val) + { + m_buffer.push_back(static_cast<UINT8>(val)); + m_buffer.push_back(static_cast<UINT8>(val >> 8)); + } + + UINT32 GetBookmark() + { + return static_cast<UINT32>(m_buffer.size()); + } + + void SetU8( + UINT32 bookmark, + UINT8 val) + { + _tld_ASSERT(bookmark < m_buffer.size(), "bookmark out of range"); + m_buffer[bookmark] = val; + } + + void SetU16( + UINT32 bookmark, + UINT16 val) + { + _tld_ASSERT(1 <= bookmark + 1, "bookmark out of range"); + SetU8(bookmark + 0, static_cast<UINT8>(val)); + SetU8(bookmark + 1, static_cast<UINT8>(val >> 8)); + } + + void IncrementU7( + UINT32 bookmark) + { + _tld_ASSERT(bookmark < m_buffer.size(), "bookmark out of range"); + UINT8 result; + result = ++m_buffer[bookmark]; + _tld_ASSERT((result & 0x7f) != 0, "too many fields in struct"); + } + + public: + + ByteBufferTy& GetBuffer() + { + return m_buffer; + } + + ByteBufferTy const& GetBuffer() const + { + return m_buffer; + } + }; + +#pragma endregion + +#pragma region Sha1ForNonSecretPurposes + + /* + Implements the SHA1 hashing algorithm. Note that this implementation is + for hashing public information. Do not use this code to hash private data, + as this implementation does not take any steps to avoid information + disclosure (i.e. does not scrub its buffers). + */ + class Sha1ForNonSecretPurposes + { + Sha1ForNonSecretPurposes(Sha1ForNonSecretPurposes const&); // = delete + void operator=(Sha1ForNonSecretPurposes const&); // = delete + + UINT64 m_length; // Total message length in bits + unsigned m_pos; // Length of current chunk in bytes + UINT32 m_results[5]; + UINT32 m_w[80]; // Workspace + + public: + + Sha1ForNonSecretPurposes() + : m_length() + , m_pos() + { + m_results[0] = 0x67452301; + m_results[1] = 0xEFCDAB89; + m_results[2] = 0x98BADCFE; + m_results[3] = 0x10325476; + m_results[4] = 0xC3D2E1F0; + } + + void Append( + _In_reads_bytes_(cbInput) void const* pInput, + unsigned cbInput) + { + for (unsigned i = 0; i != cbInput; i++) + { + Append(static_cast<UINT8 const*>(pInput)[i]); + } + } + + void Append(UINT8 input) + { + reinterpret_cast<UINT8*>(m_w)[m_pos++] = input; + if (64 == m_pos) + { + Drain(); + } + } + + _Ret_bytecount_(20) UINT8* Finish() + { + UINT64 totalBitCount = m_length + 8 * m_pos; + Append(0x80); + while (m_pos != 56) + { + Append(0x00); + } + + *reinterpret_cast<UINT64*>(m_w + 14) = _byteswap_uint64(totalBitCount); + Drain(); + + for (unsigned i = 0; i != 5; i++) + { + m_results[i] = _byteswap_ulong(m_results[i]); + } + + return reinterpret_cast<UINT8*>(m_results); + } + + private: + + void Drain() + { + for (unsigned i = 0; i != 16; i++) + { + m_w[i] = _byteswap_ulong(m_w[i]); + } + + for (unsigned i = 16; i != 80; i++) + { + m_w[i] = _rotl((m_w[i - 3] ^ m_w[i - 8] ^ m_w[i - 14] ^ m_w[i - 16]), 1); + } + + UINT32 a = m_results[0]; + UINT32 b = m_results[1]; + UINT32 c = m_results[2]; + UINT32 d = m_results[3]; + UINT32 e = m_results[4]; + + for (unsigned i = 0; i != 20; i++) + { + UINT32 const k = 0x5A827999; + UINT32 f = (b & c) | ((~b) & d); + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + for (unsigned i = 20; i != 40; i++) + { + const UINT32 k = 0x6ED9EBA1; + UINT32 f = b ^ c ^ d; + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + for (unsigned i = 40; i != 60; i++) + { + const UINT32 k = 0x8F1BBCDC; + UINT32 f = (b & c) | (b & d) | (c & d); + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + for (unsigned i = 60; i != 80; i++) + { + const UINT32 k = 0xCA62C1D6; + UINT32 f = b ^ c ^ d; + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + m_results[0] += a; + m_results[1] += b; + m_results[2] += c; + m_results[3] += d; + m_results[4] += e; + + m_length += 512; // 64 bytes == 512 bits + m_pos = 0; + } + }; + +#pragma endregion + +#pragma region CopyUtfCodePoint + + /* + Transcodes one code point from UTF-8 to UTF-16LE. + + Note that this function requires the input buffer to be nul-terminated. + This function may try to read several bytes from the input buffer, and the + nul-termination is required to ensure that it doesn't read off the end of + the valid buffer when decoding what appears to be a multi-byte code point. + + The UTF-8 validation is very permissive -- anything that is not valid + UTF-8 is treated as if it were ISO-8859-1 (i.e. the BYTE value is cast + to wchar_t and assumed to be a valid Unicode code point). This usually + works well - it has a reasonable probability of doing what the developer + expects even when the input is CP1252/Latin1 instead of UTF-8, and doesn't + fail too badly when it doesn't do what the developer expects. + + pchUtf16Output: + Buffer that receives a single code point, encoded in UTF-16-LE as one or + two 16-bit wchar_t values. + + pszUtf8Input: + Pointer into a nul-terminated UTF-8 byte sequence. On entry, this points + to the next char to be consumed. This function will advance the pointer + past the consumed data. + + returns: + The number of 16-bit wchar_t values added to pchUtf16Output (one or two). + */ + static unsigned CopyUtfCodePoint( + _Out_writes_to_(2, return) wchar_t* pchUtf16Output, + char const*& pszUtf8Input) + { + unsigned cchOutput = 1; + unsigned ch = static_cast<unsigned char>(*pszUtf8Input); + pszUtf8Input += 1; + if (ch <= 0xbf) + { + // 0x01..0x7f: ASCII - pass through. + // 0x80..0xbf: Invalid - pass through. + } + else if ((pszUtf8Input[0] & 0xc0) != 0x80) + { + // Invalid trail byte - pass through. + } + else if (ch <= 0xdf) + { + // 0xc0..0xdf: Two-byte encoding for 0x0080..0x07ff. + unsigned ch2 = ((ch & 0x1f) << 6) + | (pszUtf8Input[0] & 0x3f); + if (0x0080 <= ch2) + { + ch = ch2; + pszUtf8Input += 1; + } + } + else if ((pszUtf8Input[1] & 0xc0) != 0x80) + { + // Invalid trail byte - pass through. + } + else if (ch <= 0xef) + { + // 0xe0..0xef: Three-byte encoding for 0x0800..0xffff. + unsigned ch2 = ((ch & 0x0f) << 12) + | ((pszUtf8Input[0] & 0x3f) << 6) + | (pszUtf8Input[1] & 0x3f); + if (0x0800 <= ch2) + { + ch = ch2; + pszUtf8Input += 2; + } + } + else if ((pszUtf8Input[2] & 0xc0) != 0x80) + { + // Invalid trail byte - pass through. + } + else if (ch <= 0xf4) + { + // 0xf0..0xf4: Four-byte encoding for 0x010000..0x10ffff. + unsigned ch2 = ((ch & 0x07) << 18) + | ((pszUtf8Input[0] & 0x3f) << 12) + | ((pszUtf8Input[1] & 0x3f) << 6) + | (pszUtf8Input[2] & 0x3f); + ch2 -= 0x10000; + if (ch2 <= 0xfffff) + { + // Decode into surrogate pair (2 wchar_t units) + pchUtf16Output[1] = static_cast<wchar_t>(0xdc00 | (ch2 & 0x3ff)); + ch = 0xd800 | (ch2 >> 10); + pszUtf8Input += 3; + cchOutput = 2; + } + } + + pchUtf16Output[0] = static_cast<wchar_t>(ch); + return cchOutput; + } + + /* + Copies one code point of UTF-16LE data. + + Note that this function requires the input buffer to be nul-terminated. + This function may try to read several words from the input buffer, and the + nul-termination is required to ensure that it doesn't read off the end of + the valid buffer when copying what appears to be a surrogate pair. + + pchUtf16Output: + Buffer that receives a single code point, encoded in UTF-16-LE as one or + two 16-bit wchar_t values. + + pszUtf16Input: + Pointer into a nul-terminated UTF-16-LE wchar_t sequence. On entry, this + points to the next wchar_t to be consumed. This function will advance the + pointer past the consumed data. + + returns: + The number of 16-bit wchar_t values added to pchUtf16Output (one or two). + */ + static unsigned CopyUtfCodePoint( + _Out_writes_to_(2, return) wchar_t* pchUtf16Output, + wchar_t const*& pszUtf16Input) + { + unsigned cchOutput = 1; + wchar_t ch = *pszUtf16Input; + pszUtf16Input += 1; + if (0xd800 <= ch && ch < 0xdc00 && + 0xdc00 <= *pszUtf16Input && *pszUtf16Input < 0xe000) + { + pchUtf16Output[1] = *pszUtf16Input; + pszUtf16Input += 1; + cchOutput = 2; + } + pchUtf16Output[0] = ch; + return cchOutput; + } + +#pragma endregion + +#pragma region GetGuidForNameImpl + + __declspec(selectany) extern UINT32 const NamespaceValue[] = { + 0xB22D2C48, 0xC84790C3, 0x151AF887, 0xFB30C1Bf + }; + + template<class CharTy> + GUID GetGuidForNameImpl( + _In_z_ CharTy const* szUtfName) + { + /* + Algorithm: + szNameUpperBE = ByteSwap(ToUpper(szName)); + hash = SHA1(namespaceBytes + szNameUpperBE); // Does not include zero-termination in hash. + hash[7] = (hash[7] & 0x0F) | 0x50; + guid = First16Bytes(hash); + */ + + WCHAR buffer[16]; + unsigned const cchBuffer = ARRAYSIZE(buffer); + Sha1ForNonSecretPurposes sha1; + + sha1.Append(NamespaceValue, sizeof(NamespaceValue)); + if (szUtfName[0] != 0) + { + CharTy const* pszName = szUtfName; + unsigned iBuffer = 0; + for (;;) + { + // CopyUtfCodePoint may add up to 2 wchar_t units to buffer. + iBuffer += CopyUtfCodePoint(buffer + iBuffer, pszName); + _tld_ASSERT(iBuffer <= cchBuffer, "buffer overflow"); + + bool const bDone = *pszName == 0; + if (bDone || cchBuffer - 1 <= iBuffer) + { + #pragma warning(suppress: 26035) // string is counted, not nul-terminated. + LCMapStringEx( + LOCALE_NAME_INVARIANT, + LCMAP_UPPERCASE | LCMAP_BYTEREV, + buffer, + iBuffer, + buffer, + iBuffer, + NULL, + NULL, + 0); + sha1.Append(buffer, iBuffer * 2); + + if (bDone) + { + break; + } + + iBuffer = 0; + } + } + } + + UINT8* pSha1 = sha1.Finish(); + + // Set high 4 bits of octet 7 to 5, as per RFC 4122 + pSha1[7] = (pSha1[7] & 0x0f) | 0x50; + + return *reinterpret_cast<GUID*>(pSha1); + } + +#pragma endregion + } + // namespace detail + +#pragma region Enumerations + + enum InType : UINT8 + { + /* + A field with no value (0-length payload). + Arrays of InTypeNull are illegal. + */ + InTypeNull, + + /* + A nul-terminated CHAR16 string. + Useful OutTypes: Xml, Json. + */ + InTypeUnicodeString, + + /* + A nul-terminated CHAR8 string. + Useful OutTypes: Xml, Json, Utf8. + */ + InTypeAnsiString, + + /* + INT8. + */ + InTypeInt8, + + /* + UINT8. + Useful OutTypes: String, Boolean, Hex. + */ + InTypeUInt8, + + /* + INT16. + */ + InTypeInt16, + + /* + UINT16. + Useful OutTypes: String, Hex, Port. + */ + InTypeUInt16, + + /* + INT32. + Useful OutTypes: HResult. + */ + InTypeInt32, + + /* + UINT32. + Useful OutTypes: Pid, Tid, IPv4, Win32Error, NTStatus. + */ + InTypeUInt32, + + /* + INT64. + */ + InTypeInt64, + + /* + UINT64. + */ + InTypeUInt64, + + /* + FLOAT. + */ + InTypeFloat, + + /* + DOUBLE. + */ + InTypeDouble, + + /* + BOOL. + */ + InTypeBool32, + + /* + UINT16 byte-count followed by binary data. + Useful OutTypes: IPv6, SocketAddress. + Arrays of InTypeBinary are not supported. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + InTypeBinary, + + /* + GUID. + */ + InTypeGuid, + + /* + Not supported. Use InTypePointer. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + InTypePointer_PlatformSpecific, + + /* + FILETIME. + */ + InTypeFileTime, + + /* + SYSTEMTIME. + */ + InTypeSystemTime, + + /* + SID. Size is determined from the number of subauthorities. + */ + InTypeSid, + + /* + INT32. + */ + InTypeHexInt32, + + /* + INT64. + */ + InTypeHexInt64, + + /* + UINT16 byte-count followed by UTF-16 data. + */ + InTypeCountedString, + + /* + UINT16 byte-count followed by MBCS data. + */ + InTypeCountedAnsiString, + + /* + This field has no value by itself, but it causes the decoder to treat + the next N fields as part of this field. The value of N is taken from + the field's OutType. + + Note that it is valid to have an array of structs. The array length + will apply to the entire group of fields contained in the array. + + The Struct type is special. Do not use it directly. Use helper APIs to + create nested structures. + */ + InTypeStruct, + + /* + INT_PTR. + InTypeIntPtr is an alias for either InTypeInt32 or InTypeInt64. + */ + InTypeIntPtr = sizeof(void*) == 8 ? InTypeInt64 : InTypeInt32, + + /* + UINT_PTR. + InTypeUIntPtr is an alias for either InTypeUInt32 or InTypeUInt64. + */ + InTypeUIntPtr = sizeof(void*) == 8 ? InTypeUInt64 : InTypeUInt32, + + /* + LPVOID. + InTypePointer is an alias for either InTypeHexInt32 or InTypeHexInt64. + */ + InTypePointer = sizeof(void*) == 8 ? InTypeHexInt64 : InTypeHexInt32, + + /* + InType must fit in 5 bits. + */ + InTypeMask = 31 + }; + + enum OutType : UINT8 + { + OutTypeDefault = 0x00, + OutTypeNoPrint = 0x01, + OutTypeString = 0x02, // affects 8-bit and 16-bit integral types. + OutTypeBoolean = 0x03, // affects 8-bit and 32-bit integral types. + OutTypeHex = 0x04, // affects integral types. + OutTypePid = 0x05, // affects 32-bit integral types. + OutTypeTid = 0x06, // affects 32-bit integral types. + OutTypePort = 0x07, // affects 16-bit integral types. + OutTypeIPv4 = 0x08, // affects 32-bit integral types. + OutTypeIPv6 = 0x09, // affects arrays of 8-bit integral types, e.g. UInt8[]. + OutTypeSocketAddress = 0x0a, // affects arrays of 8-bit integral types, e.g. UInt8[]. + OutTypeXml = 0x0b, // affects strings; affects arrays of 8-bit and 16-bit integral types. + OutTypeJson = 0x0c, // affects strings; affects arrays of 8-bit and 16-bit integral types. + OutTypeWin32Error = 0x0d,// affects 32-bit integral types. + OutTypeNTStatus = 0x0e, // affects 32-bit integral types. + OutTypeHResult = 0x0f, // affects 32-bit integral types. + OutTypeFileTime = 0x10, // affects 64-bit integral types. + OutTypeSigned = 0x11, // affects integral types. + OutTypeUnsigned = 0x12, // affects integral types. + OutTypeDateTimeCultureInsensitive = 0x21, // affects FileTime, SystemTime. + OutTypeUtf8 = 0x23, // affects AnsiString types. + OutTypePkcs7WithTypeInfo = 0x24, // affects binary types. + OutTypeCodePointer = 0x25, // affects UInt32, UInt64, HexInt32, HexInt64. + OutTypeDateTimeUtc = 0x26, // affects FileTime, SystemTime. + OutTypeMask = 0x7f // OutType must fit into 7 bits. + }; + + enum Type : UINT16 + { + /* + Field with no data (empty). 0-length payload. + Can only be used on scalar fields. Illegal for arrays. + NOTE: Not well-supported by decoders. + */ + TypeNone = InTypeNull, + + /* + Encoding assumes 0-terminated CHAR16 string. + Formatting treats as UTF-16LE string. + */ + TypeUtf16String = InTypeUnicodeString, + + /* + Encoding assumes 0-terminated CHAR8 string. + Formatting treats as MBCS string. + */ + TypeMbcsString = InTypeAnsiString, + + /* + Encoding assumes nul-terminated CHAR8 string. + Formatting treats as UTF-8 string. + */ + TypeUtf8String = _tld_MAKE_TYPE(InTypeAnsiString, OutTypeUtf8), + + /* + Encoding assumes 1-byte value. + Formatting treats as signed integer. + */ + TypeInt8 = InTypeInt8, + + /* + Encoding assumes 1-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt8 = InTypeUInt8, + + /* + Encoding assumes 2-byte value. + Formatting treats as signed integer. + */ + TypeInt16 = InTypeInt16, + + /* + Encoding assumes 2-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt16 = InTypeUInt16, + + /* + Encoding assumes 4-byte value. + Formatting treats as signed integer. + */ + TypeInt32 = InTypeInt32, + + /* + Encoding assumes 4-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt32 = InTypeUInt32, + + /* + Encoding assumes 8-byte value. + Formatting treats as signed integer. + */ + TypeInt64 = InTypeInt64, + + /* + Encoding assumes 8-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt64 = InTypeUInt64, + + /* + Encoding assumes 4-byte value. + Formatting treats as Float32. + */ + TypeFloat = InTypeFloat, + + /* + Encoding assumes 8-byte value. + Formatting treats as Float64. + */ + TypeDouble = InTypeDouble, + + /* + Encoding assumes 4-byte value. + Formatting treats as Boolean. + */ + TypeBool32 = InTypeBool32, + + /* + Encoding assumes 2-byte length followed by binary data. + Formatting treats as binary blob. + Arrays of TypeBinary are not supported. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + TypeBinary = InTypeBinary, + + /* + Encoding assumes 16-byte value. + Formatting treats as GUID. + */ + TypeGuid = InTypeGuid, + + /* + Encoding assumes 8-byte value. + Formatting treats as FILETIME. + */ + TypeFileTime = InTypeFileTime, + + /* + Encoding assumes 16-byte value. + Formatting treats as SYSTEMTIME. + */ + TypeSystemTime = InTypeSystemTime, + + /* + Encoding assumes 16-byte value. + Formatting treats as UTC SYSTEMTIME. + */ + TypeSystemTimeUtc = _tld_MAKE_TYPE(InTypeSystemTime, OutTypeDateTimeUtc), + + /* + Encoding assumes SID. Length is determined from number of subauthorities. + Formatting treats as a SID. + */ + TypeSid = InTypeSid, + + /* + Encoding assumes 4-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt32 = InTypeHexInt32, + + /* + Encoding assumes 8-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt64 = InTypeHexInt64, + + /* + Encoding assumes 2-byte bytecount followed by CHAR16 data. + Formatting treats as UTF-16LE string. + */ + TypeCountedUtf16String = InTypeCountedString, + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as MBCS string. + */ + TypeCountedMbcsString = InTypeCountedAnsiString, + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as UTF-8 string. + */ + TypeCountedUtf8String = _tld_MAKE_TYPE(InTypeCountedAnsiString, OutTypeUtf8), + + /* + Encoding assumes pointer-sized value. + Formatting treats as signed integer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is an alias for TypeInt32 when compiled for a 32-bit target. + This is an alias for TypeInt64 when compiled for a 64-bit target. + */ + TypeIntPtr = InTypeIntPtr, + + /* + Encoding assumes pointer-sized value. + Formatting treats as unsigned integer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is an alias for TypeUInt32 when compiled for a 32-bit target. + This is an alias for TypeUInt64 when compiled for a 64-bit target. + */ + TypeUIntPtr = InTypeUIntPtr, + + /* + Encoding assumes pointer-sized value. + Formatting treats as hexadecimal unsigned integer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is an alias for TypeHexInt32 when compiled for a 32-bit target. + This is an alias for TypeHexInt64 when compiled for a 64-bit target. + */ + TypePointer = InTypePointer, + + /* + Encoding assumes pointer-sized value. + Formatting treats as code pointer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is a subtype of TypeHexInt32 when compiled for a 32-bit target. + This is a subtype of TypeHexInt64 when compiled for a 64-bit target. + */ + TypeCodePointer = _tld_MAKE_TYPE(InTypePointer, OutTypeCodePointer), + + /* + Encoding assumes 2-byte value. + Formatting treats as UTF-16LE character. + */ + TypeChar16 = _tld_MAKE_TYPE(InTypeUInt16, OutTypeString), + + /* + Encoding assumes 1-byte value. + Formatting treats as ANSI character. + */ + TypeChar8 = _tld_MAKE_TYPE(InTypeUInt8, OutTypeString), + + /* + Encoding assumes 1-byte value. + Formatting treats as Boolean. + */ + TypeBool8 = _tld_MAKE_TYPE(InTypeUInt8, OutTypeBoolean), + + /* + Encoding assumes 1-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt8 = _tld_MAKE_TYPE(InTypeUInt8, OutTypeHex), + + /* + Encoding assumes 2-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt16 = _tld_MAKE_TYPE(InTypeUInt16, OutTypeHex), + + /* + Encoding assumes 4-byte value. + Formatting treats as process identifier. + */ + TypePid = _tld_MAKE_TYPE(InTypeUInt32, OutTypePid), + + /* + Encoding assumes 4-byte value. + Formatting treats as thread identifier. + */ + TypeTid = _tld_MAKE_TYPE(InTypeUInt32, OutTypeTid), + + /* + Encoding assumes 2-byte value. + Formatting treats as big-endian IP port(s). + */ + TypePort = _tld_MAKE_TYPE(InTypeUInt16, OutTypePort), + + /* + Encoding assumes 4-byte value. + Formatting treats as IPv4 address. + */ + TypeIPv4 = _tld_MAKE_TYPE(InTypeUInt32, OutTypeIPv4), + + /* + Encoding assumes 2-byte length followed by binary data. + Formatting treats as IPv6 address. + Arrays of TypeIPv6 are not supported. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + TypeIPv6 = _tld_MAKE_TYPE(InTypeBinary, OutTypeIPv6), + + /* + Encoding assumes 2-byte length followed by binary data. + Formatting treats as SOCKADDR. + Arrays of TypeSocketAddress are not supported. (No error will be + reported, but decoding tools will probably not be able to decode them). + */ + TypeSocketAddress = _tld_MAKE_TYPE(InTypeBinary, OutTypeSocketAddress), + + /* + Encoding assumes nul-terminated CHAR16 string. + Formatting treats as UTF-16LE XML. + */ + TypeUtf16Xml = _tld_MAKE_TYPE(InTypeUnicodeString, OutTypeXml), + + /* + Encoding assumes nul-terminated CHAR8 string. + Formatting treats as UTF-8 XML. + */ + TypeMbcsXml = _tld_MAKE_TYPE(InTypeAnsiString, OutTypeXml), + + /* + Encoding assumes nul-terminated CHAR16 string. + Formatting treats as UTF-16LE JSON. + */ + TypeUtf16Json = _tld_MAKE_TYPE(InTypeUnicodeString, OutTypeJson), + + /* + Encoding assumes nul-terminated CHAR8 string. + Formatting treats as UTF-8 JSON. + */ + TypeMbcsJson = _tld_MAKE_TYPE(InTypeAnsiString, OutTypeJson), + + /* + Encoding assumes 2-byte bytecount followed by CHAR16 data. + Formatting treats as UTF-16LE XML. + */ + TypeCountedUtf16Xml = _tld_MAKE_TYPE(InTypeCountedString, OutTypeXml), + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as UTF-8 XML. + */ + TypeCountedMbcsXml = _tld_MAKE_TYPE(InTypeCountedAnsiString, OutTypeXml), + + /* + Encoding assumes 2-byte bytecount followed by CHAR16 data. + Formatting treats as UTF-16LE JSON. + */ + TypeCountedUtf16Json = _tld_MAKE_TYPE(InTypeCountedString, OutTypeJson), + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as UTF-8 JSON. + */ + TypeCountedMbcsJson = _tld_MAKE_TYPE(InTypeCountedAnsiString, OutTypeJson), + + /* + Encoding assumes 4-byte value. + Formatting treats as Win32 error(s). + */ + TypeWin32Error = _tld_MAKE_TYPE(InTypeUInt32, OutTypeWin32Error), + + /* + Encoding assumes 4-byte value. + Formatting treats as NTSTATUS(s). + */ + TypeNTStatus = _tld_MAKE_TYPE(InTypeUInt32, OutTypeNTStatus), + + /* + Encoding assumes 4-byte value. + Formatting treats as HRESULT(s). + */ + TypeHResult = _tld_MAKE_TYPE(InTypeInt32, OutTypeHResult) + }; + + enum ProviderTraitType : UINT8 + { + ProviderTraitNone = 0, // Invalid value + ProviderTraitGroupGuid = 1 // Data is the group GUID. + }; + +#pragma endregion + +#pragma region EventDescriptor + + /* + Simple wrapper around the ETW EVENT_DESCRIPTOR structure. + Provides a constructor to make initialization easier. + Can be used anywhere an EVENT_DESCRIPTOR is required. + + Notes: + - Channel defaults to 11 (WINEVENT_CHANNEL_TRACELOGGING). Other channels + will only work if the provider is running on a TraceLogging-enabled OS + (Windows 10 or later, or Windows 7 sp1 with updates, or Windows 8.1 with + updates). If the provider is running on an earlier version of Windows and + you use a channel other than 11, the events will not decode correctly. + - If you are not assigning unique Ids for your events, set both Id and + Version to 0. + */ + struct EventDescriptor + : EVENT_DESCRIPTOR + { + explicit EventDescriptor( + UCHAR level = 5, // 5 = WINEVENT_LEVEL_VERBOSE + ULONGLONG keyword = 0, // 0 = no keywords + UCHAR opcode = 0, // 0 = WINEVENT_OPCODE_INFO + USHORT task = 0) // 0 = WINEVENT_TASK_NONE + { + Reset(level, keyword, opcode, task); + } + + void Reset( + UCHAR level = 5, // 5 = WINEVENT_LEVEL_VERBOSE + ULONGLONG keyword = 0, // 0 = no keywords + UCHAR opcode = 0, // 0 = WINEVENT_OPCODE_INFO + USHORT task = 0) // 0 = WINEVENT_TASK_NONE + { + Id = 0; + Version = 0; + Channel = 11; // WINEVENT_CHANNEL_TRACELOGGING + Level = level; + Opcode = opcode; + Task = task; + Keyword = keyword; + } + }; + +#pragma endregion + +#pragma region Support functions + +#pragma warning(push) +#pragma warning(disable: 26018) // PREfast: Potential read overflow. (Analysis is incorrect.) + + template<class ByteBufferTy> + void PushBackAsUtf8( + ByteBufferTy& buffer, + _In_z_ wchar_t const* szUtf16) + { + for (unsigned i = 0;; i++) + { + unsigned ch = szUtf16[i]; + if (ch < 0x80) + { + buffer.push_back(static_cast<UINT8>(ch)); + if (ch == 0) + { + break; + } + } + else if (ch < 0x800) + { + buffer.push_back(static_cast<UINT8>(((ch >> 6)) | 0xc0)); + buffer.push_back(static_cast<UINT8>(((ch)& 0x3f) | 0x80)); + } + else if ( + 0xd800 <= ch && ch < 0xdc00 && + 0xdc00 <= szUtf16[i + 1] && szUtf16[i + 1] < 0xe000) + { + // Decode surrogate pair. + ++i; + ch = 0x010000 + (((ch - 0xd800) << 10) | (szUtf16[i] - 0xdc00)); + buffer.push_back(static_cast<UINT8>(((ch >> 18)) | 0xf0)); + buffer.push_back(static_cast<UINT8>(((ch >> 12) & 0x3f) | 0x80)); + buffer.push_back(static_cast<UINT8>(((ch >> 6) & 0x3f) | 0x80)); + buffer.push_back(static_cast<UINT8>(((ch)& 0x3f) | 0x80)); + } + else + { + buffer.push_back(static_cast<UINT8>(((ch >> 12)) | 0xe0)); + buffer.push_back(static_cast<UINT8>(((ch >> 6) & 0x3f) | 0x80)); + buffer.push_back(static_cast<UINT8>(((ch)& 0x3f) | 0x80)); + } + } + } + +#pragma warning(pop) + + inline ::tld::Type MakeType( + ::tld::InType inType, + ::tld::OutType outType) + { + return _tld_MAKE_TYPE(inType & InTypeMask, outType & OutTypeMask); + } + + _Use_decl_annotations_ + inline GUID GetGuidForName( + wchar_t const* szUtf16Name) + { + return ::tld::detail::GetGuidForNameImpl(szUtf16Name); + } + + _Use_decl_annotations_ + inline GUID GetGuidForName( + char const* szUtf8Name) + { + return ::tld::detail::GetGuidForNameImpl(szUtf8Name); + } + + inline HRESULT RegisterProvider( + _Out_ REGHANDLE* phProvider, + _In_ GUID const* pProviderId, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_opt_ PENABLECALLBACK pEnableCallback, + _In_opt_ void* pEnableCallbackContext) + { + HRESULT hr; + REGHANDLE hProvider = 0; + ULONG status = ::EventRegister( + pProviderId, + pEnableCallback, + pEnableCallbackContext, + &hProvider); + if (status == 0) + { + auto cbMetadata = *reinterpret_cast<UINT16 const*>(pProviderMetadata); + status = tld::SetInformation( + hProvider, + static_cast<EVENT_INFO_CLASS>(2), // EventProviderSetTraits + const_cast<UINT8*>(pProviderMetadata), + cbMetadata); + hr = S_OK; // Ignore any failures from SetInformation. + } + else + { + hr = HRESULT_FROM_WIN32(status); + } + + *phProvider = hProvider; + return hr; + } + + inline HRESULT UnregisterProvider( + REGHANDLE hProvider) + { + ULONG status = ::EventUnregister(hProvider); + return HRESULT_FROM_WIN32(status); + } + + _Use_decl_annotations_ + inline HRESULT SetInformation( + REGHANDLE hProvider, + EVENT_INFO_CLASS informationClass, + void* pvInformation, + ULONG cbInformation) + { + ULONG status; + +#if TLD_HAVE_EVENT_SET_INFORMATION == 1 + +#pragma warning(suppress: 6387) // It's ok for pvInformation to be null if cbInformation is 0. + status = ::EventSetInformation( + hProvider, + informationClass, + pvInformation, + cbInformation); + +#elif TLD_HAVE_EVENT_SET_INFORMATION == 2 + + HMODULE hEventing; + status = ERROR_NOT_SUPPORTED; + if (GetModuleHandleExW(0, L"api-ms-win-eventing-provider-l1-1-0", &hEventing) || + GetModuleHandleExW(0, L"advapi32.dll", &hEventing)) + { + typedef ULONG(WINAPI* PFEventSetInformation)( + _In_ REGHANDLE RegHandle, + _In_ EVENT_INFO_CLASS InformationClass, + _In_reads_bytes_opt_(InformationLength) PVOID EventInformation, + _In_ ULONG InformationLength); + PFEventSetInformation pfEventSetInformation = + (PFEventSetInformation)GetProcAddress(hEventing, "EventSetInformation"); + if (pfEventSetInformation) + { + status = pfEventSetInformation( + hProvider, + informationClass, + pvInformation, + cbInformation); + } + + FreeLibrary(hEventing); + } + +#else // TLD_HAVE_EVENT_SET_INFORMATION == 0 + + (void)hProvider; + (void)informationClass; + (void)pvInformation; + (void)cbInformation; + + status = ERROR_NOT_SUPPORTED; + +#endif // TLD_HAVE_EVENT_SET_INFORMATION + + return HRESULT_FROM_WIN32(status); + } + + _Use_decl_annotations_ + inline HRESULT WriteEvent( + REGHANDLE hProvider, + EVENT_DESCRIPTOR const& eventDescriptor, + UINT8 const* pProviderMetadata, + UINT8 const* pEventMetadata, + ULONG cDataDescriptors, + EVENT_DATA_DESCRIPTOR* pDataDescriptors, + LPCGUID pActivityId, + LPCGUID pRelatedActivityId) + { + _tld_ASSERT(3 <= *(UINT16*)pProviderMetadata, "Invalid provider metadata."); + _tld_ASSERT(4 <= *(UINT16*)pEventMetadata, "Invalid event metadata."); + pDataDescriptors[0].Ptr = (ULONG_PTR)pProviderMetadata; + pDataDescriptors[0].Size = *(UINT16*)pProviderMetadata; + pDataDescriptors[0].Reserved = 2; // Descriptor contains provider metadata. + pDataDescriptors[1].Ptr = (ULONG_PTR)pEventMetadata; + pDataDescriptors[1].Size = *(UINT16*)pEventMetadata; + pDataDescriptors[1].Reserved = 1; // Descriptor contains event metadata. + ULONG status = ::EventWriteTransfer( + hProvider, + &eventDescriptor, + pActivityId, + pRelatedActivityId, + cDataDescriptors, + pDataDescriptors); + return HRESULT_FROM_WIN32(status); + } + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + _Use_decl_annotations_ + inline HRESULT WriteEventEx( + REGHANDLE hProvider, + EVENT_DESCRIPTOR const& eventDescriptor, + UINT8 const* pProviderMetadata, + UINT8 const* pEventMetadata, + ULONG cDataDescriptors, + EVENT_DATA_DESCRIPTOR* pDataDescriptors, + ULONG64 filter, + ULONG flags, + GUID const* pActivityId, + GUID const* pRelatedActivityId) + { + _tld_ASSERT(3 <= *(UINT16*)pProviderMetadata, "Invalid provider metadata."); + _tld_ASSERT(4 <= *(UINT16*)pEventMetadata, "Invalid event metadata."); + pDataDescriptors[0].Ptr = (ULONG_PTR)pProviderMetadata; + pDataDescriptors[0].Size = *(UINT16*)pProviderMetadata; + pDataDescriptors[0].Reserved = 2; // Descriptor contains provider metadata. + pDataDescriptors[1].Ptr = (ULONG_PTR)pEventMetadata; + pDataDescriptors[1].Size = *(UINT16*)pEventMetadata; + pDataDescriptors[1].Reserved = 1; // Descriptor contains event metadata. + ULONG status = ::EventWriteEx( + hProvider, + &eventDescriptor, + filter, + flags, + pActivityId, + pRelatedActivityId, + cDataDescriptors, + pDataDescriptors); + return HRESULT_FROM_WIN32(status); + } + +#endif // TLD_HAVE_EVENT_WRITE_EX + +#pragma endregion + +#pragma region ByteArrayWrapper + + class ByteArrayWrapper + { + UINT8* m_pb; + UINT32 m_cbMax; + UINT32 m_cbCur; + + public: + + ByteArrayWrapper() + { + reset(); + } + + ByteArrayWrapper( + _Out_writes_(cbMax) UINT8* pb, + UINT32 cbMax) + { + reset(pb, cbMax); + } + + void reset() + { + m_pb = 0; + m_cbMax = 0; + m_cbCur = 0; + } + + void reset( + _Inout_updates_bytes_(cbMax) UINT8* pb, + UINT32 cbMax) + { + m_pb = pb; + m_cbMax = cbMax; + m_cbCur = 0; + } + + void clear() + { + m_cbCur = 0; + } + + UINT32 size() const + { + return m_cbCur; + } + + BYTE const& front() const + { + return *m_pb; + } + + BYTE const* data() const + { + return m_pb; + } + + UINT32 capacity() const + { + return m_cbMax; + } + + void push_back(UINT8 val) + { + if (m_cbCur < m_cbMax) + { + m_pb[m_cbCur] = val; + } + + m_cbCur += 1; + } + + UINT8& operator[](UINT32 index) + { + static UINT8 dummy; + return index < m_cbMax + ? m_pb[index] + : dummy; + } + }; + +#pragma endregion + +#pragma region ProviderMetadataBuilder + + template<class ByteBufferTy> + class ProviderMetadataBuilder + : public detail::MetadataBuilderBase<ByteBufferTy> + { + /* + ProviderMetadata = + Size : UINT16; // Size counts the whole data structure, including the Size field. + Name : Nul-terminated UTF-8. + Traits[]: ProviderTrait (parse until you have consumed Size bytes) + + ProviderTrait = + TraitSize: UINT16; + TraitType: UINT8; + TraitData: UINT8[TraitSize - 3]; + */ + + public: + + explicit ProviderMetadataBuilder(ByteBufferTy& buffer) + : detail::MetadataBuilderBase<ByteBufferTy>(buffer) + { + return; + } + + template<class CharTy> + void Begin( + _In_z_ CharTy const* szUtfProviderName) + { + this->BaseBegin(); + this->AddString(szUtfProviderName); + } + + void AddTrait( + ProviderTraitType type, + _In_reads_bytes_(cbData) void const* pData, + unsigned cbData) + { + this->AddU16(static_cast<UINT16>(cbData + 3)); + this->AddU8(static_cast<UINT8>(type)); + this->AddBytes(pData, cbData); + } + + bool End() + { + return this->BaseEnd(); + } + }; + +#pragma endregion + +#pragma region EventMetadataBuilder + + template<class ByteBufferTy> + class EventMetadataBuilder + : public detail::MetadataBuilderBase<ByteBufferTy> + { + /* + EventMetadata = + Size : UINT16; // Size counts the whole data structure, including the Size field. + Tags[] : UINT8 (read bytes until you encounter a byte with high bit unset); + Name : Nul-terminated UTF-8; + Fields[]: FieldMetadata (parse until you get to end of EventMetadata); + + FieldMetadata = + Name : Nul-terminated UTF-8. + InMeta : UINT8. + OutMeta : UINT8 (only present if InMeta & InMetaChain); + Tags[] : UINT8 (only present if OutMeta & OutMetaChain, read bytes until you encounter a byte with high bit unset); + Ccount : UINT16 (only present if InMeta & InMetaCountMask == InMetaCcount); + CbSchema: UINT16 (only present if InMeta & InMetaCountMask == InMetaCustom); + Schema : UINT8[CbSchema] + */ + + static const UINT8 InMetaScalar = 0; + static const UINT8 InMetaCcount = 32; + static const UINT8 InMetaVcount = 64; + static const UINT8 InMetaCustom = 96; + static const UINT8 InMetaCountMask = 96; + static const UINT8 InMetaChain = 128; + static const UINT8 OutMetaChain = 128; + + UINT32 const m_bookmark; + + EventMetadataBuilder( + ByteBufferTy& buffer, + UINT32 bookmark) + : detail::MetadataBuilderBase<ByteBufferTy>(buffer) + , m_bookmark(bookmark) + { + return; + } + + void AddTags(UINT32 tags) + { + _tld_ASSERT(0 == (tags & 0xf0000000), "Tags only supports 28-bit values."); + for (;;) + { + auto val = static_cast<UINT8>(tags >> 21); + if ((tags & 0x1fffff) == 0) + { + this->AddU8(val & 0x7f); + break; + } + + this->AddU8(val | 0x80); + tags = tags << 7; + } + } + + void AddFieldInfo(UINT8 arity, Type type, UINT32 tags) + { + _tld_ASSERT((type & InTypeMask) == (type & 0xff), "InType out of range"); + _tld_ASSERT((type & _tld_MAKE_TYPE(0, OutTypeMask)) == (Type)(type & 0xffffff00), "OutType out of range"); + + UINT8 inMeta = arity | static_cast<UINT8>(type); + UINT8 outMeta = static_cast<UINT8>(type >> 8); + if (tags != 0) + { + this->AddU8(static_cast<UINT8>(inMeta | InMetaChain)); + this->AddU8(static_cast<UINT8>(outMeta | OutMetaChain)); + AddTags(tags); + } + else if (outMeta != 0) + { + this->AddU8(static_cast<UINT8>(inMeta | InMetaChain)); + this->AddU8(static_cast<UINT8>(outMeta)); + } + else + { + this->AddU8(inMeta); + } + + if (m_bookmark != 0) + { + this->IncrementU7(m_bookmark); + } + } + + UINT32 AddStructInfo(UINT8 arity, UINT32 tags) + { + UINT8 inMeta = arity | InTypeStruct; + this->AddU8(static_cast<UINT8>(inMeta | InMetaChain)); + UINT32 bookmark = this->GetBookmark(); + if (tags == 0) + { + this->AddU8(0); + } + else + { + this->AddU8(OutMetaChain); + AddTags(tags); + } + + if (m_bookmark != 0) + { + this->IncrementU7(m_bookmark); + } + + return bookmark; + } + + public: + + explicit EventMetadataBuilder( + ByteBufferTy& buffer) + : detail::MetadataBuilderBase<ByteBufferTy>(buffer) + , m_bookmark(0) + { + return; + } + + EventMetadataBuilder(EventMetadataBuilder& other) + : detail::MetadataBuilderBase<ByteBufferTy>(other.GetBuffer()) + , m_bookmark(other.m_bookmark) + { + return; + } + + EventMetadataBuilder(EventMetadataBuilder&& other) + : detail::MetadataBuilderBase<ByteBufferTy>(other.GetBuffer()) + , m_bookmark(other.m_bookmark) + { + return; + } + + template<class CharTy> + void Begin( + _In_z_ CharTy const* szUtfEventName, + UINT32 eventTags = 0) + { + _tld_ASSERT(m_bookmark == 0, "Must not call Begin on a struct builder"); + this->BaseBegin(); + AddTags(eventTags); + this->AddString(szUtfEventName); + } + + bool End() + { + _tld_ASSERT(m_bookmark == 0, "Must not call End on a struct builder"); + return this->BaseEnd(); + } + + // Note: Do not create structs with 0 fields. + template<class CharTy> + EventMetadataBuilder AddStruct( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaScalar, fieldTags); + return EventMetadataBuilder(this->GetBuffer(), bookmark); + } + + // Note: Do not create structs with 0 fields. + template<class CharTy> + UINT32 AddStructRaw( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaScalar, fieldTags); + return bookmark; + } + + // Note: Do not create structs with 0 fields. + template<class CharTy> + EventMetadataBuilder AddStructArray( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaVcount, fieldTags); + return EventMetadataBuilder(this->GetBuffer(), bookmark); + } + + // Note: Do not use 0 for itemCount. + // Note: Do not create structs with 0 fields. + template<class CharTy> + EventMetadataBuilder AddStructFixedArray( + _In_z_ CharTy const* szUtfStructName, + UINT16 itemCount, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaCcount, fieldTags); + this->AddU16(itemCount); + return EventMetadataBuilder(this->GetBuffer(), bookmark); + } + + template<class CharTy> + void AddField( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaScalar, type, fieldTags); + } + + template<class CharTy> + void AddFieldArray( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaVcount, type, fieldTags); + } + + // Note: Do not use 0 for itemCount. + template<class CharTy> + void AddFieldFixedArray( + _In_z_ CharTy const* szUtfFieldName, + UINT16 itemCount, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaCcount, type, fieldTags); + this->AddU16(itemCount); + } + + template<class CharTy> + void AddFieldCustom( + _In_z_ CharTy const* szUtfFieldName, + _In_reads_bytes_(cbSchema) void const* pSchema, + UINT16 cbSchema, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaCustom, type, fieldTags); + this->AddU16(cbSchema); + auto pbSchema = static_cast<UINT8 const*>(pSchema); + for (UINT32 i = 0; i != cbSchema; i++) + { + this->AddU8(pbSchema[i]); + } + } + }; + +#pragma endregion + +#pragma region EventDataBuilder + + template<class ByteBufferTy> + class EventDataBuilder + { + void operator=(EventDataBuilder const&); // = delete + ByteBufferTy& m_buffer; + + public: + + explicit EventDataBuilder(ByteBufferTy& buffer) + : m_buffer(buffer) + { + return; + } + + EventDataBuilder(EventDataBuilder& other) + : m_buffer(other.m_buffer) + { + return; + } + + EventDataBuilder(EventDataBuilder&& other) + : m_buffer(other.m_buffer) + { + return; + } + + ByteBufferTy& Buffer() + { + return m_buffer; + } + + ByteBufferTy const& Buffer() const + { + return m_buffer; + } + + void Clear() + { + m_buffer.clear(); + } + + UINT32 Size() const + { + return static_cast<UINT32>(m_buffer.size()); + } + + UINT8& operator[](UINT32 index) + { + return m_buffer[index]; + } + + void AddArrayCount(UINT16 cItems) + { + AddBytes(&cItems, sizeof(cItems)); + } + + /* + Adds a value to the payload. + Note: should only be used for blittable POD types with no padding. + */ + template<class T> + void AddValue(T const& value) + { + AddBytes(&reinterpret_cast<const char&>(value), sizeof(T)); + } + + /* + Adds an array of values to the payload. + Note: should only be used for blittable POD types with no padding. + */ + template<class T> + void AddValues(_In_reads_(cValues) T const* pValues, unsigned cValues) + { + AddBytes(pValues, sizeof(T) * cValues); + } + + /* + Appends an InTypeBinary field to the payload. + Compatible with: TypeBinary, TypeIPv6, TypeSocketAddress. + */ + void AddBinary(_In_reads_bytes_(cbData) void const* pbData, UINT16 cbData) + { + AddBytes(&cbData, sizeof(cbData)); + AddBytes(pbData, cbData); + } + + /* + Appends an InTypeCountedAnsiString field to the payload. + Compatible with: TypeCountedMbcsString, TypeCountedMbcsXml, TypeCountedMbcsJson. + */ + void AddCountedString(_In_reads_(cch) char const* pch, UINT16 cch) + { + AddBinary(pch, cch * sizeof(*pch)); + } + + /* + Appends an InTypeCountedString field to the payload. + Compatible with: TypeCountedUtf16String, TypeCountedUtf16Xml, TypeCountedUtf16Json. + */ + void AddCountedString(_In_reads_(cch) wchar_t const* pch, UINT16 cch) + { + AddBinary(pch, cch * sizeof(*pch)); + } + + /* + Appends an InTypeAnsiString field to the payload. + Compatible with: TypeMbcsString, TypeMbcsXml, TypeMbcsJson. + */ + void AddString(_In_z_ char const* psz) + { + AddBytes(psz, static_cast<unsigned>(strlen(psz) + 1)); + } + + /* + Appends a InTypeUnicodeString field to the payload. + Compatible with: TypeUtf16String, TypeUtf16Xml, TypeUtf16Json + */ + void AddString(_In_z_ wchar_t const* psz) + { + AddBytes(psz, static_cast<unsigned>(2 * (wcslen(psz) + 1))); + } + + /* + Appends a raw byte to the payload. (Calls buffer.push_back) + */ + void AddByte(UINT8 val) + { + m_buffer.push_back(val); + } + + /* + Appends raw bytes to the payload. (Calls buffer.insert) + */ + void AddBytes(_In_reads_bytes_(cbData) void const* pbData, unsigned cbData) + { + auto pb = static_cast<UINT8 const*>(pbData); + m_buffer.insert(m_buffer.end(), pb + 0, pb + cbData); + } + }; + +#pragma endregion + +#pragma region Provider + + class Provider + { + Provider(Provider const&); // = delete + void operator=(Provider const&); // = delete + + UINT32 m_levelPlus1; + HRESULT m_hrInitialization; + ULONGLONG m_keywordsAny; + ULONGLONG m_keywordsAll; + REGHANDLE m_hProvider; + UINT8 const* m_pbMetadata; + PENABLECALLBACK m_pfCallback; + LPVOID m_pCallbackContext; + GUID m_id; + + /* + Shortest possible valid provider metadata blob: + Size = 0x0003, Name = "", no options. + */ + static _Ret_bytecount_(3) UINT8 const* NullMetadata() + { + return reinterpret_cast<UINT8 const*>("\03\0"); + } + + static VOID NTAPI EnableCallback( + _In_ LPCGUID pSourceId, + _In_ ULONG callbackType, + _In_ UCHAR level, + _In_ ULONGLONG keywordsAny, + _In_ ULONGLONG keywordsAll, + _In_opt_ PEVENT_FILTER_DESCRIPTOR pFilterData, + _Inout_opt_ PVOID pCallbackContext) + { + if (pCallbackContext) + { + Provider* pProvider = static_cast<Provider*>(pCallbackContext); + switch (callbackType) + { + case 0: // EVENT_CONTROL_CODE_DISABLE_PROVIDER + pProvider->m_levelPlus1 = 0; + break; + case 1: // EVENT_CONTROL_CODE_ENABLE_PROVIDER + pProvider->m_levelPlus1 = level != 0 ? (UINT32)level + 1u : 256u; + pProvider->m_keywordsAny = keywordsAny; + pProvider->m_keywordsAll = keywordsAll; + break; + } + + if (pProvider->m_pfCallback) + { + pProvider->m_pfCallback( + pSourceId, + callbackType, + level, + keywordsAny, + keywordsAll, + pFilterData, + pProvider->m_pCallbackContext); + } + } + } + + _Ret_opt_bytecount_(cbMetadata) + static UINT8* MetadataAlloc( + UINT32 cbMetadata) + { + return static_cast<UINT8*>(HeapAlloc(GetProcessHeap(), 0, cbMetadata)); + } + + static void MetadataFree( + _Post_invalid_ UINT8* pbMetadata) + { + if (pbMetadata) + { + HeapFree(GetProcessHeap(), 0, pbMetadata); + } + } + + void InitFail( + HRESULT hr) + { + _tld_ASSERT(m_pbMetadata != NullMetadata(), "InitFail called with m_pbMetadata == NullMetadata()"); + MetadataFree(const_cast<UINT8*>(m_pbMetadata)); + m_hrInitialization = hr; + m_pbMetadata = NullMetadata(); + } + + template<class CharTy> + void Init( + _In_z_ CharTy const* szName, + _In_opt_ GUID const* pGroupId) + { + // Already initialized: m_pfCallback, m_pCallbackContext, m_id. + m_levelPlus1 = 0; + m_hrInitialization = 0; + m_keywordsAny = 0; + m_keywordsAll = 0; + m_hProvider = 0; + m_pbMetadata = 0; + + ByteArrayWrapper wrapper(0, 0); + ProviderMetadataBuilder<ByteArrayWrapper> builder(wrapper); + builder.Begin(szName); + if (pGroupId) + { + builder.AddTrait(ProviderTraitGroupGuid, pGroupId, sizeof(GUID)); + } + if (!builder.End()) + { + InitFail(E_INVALIDARG); // Metadata > 64KB. + goto Done; + } + + { + UINT32 const cbMetadata = wrapper.size(); + UINT8* pbMetadata = MetadataAlloc(cbMetadata); + if (!pbMetadata) + { + InitFail(E_OUTOFMEMORY); + goto Done; + } + + m_pbMetadata = pbMetadata; + wrapper.reset(pbMetadata, cbMetadata); + builder.Begin(szName); + if (pGroupId) + { + builder.AddTrait(ProviderTraitGroupGuid, pGroupId, sizeof(GUID)); + } + if (!builder.End() || cbMetadata < wrapper.size()) + { + InitFail(0x8000000CL); // E_CHANGED_STATE + goto Done; + } + + { + HRESULT hr = ::tld::RegisterProvider(&m_hProvider, &m_id, m_pbMetadata, &EnableCallback, this); + if (FAILED(hr)) + { + InitFail(hr); + } + } + } + + Done: + + return; + } + + bool IsEnabledForKeywords(ULONGLONG keywords) const + { + return keywords == 0 || ( + (keywords & m_keywordsAny) != 0 && + (keywords & m_keywordsAll) == m_keywordsAll); + } + + public: + + ~Provider() + { + EventUnregister(m_hProvider); + if (m_pbMetadata != NullMetadata()) + { + MetadataFree(const_cast<UINT8*>(m_pbMetadata)); + } + } + + Provider( + _In_z_ wchar_t const* szUtf16Name, + GUID const& providerId, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(providerId) + { + Init(szUtf16Name, pGroupId); + } + + Provider( + _In_z_ char const* szUtf8Name, + GUID const& providerId, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(providerId) + { + Init(szUtf8Name, pGroupId); + } + + explicit Provider( + _In_z_ wchar_t const* szUtf16Name, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(GetGuidForName(szUtf16Name)) + { + Init(szUtf16Name, pGroupId); + } + + explicit Provider( + _In_z_ char const* szUtf8Name, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(GetGuidForName(szUtf8Name)) + { + Init(szUtf8Name, pGroupId); + } + + HRESULT InitializationResult() const + { + return m_hrInitialization; + } + + _Ret_z_ char const* Name() const + { + return reinterpret_cast<char const*>(m_pbMetadata + 2); + } + + GUID const& Id() const + { + return m_id; + } + + _Ret_notnull_ UINT8 const* GetMetadata() const + { + return m_pbMetadata; + } + + UINT16 GetMetadataSize() const + { + return *reinterpret_cast<UINT16 const*>(m_pbMetadata); + } + + bool IsEnabled() const + { + return m_levelPlus1 != 0; + } + + bool IsEnabled(UCHAR level) const + { + return level < m_levelPlus1; + } + + bool IsEnabled(UCHAR level, ULONGLONG keywords) const + { + return level < m_levelPlus1 && IsEnabledForKeywords(keywords); + } + + HRESULT SetInformation( + EVENT_INFO_CLASS informationClass, + _In_reads_bytes_opt_(cbInformation) void* pbInformation, + ULONG cbInformation + ) const + { + return ::tld::SetInformation(m_hProvider, informationClass, pbInformation, cbInformation); + } + + HRESULT WriteGather( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + return ::tld::WriteEvent( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cDataDescriptors, + pDataDescriptors, + pActivityId, + pRelatedActivityId); + } + + HRESULT Write( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_reads_bytes_(cbData) LPCVOID pbData, + UINT32 cbData, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + EventDataDescCreate(dataDesc + 2, pbData, cbData); + return ::tld::WriteEvent( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cbData ? 3 : 2, + dataDesc, + pActivityId, + pRelatedActivityId); + } + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + HRESULT WriteGatherEx( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + ULONG64 filter, + ULONG flags, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + return ::tld::WriteEventEx( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cDataDescriptors, + pDataDescriptors, + filter, + flags, + pActivityId, + pRelatedActivityId); + } + + HRESULT WriteEx( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_reads_bytes_(cbData) LPCVOID pbData, + UINT32 cbData, + ULONG64 filter, + ULONG flags, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + EventDataDescCreate(dataDesc + 2, pbData, cbData); + return ::tld::WriteEventEx( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cbData ? 3 : 2, + dataDesc, + filter, + flags, + pActivityId, + pRelatedActivityId); + } + +#endif // TLD_HAVE_EVENT_WRITE_EX + }; + +#pragma endregion + +#pragma region EventBuilder + + enum EventState + { + EventStateOpen, + EventStateClosed, + EventStateError + }; + + template<class ByteBufferTy> + class EventBuilder + { + void operator=(EventBuilder const&); // = delete + + EventMetadataBuilder<ByteBufferTy> m_metaBuilder; + EventDataBuilder<ByteBufferTy> m_dataBuilder; + EventState& m_eventState; + + EventBuilder(EventBuilder& other, EventMetadataBuilder<ByteBufferTy>&& metaBuilder) + : m_metaBuilder(metaBuilder) + , m_dataBuilder(other.m_dataBuilder) + , m_eventState(other.m_eventState) + { + return; + } + + protected: + + EventBuilder( + ByteBufferTy& metaBuffer, + ByteBufferTy& dataBuffer, + EventState& eventState) + : m_metaBuilder(metaBuffer) + , m_dataBuilder(dataBuffer) + , m_eventState(eventState) + { + return; + } + + public: + + EventBuilder(EventBuilder& other) + : m_metaBuilder(other.m_metaBuilder) + , m_dataBuilder(other.m_dataBuilder) + , m_eventState(other.m_eventState) + { + return; + } + + EventBuilder(EventBuilder&& other) + : m_metaBuilder(other.m_metaBuilder) + , m_dataBuilder(other.m_dataBuilder) + , m_eventState(other.m_eventState) + { + return; + } + + /* + Returns a const reference to the event metadata's underlying buffer. + */ + ByteBufferTy const& MetaBuffer() const + { + return m_metaBuilder.GetBuffer(); + } + + /* + Returns a const reference to the event payload data's underlying buffer. + */ + ByteBufferTy const& DataBuffer() const + { + return m_dataBuilder.Buffer(); + } + + /* + Adds a nested struct to the event's metadata. + Use the returned EventBuilder to add data and metadata for the fields + of the nested struct. + Note: do not call any Add methods on this builder object until you are + done calling Add methods on the nested builder object. + Note: Do not create structs with 0 fields. + */ + template<class CharTy> + EventBuilder AddStruct( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + return EventBuilder(*this, m_metaBuilder.AddStruct(szUtfStructName, fieldTags)); + } + + /* + Adds a nested array of struct to the event's metadata. + Use the returned EventBuilder to add data and metadata for the fields + of the nested struct. + Note: do not call any Add methods on this builder object until you are + done calling Add methods on the nested builder object. + Note: Do not create structs with 0 fields. + */ + template<class CharTy> + EventBuilder AddStructArray( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + return EventBuilder(*this, m_metaBuilder.AddStructArray(szUtfStructName, fieldTags)); + } + + /* + Adds a nested array of struct to the event's metadata. + Use the returned EventBuilder to add data and metadata for the fields + of the nested struct. + Note: do not call any Add methods on this builder object until you are + done calling Add methods on the nested builder object. + Note: Do not use 0 for itemCount. + Note: Do not create structs with 0 fields. + */ + template<class CharTy> + EventBuilder AddStructFixedArray( + _In_z_ CharTy const* szUtfStructName, + UINT16 itemCount, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + return EventBuilder(*this, m_metaBuilder.AddStructFixedArray(szUtfStructName, itemCount, fieldTags)); + } + + /* + Adds a scalar field to the event's metadata. + */ + template<class CharTy> + void AddField( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddField(szUtfFieldName, type, fieldTags); + } + + /* + Adds a variable-length array field to the event's metadata. + The length (item count) must be added to the event's payload + immediately before the array item values. + */ + template<class CharTy> + void AddFieldArray( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddFieldArray(szUtfFieldName, type, fieldTags); + } + + /* + Adds a fixed-length array field to the event's metadata. + The length (item count) is encoded in the metadata, so it does not + need to be included in the event's payload. + Note: Do not use 0 for itemCount. + */ + template<class CharTy> + void AddFieldFixedArray( + _In_z_ CharTy const* szUtfFieldName, + UINT16 itemCount, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddFieldFixedArray(szUtfFieldName, itemCount, type, fieldTags); + } + + /* + Adds a custom-encoded field to the event's metadata. + Custom-encoded means that you are using some other serialization + system (e.g. Bond, Protocol Buffers) to produce the schema and data. + The payload is encoded as a UINT16 byte count followed by the data. + */ + template<class CharTy> + void AddFieldCustom( + _In_z_ CharTy const* szUtfFieldName, + _In_reads_bytes_(cbSchema) void const* pSchema, + UINT16 cbSchema, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddFieldCustom(szUtfFieldName, pSchema, cbSchema, type, fieldTags); + } + + /* + Advanced use: adds raw metadata bytes. + */ + void AddRawMetadata( + _In_reads_bytes_(cbMetadata) void const* pMetadata, + UINT32 cbMetadata) + { + auto& buf = m_metaBuilder.GetBuffer(); + auto pb = static_cast<BYTE const*>(pMetadata); + buf.insert(buf.end(), pb + 0u, pb + cbMetadata); + } + + /* + Adds an array length (item count) to the payload. This is for + variable-length arrays only (not fixed-length), and must occur + immediately before the array item values are written to the payload. + */ + void AddArrayCount(UINT16 cItems) + { + m_dataBuilder.AddArrayCount(cItems); + } + + /* + Adds a value to the payload. + Note: should only be used for blittable POD types with no padding, + e.g. INT32, FILETIME, GUID, not for strings or structs. + */ + template<class T> + void AddValue(T const& value) + { + m_dataBuilder.AddValue(value); + } + + /* + Adds an array of values to the payload. + Note: should only be used for blittable POD types with no padding, + e.g. INT32, FILETIME, GUID, not for strings or structs. + */ + template<class T> + void AddValues(_In_reads_(cValues) T const* pValues, unsigned cValues) + { + m_dataBuilder.AddValues(pValues, cValues); + } + + /* + Appends an InTypeBinary field to the payload. + Compatible with: TypeBinary, TypeIPv6, TypeSocketAddress. + */ + void AddBinary(_In_reads_bytes_(cbData) void const* pbData, UINT16 cbData) + { + m_dataBuilder.AddBinary(pbData, cbData); + } + + /* + Appends an InTypeCountedAnsiString field to the payload. + Compatible with: TypeCountedMbcsString, TypeCountedMbcsXml, TypeCountedMbcsJson. + */ + void AddCountedString(_In_reads_(cch) char const* pch, UINT16 cch) + { + m_dataBuilder.AddCountedString(pch, cch); + } + + /* + Appends an InTypeCountedString field to the payload. + Compatible with: TypeCountedUtf16String, TypeCountedUtf16Xml, TypeCountedUtf16Json. + */ + void AddCountedString(_In_reads_(cch) wchar_t const* pch, UINT16 cch) + { + m_dataBuilder.AddCountedString(pch, cch); + } + + /* + Appends an InTypeAnsiString field to the payload. + Compatible with: TypeMbcsString, TypeMbcsXml, TypeMbcsJson. + */ + void AddString(_In_z_ char const* psz) + { + m_dataBuilder.AddString(psz); + } + + /* + Appends a InTypeUnicodeString field to the payload. + Compatible with: TypeUtf16String, TypeUtf16Xml, TypeUtf16Json + */ + void AddString(_In_z_ wchar_t const* psz) + { + m_dataBuilder.AddString(psz); + } + + /* + Appends a raw byte to the payload. + */ + void AddByte(UINT8 val) + { + m_dataBuilder.AddByte(val); + } + + /* + Appends raw bytes to the payload. + */ + void AddBytes(_In_reads_bytes_(cbData) void const* pbData, unsigned cbData) + { + m_dataBuilder.AddBytes(pbData, cbData); + } + }; + +#pragma endregion + +#pragma region Event + + template<class ByteBufferTy> + class Event + : public EventBuilder<ByteBufferTy> + { + Event(Event const&); // = delete + void operator=(Event const&); // = delete + + ByteBufferTy m_metaBuffer; + ByteBufferTy m_dataBuffer; + EventDescriptor m_descriptor; + GUID const* m_pActivityId; + GUID const* m_pRelatedActivityId; + EventState m_state; + + public: + + Event( + _In_z_ char const* szUtf8Name, + UCHAR level = 5, + ULONGLONG keywords = 0, + UINT32 tags = 0, + UCHAR opcode = 0, + USHORT task = 0, + GUID const* pActivityId = 0, + GUID const* pRelatedActivityId = 0) + : EventBuilder<ByteBufferTy>(m_metaBuffer, m_dataBuffer, m_state) + , m_descriptor(level, keywords, opcode, task) + , m_pActivityId(pActivityId) + , m_pRelatedActivityId(pRelatedActivityId) + , m_state(EventStateOpen) + { + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).Begin(szUtf8Name, tags); + } + + Event( + _In_z_ wchar_t const* szUtf16Name, + UCHAR level = 5, + ULONGLONG keywords = 0, + UINT32 tags = 0, + UCHAR opcode = 0, + USHORT task = 0, + GUID const* pActivityId = 0, + GUID const* pRelatedActivityId = 0) + : EventBuilder<ByteBufferTy>(m_metaBuffer, m_dataBuffer, m_state) + , m_descriptor(level, keywords, opcode, task) + , m_pActivityId(pActivityId) + , m_pRelatedActivityId(pRelatedActivityId) + , m_state(EventStateOpen) + { + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).Begin(szUtf16Name, tags); + } + + /* + Clears the metadata and the payload. + */ + template<class CharTy> + void Reset( + _In_z_ CharTy const* szUtfName, + UCHAR level = 5, + ULONGLONG keywords = 0, + UINT32 tags = 0, + UCHAR opcode = 0, + USHORT task = 0, + GUID* pActivityId = 0, + GUID* pRelatedActivityId = 0) + { + m_metaBuffer.clear(); + m_dataBuffer.clear(); + m_descriptor.Reset(level, keywords, opcode, task); + m_pActivityId = pActivityId; + m_pRelatedActivityId = pRelatedActivityId; + m_state = EventStateOpen; + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).Begin(szUtfName, tags); + } + + /* + Clears the payload without clearing the metadata. + */ + void ResetData() + { + m_dataBuffer.clear(); + } + + /* + Sends the event to ETW using EventWriteTransfer. + */ + HRESULT Write(Provider const& provider) + { + HRESULT hr = E_INVALIDARG; + if (PrepareEvent()) + { + hr = provider.Write( + m_descriptor, + &m_metaBuffer.front(), + m_dataBuffer.empty() ? NULL : &m_dataBuffer.front(), + static_cast<UINT32>(m_dataBuffer.size()), + m_pActivityId, + m_pRelatedActivityId); + } + return hr; + } + + /* + Sends the event to ETW using EventWriteTransfer. + */ + HRESULT Write( + REGHANDLE hProvider, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata) + { + HRESULT hr = E_INVALIDARG; + + if (PrepareEvent()) + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + ULONG cDataDesc = 2; + if (!m_dataBuffer.empty()) + { + EventDataDescCreate( + &dataDesc[cDataDesc++], + &m_dataBuffer.front(), + static_cast<ULONG>(m_dataBuffer.size())); + } + + hr = ::tld::WriteEvent( + hProvider, + m_descriptor, + pProviderMetadata, + &m_metaBuffer.front(), + cDataDesc, + dataDesc, + m_pActivityId, + m_pRelatedActivityId); + } + + return hr; + } + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + /* + Sends the event to ETW using EventWriteEx. + */ + HRESULT WriteEx( + Provider const& provider, + ULONG64 filter, + ULONG flags) + { + HRESULT hr = E_INVALIDARG; + if (PrepareEvent()) + { + hr = provider.WriteEx( + m_descriptor, + &m_metaBuffer.front(), + m_dataBuffer.empty() ? NULL : &m_dataBuffer.front(), + static_cast<UINT32>(m_dataBuffer.size()), + filter, + flags, + m_pActivityId, + m_pRelatedActivityId); + } + return hr; + } + + /* + Sends the event to ETW using EventWriteEx. + */ + HRESULT WriteEx( + REGHANDLE hProvider, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pProviderMetadata, + ULONG64 filter, + ULONG flags) + { + HRESULT hr = E_INVALIDARG; + + if (PrepareEvent()) + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + ULONG cDataDesc = 2; + if (!m_dataBuffer.empty()) + { + EventDataDescCreate( + &dataDesc[cDataDesc++], + &m_dataBuffer.front(), + static_cast<ULONG>(m_dataBuffer.size())); + } + + hr = ::tld::WriteEventEx( + hProvider, + m_descriptor, + pProviderMetadata, + &m_metaBuffer.front(), + cDataDesc, + dataDesc, + filter, + flags, + m_pActivityId, + m_pRelatedActivityId); + } + + return hr; + } + +#endif // TLD_HAVE_EVENT_WRITE_EX + +#pragma region Basic properties + + UCHAR Channel() const + { + return m_descriptor.Channel; + } + + /* + Note: the default channel is 11 (WINEVENT_CHANNEL_TRACELOGGING). + Other channels are only supported if the provider is running on + Windows 10 or later. If a provider is running on an earlier version + of Windows and it uses a channel other than 11, TDH will not be able + to decode the events. + */ + void Channel(UCHAR channel) + { + m_descriptor.Channel = channel; + } + + UCHAR Level() const + { + return m_descriptor.Level; + } + + void Level(UCHAR value) + { + m_descriptor.Level = value; + } + + ULONGLONG Keywords() const + { + return m_descriptor.Keyword; + } + + void Keywords(ULONGLONG value) + { + m_descriptor.Keyword = value; + } + + UCHAR Opcode() const + { + return m_descriptor.Opcode; + } + + void Opcode(UCHAR value) + { + m_descriptor.Opcode = value; + } + + USHORT Task() const + { + return m_descriptor.Task; + } + + void Task(USHORT value) + { + m_descriptor.Task = value; + } + + const GUID* ActivityId() const + { + return m_pActivityId; + } + + void ActivityId(GUID* pValue) + { + m_pActivityId = pValue; + } + + const GUID* RelatedActivityId() const + { + return m_pRelatedActivityId; + } + + void RelatedActivityId(GUID* pValue) + { + m_pRelatedActivityId = pValue; + } + + bool IsEnabledFor(Provider const& provider) const + { + return provider.IsEnabled(m_descriptor.Level, m_descriptor.Keyword); + } + +#pragma endregion + + private: + + bool PrepareEvent() + { + if (m_state == EventStateOpen) + { + PrepareEventImpl(); + } + return m_state == EventStateClosed; + } + + void PrepareEventImpl() + { + m_state = + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).End() + ? EventStateClosed + : EventStateError; + } + }; + +#pragma endregion +} +// namespace tld diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h new file mode 100644 index 000000000..60cac48a5 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h @@ -0,0 +1,186 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#include <map> + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/span_id.h" + +#include "opentelemetry/exporters/etw/etw_provider.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ +/** + * @brief TelemetryProvider Options passed via SDK API. + */ +using TelemetryProviderOptions = + std::map<std::string, nostd::variant<std::string, uint64_t, float, bool>>; + +/** + * @brief TelemetryProvider runtime configuration class. Internal representation + * of TelemetryProviderOptions used by various components of SDK. + */ +typedef struct +{ + bool enableTraceId; // Set `TraceId` on ETW events + bool enableSpanId; // Set `SpanId` on ETW events + bool enableActivityId; // Assign `SpanId` to `ActivityId` + bool enableActivityTracking; // Emit TraceLogging events for Span/Start and Span/Stop Not used + // for Logs + bool enableRelatedActivityId; // Assign parent `SpanId` to `RelatedActivityId` + bool enableAutoParent; // Start new spans as children of current active span, Not used for Logs + ETWProvider::EventFormat + encoding; // Event encoding to use for this provider (TLD, MsgPack, XML, etc.). +} TelemetryProviderConfiguration; + +/** + * @brief Helper template to convert a variant value from TelemetryProviderOptions to + * LoggerProviderConfiguration + * + * @param options TelemetryProviderOptions passed on API surface + * @param key Option name + * @param value Reference to destination value + * @param defaultValue Default value if option is not supplied + */ +template <typename T> +static inline void GetOption(const TelemetryProviderOptions &options, + const char *key, + T &value, + T defaultValue) +{ + auto it = options.find(key); + if (it != options.end()) + { + auto val = it->second; + value = nostd::get<T>(val); + } + else + { + value = defaultValue; + } +} + +/** + * @brief Helper template to convert encoding config option to EventFormat. + * Configuration option passed as `options["encoding"] = "MsgPack"`. + * Default encoding is TraceLogging Dynamic Manifest (TLD). + * + * Valid encoding names listed below. + * + * For MessagePack encoding: + * - "MSGPACK" + * - "MsgPack" + * - "MessagePack" + * + * For XML encoding: + * - "XML" + * - "xml" + * + * For TraceLogging Dynamic encoding: + * - "TLD" + * - "tld" + * + */ +static inline ETWProvider::EventFormat GetEncoding(const TelemetryProviderOptions &options) +{ + ETWProvider::EventFormat evtFmt = ETWProvider::EventFormat::ETW_MANIFEST; + + auto it = options.find("encoding"); + if (it != options.end()) + { + auto varValue = it->second; + std::string val = nostd::get<std::string>(varValue); + +#pragma warning(push) +#pragma warning(disable : 4307) /* Integral constant overflow - OK while computing hash */ + auto h = utils::hashCode(val.c_str()); + switch (h) + { + case CONST_HASHCODE(MSGPACK): + // nobrk + case CONST_HASHCODE(MsgPack): + // nobrk + case CONST_HASHCODE(MessagePack): + evtFmt = ETWProvider::EventFormat::ETW_MSGPACK; + break; + + case CONST_HASHCODE(XML): + // nobrk + case CONST_HASHCODE(xml): + evtFmt = ETWProvider::EventFormat::ETW_XML; + break; + + case CONST_HASHCODE(TLD): + // nobrk + case CONST_HASHCODE(tld): + // nobrk + evtFmt = ETWProvider::EventFormat::ETW_MANIFEST; + break; + + default: + break; + } +#pragma warning(pop) + } + + return evtFmt; +} + +/** + * @brief Utility template to obtain etw::TracerProvider._config or etw::LoggerProvider._config + * + * @tparam T etw::TracerProvider + * @param t etw::TracerProvider ref + * @return TelemetryProviderConfiguration ref + */ +template <class T> +TelemetryProviderConfiguration &GetConfiguration(T &t) +{ + return t.config_; +} + +/** + * @brief Utility template to convert SpanId or TraceId to hex. + * @param id - value of SpanId or TraceId + * @return Hexadecimal representation of Id as string. + */ +template <class T> +static inline std::string ToLowerBase16(const T &id) +{ + char buf[2 * T::kSize] = {0}; + id.ToLowerBase16(buf); + return std::string(buf, sizeof(buf)); +} + +/** + * @brief Utility method to convert span_id (8 byte) to ActivityId GUID (16 bytes) + * @param span OpenTelemetry Span Id object + * @return GUID struct containing 8-bytes of SpanId + 8 NUL bytes. + */ +static inline bool CopySpanIdToActivityId(const opentelemetry::trace::SpanId &span_id, + GUID &outGuid) +{ + memset(&outGuid, 0, sizeof(outGuid)); + if (!span_id.IsValid()) + { + return false; + } + auto spanId = span_id.Id().data(); + uint8_t *guidPtr = reinterpret_cast<uint8_t *>(&outGuid); + for (size_t i = 0; i < 8; i++) + { + guidPtr[i] = spanId[i]; + } + return true; +} + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h new file mode 100644 index 000000000..a2fd6c727 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h @@ -0,0 +1,142 @@ +/* // Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <unordered_map> + +#include "opentelemetry/exporters/etw/utils.h" + +/* clang-format off */ +#ifdef CUSTOM_ETW_FIELDS_H +/* Customers may redefine the default field names by including CUSTOM_ETW_FIELDS_H header */ +# include CUSTOM_ETW_FIELDS_H +#else + +/** + + List of configurable Field Name constants: + + Version - Schema version (optional for ETW exporter). + _name - Built-in ETW name at envelope level (dedicated ETW field). + _time - Built-in ETW time at envelope level (dedicated ETW field). + SpanId - OT SpanId + TraceId - OT TraceId + StartTime - OT Span start time + Kind - OT Span kind + Name - OT Span name in ETW 'Payload["Name"]' + ParentId - OT Span parentId + Links - OT Span links array + + Other standard fields (reserved names) that may be appended by ETW channel: + + Level - a 1-byte integer that enables filtering based on the severity or verbosity of events + ProviderGuid - ETW Provider Guid + ProviderName - ETW Provider Name + OpcodeName - Name of Opcode (e.g. Start, Stop) + KeywordName - Name of Keyword + TaskName - TaskName, could be handled as an alias to Payload['name'] + ChannelName - ETW Channel Name + EventMessage - ETW Event Message string for unstructured events + ActivityId - ActivityId for EventSource parenting (current event) + RelatedActivityId - RelatedActivityId for EventSource parenting (parent event) + Pid - Process Id + Tid - Thread Id + + Example "Span" as shown in Visual Studio "Diagnostic Events" view. EventName="Span": + + { + "Timestamp": "2021-04-01T00:33:25.5876605-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 20, + "Message": null, + "ProcessId": 10424, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "56f2366b-5475-496f-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 0, + "Name": "B.max", + "ParentId": "8ad900d0587fad4a", + "SpanId": "6b36f25675546f49", + "StartTime": "2021-04-01T07:33:25.587Z", + "TraceId": "8f8ac710c37c5a419f0fe574f335e986" + } + } + + Example named Event on Span. Note that EventName="MyEvent2" in this case: + + { + "Timestamp": "2021-04-01T00:33:22.5848789-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 15, + "Message": null, + "ProcessId": 10424, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "MyEvent2", + "ActivityID": null, + "RelatedActivityID": null, + "Payload": { + "SpanId": "0da9f6bf7524a449", + "TraceId": "7715c9d490f54f44a5d0c6b62570f1b2", + "strKey": "anotherValue", + "uint32Key": 9876, + "uint64Key": 987654321 + } + } + + */ +# define ETW_FIELD_VERSION "Version" /* Event version */ +# define ETW_FIELD_TYPE "Type" /* Event type */ +# define ETW_FIELD_NAME "_name" /* Event name */ +# define ETW_FIELD_TIME "_time" /* Event time */ +# define ETW_FIELD_OPCODE "OpCode" /* OpCode for TraceLogging */ + +# define ETW_FIELD_TRACE_ID "TraceId" /* Trace Id */ +# define ETW_FIELD_SPAN_ID "SpanId" /* Span Id */ +# define ETW_FIELD_SPAN_PARENTID "ParentId" /* Span ParentId */ +# define ETW_FIELD_SPAN_KIND "Kind" /* Span Kind */ +# define ETW_FIELD_SPAN_LINKS "Links" /* Span Links array */ + +# define ETW_FIELD_PAYLOAD_NAME "Name" /* ETW Payload["Name"] */ + +/* Span option constants */ +# define ETW_FIELD_STARTTIME "StartTime" /* Operation start time */ +# define ETW_FIELD_DURATION "Duration" /* Operation duration */ +# define ETW_FIELD_STATUSCODE "StatusCode" /* Span status code */ +# define ETW_FIELD_STATUSMESSAGE "StatusMessage" /* Span status message */ +# define ETW_FIELD_SUCCESS "Success" /* Span success */ +# define ETW_FIELD_TIMESTAMP "Timestamp" /* Log timestamp */ + +/* Value constants */ +# define ETW_VALUE_SPAN "Span" /* ETW event name for Span */ +# define ETW_VALUE_LOG "Log" /* ETW event name for Log */ + +# define ETW_VALUE_SPAN_START "SpanStart" /* ETW for Span Start */ +# define ETW_VALUE_SPAN_END "SpanEnd" /* ETW for Span Start */ + + +/* Log specific */ +# define ETW_FIELD_LOG_BODY "body" /* Log body */ +# define ETW_FIELD_LOG_SEVERITY_TEXT "severityText" /* Sev text */ +# define ETW_FIELD_LOG_SEVERITY_NUM "severityNumber" /* Sev num */ + + +#endif + +/* clang-format on */ diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h new file mode 100644 index 000000000..bd2478224 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h @@ -0,0 +1,297 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +# include <algorithm> + +# include <cstdint> +# include <cstdio> +# include <cstdlib> +# include <sstream> +# include <type_traits> + +# include <fstream> + +# include <map> + +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/nostd/string_view.h" +# include "opentelemetry/nostd/unique_ptr.h" +# include "opentelemetry/nostd/variant.h" + +# include "opentelemetry/common/key_value_iterable_view.h" + +# include "opentelemetry/logs/logger_provider.h" +# include "opentelemetry/trace/span_id.h" +# include "opentelemetry/trace/trace_id.h" + +# include "opentelemetry/exporters/etw/etw_config.h" +# include "opentelemetry/exporters/etw/etw_fields.h" +# include "opentelemetry/exporters/etw/etw_properties.h" +# include "opentelemetry/exporters/etw/etw_provider.h" +# include "opentelemetry/exporters/etw/utils.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ + +class LoggerProvider; + +/** + * @brief Logger class that allows to send logs to ETW Provider. + */ +class Logger : public opentelemetry::logs::Logger +{ + + /** + * @brief Parent provider of this Tracer + */ + etw::LoggerProvider &loggerProvider_; + + /** + * @brief ProviderId (Name or GUID) + */ + std::string provId; + + /** + * @brief Encoding (Manifest, MessagePack or XML) + */ + ETWProvider::EventFormat encoding; + + /** + * @brief Provider Handle + */ + ETWProvider::Handle &provHandle; + + /** + * @brief ETWProvider is a singleton that aggregates all ETW writes. + * @return + */ + static ETWProvider &etwProvider() + { + static ETWProvider instance; // C++11 magic static + return instance; + } + + /** + * @brief Init a reference to etw::ProviderHandle + * @return Provider Handle + */ + ETWProvider::Handle &initProvHandle() { return etwProvider().open(provId, encoding); } + +public: + /** + * @brief Tracer constructor + * @param parent Parent LoggerProvider + * @param providerId ProviderId - Name or GUID + * @param encoding ETW encoding format to use. + */ + Logger(etw::LoggerProvider &parent, + nostd::string_view providerId = "", + ETWProvider::EventFormat encoding = ETWProvider::EventFormat::ETW_MANIFEST) + : opentelemetry::logs::Logger(), + loggerProvider_(parent), + provId(providerId.data(), providerId.size()), + encoding(encoding), + provHandle(initProvHandle()) + {} + + void Log(opentelemetry::logs::Severity severity, + nostd::string_view body, + const common::KeyValueIterable &attributes, + opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::TraceFlags trace_flags, + common::SystemTimestamp timestamp) noexcept override + { + +# ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + // Properties *res = dynamic_cast<Properties *>(&resr); + + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return Log(severity, provId, body, *evt, trace_id, span_id, trace_flags, timestamp); + } +# endif + Properties evtCopy = attributes; + return Log(severity, provId, body, evtCopy, trace_id, span_id, trace_flags, timestamp); + } + + void Log(opentelemetry::logs::Severity severity, + nostd::string_view name, + nostd::string_view body, + const common::KeyValueIterable &attributes, + opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::TraceFlags trace_flags, + common::SystemTimestamp timestamp) noexcept override + { + +# ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + // Properties *res = dynamic_cast<Properties *>(&resr); + + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return Log(severity, name, body, *evt, trace_id, span_id, trace_flags, timestamp); + } +# endif + Properties evtCopy = attributes; + return Log(severity, name, body, evtCopy, trace_id, span_id, trace_flags, timestamp); + } + + virtual void Log(opentelemetry::logs::Severity severity, + nostd::string_view name, + nostd::string_view body, + Properties &evt, + opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::TraceFlags trace_flags, + common::SystemTimestamp timestamp) noexcept + { + // Populate Etw.EventName attribute at envelope level + evt[ETW_FIELD_NAME] = ETW_VALUE_LOG; + +# ifdef HAVE_FIELD_TIME + { + auto timeNow = std::chrono::system_clock::now().time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(timeNow).count(); + evt[ETW_FIELD_TIME] = utils::formatUtcTimestampMsAsISO8601(millis); + } +# endif + const auto &cfg = GetConfiguration(loggerProvider_); + if (cfg.enableSpanId) + { + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(span_id); + } + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(trace_id); + } + // Populate ActivityId if enabled + GUID ActivityId; + LPGUID ActivityIdPtr = nullptr; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(span_id, ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + evt[ETW_FIELD_PAYLOAD_NAME] = std::string(name.data(), name.size()); + std::chrono::system_clock::time_point ts = timestamp; + int64_t tsMs = + std::chrono::duration_cast<std::chrono::milliseconds>(ts.time_since_epoch()).count(); + evt[ETW_FIELD_TIMESTAMP] = utils::formatUtcTimestampMsAsISO8601(tsMs); + int severity_index = static_cast<int>(severity); + if (severity_index < 0 || + severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value) + { + std::stringstream sout; + sout << "Invalid severity(" << severity_index << ")"; + evt[ETW_FIELD_LOG_SEVERITY_TEXT] = sout.str(); + } + else + { + evt[ETW_FIELD_LOG_SEVERITY_TEXT] = + opentelemetry::logs::SeverityNumToText[severity_index].data(); + } + evt[ETW_FIELD_LOG_SEVERITY_NUM] = static_cast<uint32_t>(severity); + evt[ETW_FIELD_LOG_BODY] = std::string(body.data(), body.length()); + etwProvider().write(provHandle, evt, nullptr, nullptr, 0, encoding); + } + + const nostd::string_view GetName() noexcept override { return std::string(); } + // TODO : Flush and Shutdown method in main Logger API + ~Logger() { etwProvider().close(provHandle); } +}; + +/** + * @brief ETW LoggerProvider + */ +class LoggerProvider : public opentelemetry::logs::LoggerProvider +{ +public: + /** + * @brief LoggerProvider options supplied during initialization. + */ + TelemetryProviderConfiguration config_; + + /** + * @brief Construct instance of LoggerProvider with given options + * @param options Configuration options + */ + LoggerProvider(TelemetryProviderOptions options) : opentelemetry::logs::LoggerProvider() + { + GetOption(options, "enableTraceId", config_.enableTraceId, true); + GetOption(options, "enableSpanId", config_.enableSpanId, true); + GetOption(options, "enableActivityId", config_.enableActivityId, false); + + // Determines what encoding to use for ETW events: TraceLogging Dynamic, MsgPack, XML, etc. + config_.encoding = GetEncoding(options); + } + + LoggerProvider() : opentelemetry::logs::LoggerProvider() + { + config_.encoding = ETWProvider::EventFormat::ETW_MANIFEST; + } + + nostd::shared_ptr<opentelemetry::logs::Logger> GetLogger( + nostd::string_view logger_name, + nostd::string_view options, + nostd::string_view library_name, + nostd::string_view version = "", + nostd::string_view schema_url = "") override + { + UNREFERENCED_PARAMETER(options); + UNREFERENCED_PARAMETER(library_name); + UNREFERENCED_PARAMETER(version); + UNREFERENCED_PARAMETER(schema_url); + ETWProvider::EventFormat evtFmt = config_.encoding; + return nostd::shared_ptr<opentelemetry::logs::Logger>{ + new (std::nothrow) etw::Logger(*this, logger_name, evtFmt)}; + } + + /** + * @brief Obtain ETW Tracer. + * @param name ProviderId (instrumentation name) - Name or GUID + * @param args Additional arguments that controls `codec` of the provider. + * Possible values are: + * - "ETW" - 'classic' Trace Logging Dynamic manifest ETW events. + * - "MSGPACK" - MessagePack-encoded binary payload ETW events. + * - "XML" - XML events (reserved for future use) + * @param library_name Library name + * @param version Library version + * @param schema_url schema URL + * @return + */ + nostd::shared_ptr<opentelemetry::logs::Logger> GetLogger( + nostd::string_view logger_name, + nostd::span<nostd::string_view> args, + nostd::string_view library_name, + nostd::string_view version = "", + nostd::string_view schema_url = "") override + { + UNREFERENCED_PARAMETER(args); + UNREFERENCED_PARAMETER(library_name); + UNREFERENCED_PARAMETER(version); + UNREFERENCED_PARAMETER(schema_url); + ETWProvider::EventFormat evtFmt = config_.encoding; + return nostd::shared_ptr<opentelemetry::logs::Logger>{ + new (std::nothrow) etw::Logger(*this, logger_name, evtFmt)}; + } +}; + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif // ENABLE_LOGS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger_exporter.h new file mode 100644 index 000000000..9b96cb27a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger_exporter.h @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef ENABLE_LOGS_PREVIEW +# include <cstdint> +# include <cstdio> +# include <cstdlib> + +# include <mutex> + +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/nostd/string_view.h" +# include "opentelemetry/nostd/unique_ptr.h" + +# include "opentelemetry/common/key_value_iterable_view.h" + +# include "opentelemetry/logs/logger_provider.h" +# include "opentelemetry/trace/span_id.h" +# include "opentelemetry/trace/trace_id.h" + +# include "opentelemetry/sdk/logs/exporter.h" + +# include "opentelemetry/exporters/etw/etw_config.h" +# include "opentelemetry/exporters/etw/etw_logger.h" +# include "opentelemetry/exporters/etw/etw_provider.h" + +# include "opentelemetry/exporters/etw/utils.h" + +#endif // ENABLE_LOGS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_properties.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_properties.h new file mode 100644 index 000000000..3cf365c8a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_properties.h @@ -0,0 +1,456 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "opentelemetry/version.h" + +#include "opentelemetry/common/key_value_iterable_view.h" + +#include <opentelemetry/nostd/span.h> +#include <map> +#include <string> +#include <vector> + +#ifdef _WIN32 +# include <Windows.h> +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ + +/** + * @brief PropertyVariant provides: + * - a constructor to initialize from initializer lists + * - an owning wrapper around `common::AttributeValue` + */ +using PropertyVariant = + nostd::variant<bool, + int32_t, + int64_t, + uint32_t, + uint64_t, + double, + std::string, + const char *, + // 8-bit byte arrays / binary blobs are not part of OT spec yet! + // Ref: https://github.com/open-telemetry/opentelemetry-specification/issues/780 + std::vector<uint8_t>, + std::vector<bool>, + std::vector<int32_t>, + std::vector<int64_t>, + std::vector<uint32_t>, + std::vector<uint64_t>, + std::vector<double>, + std::vector<std::string>>; + +enum PropertyType +{ + kTypeBool, + kTypeInt, + kTypeInt64, + kTypeUInt, + kTypeUInt64, + kTypeDouble, + kTypeString, + kTypeCString, + kTypeSpanByte, + kTypeSpanBool, + kTypeSpanInt, + kTypeSpanInt64, + kTypeSpanUInt, + kTypeSpanUInt64, + kTypeSpanDouble, + kTypeSpanString +}; + +/** + * @brief PropertyValue class that holds PropertyVariant and + * provides converter for non-owning common::AttributeValue + */ +class PropertyValue : public PropertyVariant +{ + + /** + * @brief Convert span<T> to vector<T> + * @tparam T + * @param source + * @return + */ + template <typename T> + static std::vector<T> to_vector(const nostd::span<const T, nostd::dynamic_extent> &source) + { + return std::vector<T>(source.begin(), source.end()); + } + + /** + * @brief Convert span<string_view> to vector<string> + * @param source Span of non-owning string views. + * @return Vector of owned strings. + */ + std::vector<std::string> static to_vector(const nostd::span<const nostd::string_view> &source) + { + std::vector<std::string> result(source.size()); + for (const auto &item : source) + { + result.push_back(std::string(item.data())); + } + return result; + } + + /** + * @brief Convert vector<INTEGRAL> to span<INTEGRAL>. + * @tparam T Integral type + * @param vec Vector of integral type primitives to convert to span. + * @return Span of integral type primitives. + */ + template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true> + static nostd::span<const T> to_span(const std::vector<T> &vec) + { + nostd::span<const T> result(vec.data(), vec.size()); + return result; + } + + /** + * @brief Convert vector<FLOAT> to span<const FLOAT>. + * @tparam T Float type + * @param vec Vector of float type primitives to convert to span. + * @return Span of float type primitives. + */ + template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> + static nostd::span<const T> to_span(const std::vector<T> &vec) + { + nostd::span<const T> result(vec.data(), vec.size()); + return result; + } + +public: + /** + * @brief PropertyValue from bool + * @param v + * @return + */ + PropertyValue(bool value) : PropertyVariant(value) {} + + /** + * @brief PropertyValue from integral. + * @param v + * @return + */ + template <typename TInteger, std::enable_if_t<std::is_integral<TInteger>::value, bool> = true> + PropertyValue(TInteger number) : PropertyVariant(number) + {} + + /** + * @brief PropertyValue from floating point. + * @param v + * @return + */ + template <typename TFloat, std::enable_if_t<std::is_floating_point<TFloat>::value, bool> = true> + PropertyValue(TFloat number) : PropertyVariant(double(number)) + {} + + /** + * @brief Default PropertyValue (int32_t=0) + * @param v + * @return + */ + PropertyValue() : PropertyVariant(int32_t(0)) {} + + /** + * @brief PropertyValue from array of characters as string. + * + * @param v + * @return + */ + PropertyValue(char value[]) : PropertyVariant(std::string(value)) {} + + /** + * @brief PropertyValue from array of characters as string. + * + * @param v + * @return + */ + PropertyValue(const char *value) : PropertyVariant(std::string(value)) {} + + /** + * @brief PropertyValue from string. + * + * @param v + * @return + */ + PropertyValue(const std::string &value) : PropertyVariant(value) {} + + /** + * @brief PropertyValue from vector as array. + * @return + */ + template <typename T> + PropertyValue(std::vector<T> value) : PropertyVariant(value) + {} + + /** + * @brief Convert non-owning common::AttributeValue to owning PropertyValue. + * @return + */ + PropertyValue &FromAttributeValue(const common::AttributeValue &v) + { + switch (v.index()) + { + case common::AttributeType::kTypeBool: + PropertyVariant::operator=(nostd::get<bool>(v)); + break; + case common::AttributeType::kTypeInt: + PropertyVariant::operator=(nostd::get<int32_t>(v)); + break; + case common::AttributeType::kTypeInt64: + PropertyVariant::operator=(nostd::get<int64_t>(v)); + break; + case common::AttributeType::kTypeUInt: + PropertyVariant::operator=(nostd::get<uint32_t>(v)); + break; + case common::AttributeType::kTypeUInt64: + PropertyVariant::operator=(nostd::get<uint64_t>(v)); + break; + case common::AttributeType::kTypeDouble: + PropertyVariant::operator=(nostd::get<double>(v)); + break; + case common::AttributeType::kTypeCString: { + PropertyVariant::operator=(nostd::get<const char *>(v)); + break; + } + case common::AttributeType::kTypeString: { + PropertyVariant::operator= + (std::string{nostd::string_view(nostd::get<nostd::string_view>(v)).data()}); + break; + } + + case common::AttributeType::kTypeSpanByte: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const uint8_t>>(v))); + break; + + case common::AttributeType::kTypeSpanBool: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const bool>>(v))); + break; + + case common::AttributeType::kTypeSpanInt: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const int32_t>>(v))); + break; + + case common::AttributeType::kTypeSpanInt64: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const int64_t>>(v))); + break; + + case common::AttributeType::kTypeSpanUInt: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const uint32_t>>(v))); + break; + + case common::AttributeType::kTypeSpanUInt64: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const uint64_t>>(v))); + break; + + case common::AttributeType::kTypeSpanDouble: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const double>>(v))); + break; + + case common::AttributeType::kTypeSpanString: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const nostd::string_view>>(v))); + break; + + default: + break; + } + return (*this); + } + + /** + * @brief Convert owning PropertyValue to non-owning common::AttributeValue + * @param other + */ + common::AttributeValue ToAttributeValue() const + { + common::AttributeValue value; + + switch (this->index()) + { + case PropertyType::kTypeBool: + value = nostd::get<bool>(*this); + break; + case PropertyType::kTypeInt: + value = nostd::get<int32_t>(*this); + break; + case PropertyType::kTypeInt64: + value = nostd::get<int64_t>(*this); + break; + case PropertyType::kTypeUInt: + value = nostd::get<uint32_t>(*this); + break; + case PropertyType::kTypeUInt64: + value = nostd::get<uint64_t>(*this); + break; + case PropertyType::kTypeDouble: + value = nostd::get<double>(*this); + break; + case PropertyType::kTypeString: { + const std::string &str = nostd::get<std::string>(*this); + return nostd::string_view(str.data(), str.size()); + break; + } + case PropertyType::kTypeCString: { + const char *data = nostd::get<const char *>(*this); + return nostd::string_view(data, (data) ? strlen(data) : 0); + break; + } + case PropertyType::kTypeSpanByte: { + value = to_span(nostd::get<std::vector<uint8_t>>(*this)); + break; + } + case PropertyType::kTypeSpanBool: { + const auto &vec = nostd::get<std::vector<bool>>(*this); + // FIXME: sort out how to remap from vector<bool> to span<bool> + UNREFERENCED_PARAMETER(vec); + break; + } + case PropertyType::kTypeSpanInt: + value = to_span(nostd::get<std::vector<int32_t>>(*this)); + break; + + case PropertyType::kTypeSpanInt64: + value = to_span(nostd::get<std::vector<int64_t>>(*this)); + break; + + case PropertyType::kTypeSpanUInt: + value = to_span(nostd::get<std::vector<uint32_t>>(*this)); + break; + + case PropertyType::kTypeSpanUInt64: + value = to_span(nostd::get<std::vector<uint64_t>>(*this)); + break; + + case PropertyType::kTypeSpanDouble: + value = to_span(nostd::get<std::vector<double>>(*this)); + break; + + case PropertyType::kTypeSpanString: + // FIXME: sort out how to remap from vector<string> to span<string_view> + // value = to_span(nostd::get<std::vector<std::string>>(self)); + break; + + default: + break; + } + return value; + } +}; + +/** + * @brief Map of PropertyValue + */ +using PropertyValueMap = std::map<std::string, PropertyValue>; + +/** + * @brief Map of PropertyValue with common::KeyValueIterable interface. + */ +class Properties : public common::KeyValueIterable, public PropertyValueMap +{ + + /** + * @brief Helper tyoe for map constructor. + */ + using PropertyValueType = std::pair<const std::string, PropertyValue>; + +public: + /** + * @brief PropertyValueMap constructor. + */ + Properties() : PropertyValueMap() {} + + /** + * @brief PropertyValueMap constructor from initializer list. + */ + Properties(const std::initializer_list<PropertyValueType> properties) : PropertyValueMap() + { + (*this) = (properties); + } + + /** + * @brief PropertyValueMap assignment operator from initializer list. + */ + Properties &operator=(std::initializer_list<PropertyValueType> properties) + { + PropertyValueMap::operator=(properties); + return (*this); + } + + /** + * @brief PropertyValueMap constructor from map. + */ + Properties(const PropertyValueMap &properties) : PropertyValueMap() { (*this) = properties; } + + /** + * @brief PropertyValueMap assignment operator from map. + */ + Properties &operator=(const PropertyValueMap &properties) + { + PropertyValueMap::operator=(properties); + return (*this); + } + + /** + * @brief PropertyValueMap constructor from KeyValueIterable + * allows to convert non-Owning KeyValueIterable to owning + * container. + * + */ + Properties(const common::KeyValueIterable &other) { (*this) = other; } + + /** + * @brief PropertyValueMap assignment operator. + */ + Properties &operator=(const common::KeyValueIterable &other) + { + clear(); + other.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + std::string k(key.data(), key.length()); + (*this)[k].FromAttributeValue(value); + return true; + }); + return (*this); + } + + /** + * @brief PropertyValueMap property accessor. + */ + PropertyValue &operator[](const std::string &k) { return PropertyValueMap::operator[](k); } + + /** + * Iterate over key-value pairs + * @param callback a callback to invoke for each key-value. If the callback returns false, + * the iteration is aborted. + * @return true if every key-value pair was iterated over + */ + bool ForEachKeyValue(nostd::function_ref<bool(nostd::string_view, common::AttributeValue)> + callback) const noexcept override + { + for (const auto &kv : (*this)) + { + const common::AttributeValue &value = kv.second.ToAttributeValue(); + if (!callback(nostd::string_view{kv.first}, value)) + { + return false; + } + } + return true; + } + + /** + * @return the number of key-value pairs + */ + size_t size() const noexcept override { return PropertyValueMap::size(); } +}; + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_provider.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_provider.h new file mode 100644 index 000000000..51cca03a6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_provider.h @@ -0,0 +1,629 @@ +// // Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4459) +# pragma warning(disable : 4018) +# pragma warning(disable : 5054) +#endif + +#include "opentelemetry/exporters/etw/etw_properties.h" +#include "opentelemetry/exporters/etw/uuid.h" +#include "opentelemetry/version.h" + +#include "opentelemetry/exporters/etw/etw_fields.h" +#include "opentelemetry/exporters/etw/utils.h" + +#ifdef HAVE_MSGPACK +# include "nlohmann/json.hpp" +#endif + +#include "opentelemetry/exporters/etw/etw_traceloggingdynamic.h" + +#include <map> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#ifdef HAVE_KRABS_TESTS +// krabs.hpp requires this definition of min macro from Windows.h +# ifndef min +# define min(a, b) (((a) < (b)) ? (a) : (b)) +# endif +#endif + +#define MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE 0x01000000 + +using namespace OPENTELEMETRY_NAMESPACE::exporter::etw; + +OPENTELEMETRY_BEGIN_NAMESPACE + +class ETWProvider +{ + +public: + const unsigned long STATUS_OK = 0; + const unsigned long STATUS_ERROR = ULONG_MAX; + const unsigned long STATUS_EFBIG = ULONG_MAX - 1; + + enum EventFormat + { + ETW_MANIFEST = 0, + ETW_MSGPACK = 1, + ETW_XML = 2 + }; + + /// <summary> + /// Entry that contains Provider Handle, Provider MetaData and Provider GUID + /// </summary> + struct Handle + { + uint64_t refCount; + REGHANDLE providerHandle; + std::vector<BYTE> providerMetaVector; + GUID providerGuid; + }; + + /// <summary> + /// Check if given provider is registered. + /// </summary> + /// <param name="providerId"></param> + /// <returns></returns> + bool is_registered(const std::string &providerId) + { + std::lock_guard<std::mutex> lock(m_providerMapLock); + auto it = providers().find(providerId); + if (it != providers().end()) + { + if (it->second.providerHandle != INVALID_HANDLE) + { + return true; + } + } + return false; + } + + /// <summary> + /// Get Provider by Name or string representation of GUID + /// </summary> + /// <param name="providerId"></param> + /// <returns></returns> + Handle &open(const std::string &providerId, EventFormat format = EventFormat::ETW_MSGPACK) + { + std::lock_guard<std::mutex> lock(m_providerMapLock); + + // Check and return if provider is already registered + auto it = providers().find(providerId); + if (it != providers().end()) + { + if (it->second.providerHandle != INVALID_HANDLE) + { + it->second.refCount++; + return it->second; + } + } + + // Register provider if necessary + auto &data = providers()[providerId]; + data.providerMetaVector.clear(); + + utils::UUID guid = (providerId.rfind("{", 0) == 0) ? utils::UUID(providerId.c_str()) + : // It's a ProviderGUID + utils::GetProviderGuid(providerId.c_str()); // It's a ProviderName + + data.providerGuid = guid.to_GUID(); + + // TODO: currently we do not allow to specify a custom group GUID + GUID providerGroupGuid = NULL_GUID; + + switch (format) + { + // Register with TraceLoggingDynamic facility - dynamic manifest ETW events. + case EventFormat::ETW_MANIFEST: { + tld::ProviderMetadataBuilder<std::vector<BYTE>> providerMetaBuilder( + data.providerMetaVector); + + // Use Tenant ID as provider Name + providerMetaBuilder.Begin(providerId.c_str()); + providerMetaBuilder.AddTrait(tld::ProviderTraitType::ProviderTraitGroupGuid, + (void *)&providerGroupGuid, sizeof(GUID)); + providerMetaBuilder.End(); + + REGHANDLE hProvider = 0; + if (0 != + tld::RegisterProvider(&hProvider, &data.providerGuid, data.providerMetaVector.data())) + { + // There was an error registering the ETW provider + data.refCount = 0; + data.providerHandle = INVALID_HANDLE; + } + else + { + data.refCount = 1; + data.providerHandle = hProvider; + } + } + break; + +#ifdef HAVE_MSGPACK + // Register for MsgPack payload ETW events. + case EventFormat::ETW_MSGPACK: { + REGHANDLE hProvider = 0; + if (EventRegister(&data.providerGuid, NULL, NULL, &hProvider) != ERROR_SUCCESS) + { + // There was an error registering the ETW provider + data.refCount = 0; + data.providerHandle = INVALID_HANDLE; + } + else + { + data.refCount = 1; + data.providerHandle = hProvider; + } + } + break; +#endif + + default: + // TODO: other protocols, e.g. XML events - not supported yet + break; + } + + // We always return an entry even if we failed to register. + // Caller should check whether the hProvider handle is valid. + return data; + } + + /** + * @brief Unregister provider + * @param data Provider Handle + * @return status code + */ + unsigned long close(Handle handle) + { + std::lock_guard<std::mutex> lock(m_providerMapLock); + + // use reference to provider list, NOT it' copy. + auto &m = providers(); + auto it = m.begin(); + while (it != m.end()) + { + if (it->second.providerHandle == handle.providerHandle) + { + // reference to item in the map of open provider handles + auto &data = it->second; + unsigned long result = STATUS_OK; + + data.refCount--; + if (data.refCount == 0) + { + if (data.providerMetaVector.size()) + { + // ETW/TraceLoggingDynamic provider + result = tld::UnregisterProvider(data.providerHandle); + } + else + { + // Other provider types, e.g. ETW/MsgPack + result = EventUnregister(data.providerHandle); + } + + it->second.providerHandle = INVALID_HANDLE; + if (result == STATUS_OK) + { + m.erase(it); + } + } + return result; + } + } + return STATUS_ERROR; + } + + unsigned long writeMsgPack(Handle &providerData, + exporter::etw::Properties &eventData, + LPCGUID ActivityId = nullptr, + LPCGUID RelatedActivityId = nullptr, + uint8_t Opcode = 0) + { +#ifdef HAVE_MSGPACK + // Events can only be sent if provider is registered + if (providerData.providerHandle == INVALID_HANDLE) + { + // Provider not registered! + return STATUS_ERROR; + } + + std::string eventName = "NoName"; + auto nameField = eventData[ETW_FIELD_NAME]; + +# ifdef HAVE_FIELD_TIME + // Event time is appended by ETW layer itself by default. This code allows + // to override the timestamp by millisecond timestamp, in case if it has + // not been already provided by the upper layer. + if (!eventData.count(ETW_FIELD_TIME)) + { + // TODO: if nanoseconds resolution is required, then we can populate it in 96-bit MsgPack + // spec. auto nanos = + // std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count(); + eventData[ETW_FIELD_TIME] = opentelemetry::utils::getUtcSystemTimeMs(); + } +# endif + + switch (nameField.index()) + { + case PropertyType::kTypeString: + eventName = (char *)(nostd::get<std::string>(nameField).data()); // must be 0-terminated! + break; + case PropertyType::kTypeCString: + eventName = (char *)(nostd::get<const char *>(nameField)); + break; + default: + // If invalid event name is supplied, then we replace it with 'NoName' + break; + } + + /* clang-format off */ + nlohmann::json jObj = + { + { ETW_FIELD_NAME, eventName }, + { ETW_FIELD_OPCODE, Opcode } + }; + /* clang-format on */ + + std::string eventFieldName(ETW_FIELD_NAME); + for (auto &kv : eventData) + { + const char *name = kv.first.data(); + + // Don't include event name field in the Payload section + if (eventFieldName == name) + continue; + + auto &value = kv.second; + switch (value.index()) + { + case PropertyType::kTypeBool: { + UINT8 temp = static_cast<UINT8>(nostd::get<bool>(value)); + jObj[name] = temp; + break; + } + case PropertyType::kTypeInt: { + auto temp = nostd::get<int32_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeInt64: { + auto temp = nostd::get<int64_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeUInt: { + auto temp = nostd::get<uint32_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeUInt64: { + auto temp = nostd::get<uint64_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeDouble: { + auto temp = nostd::get<double>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeString: { + jObj[name] = nostd::get<std::string>(value); + break; + } + case PropertyType::kTypeCString: { + auto temp = nostd::get<const char *>(value); + jObj[name] = temp; + break; + } +# if HAVE_TYPE_GUID + // TODO: consider adding UUID/GUID to spec + case common::AttributeType::TYPE_GUID: { + auto temp = nostd::get<GUID>(value); + // TODO: add transform from GUID type to string? + jObj[name] = temp; + break; + } +# endif + + // TODO: arrays are not supported yet + case PropertyType::kTypeSpanByte: + case PropertyType::kTypeSpanBool: + case PropertyType::kTypeSpanInt: + case PropertyType::kTypeSpanInt64: + case PropertyType::kTypeSpanUInt: + case PropertyType::kTypeSpanUInt64: + case PropertyType::kTypeSpanDouble: + case PropertyType::kTypeSpanString: + default: + // TODO: unsupported type + break; + } + } + + std::vector<uint8_t> v = nlohmann::json::to_msgpack(jObj); + + EVENT_DESCRIPTOR evtDescriptor; + // TODO: event descriptor may be populated with additional values as follows: + // Id - if 0, auto-incremented sequence number that uniquely identifies event in a trace + // Version - event version + // Channel - 11 for TraceLogging + // Level - verbosity level + // Task - TaskId + // Opcode - described in evntprov.h:259 : 0 - info, 1 - activity start, 2 - activity stop. + EventDescCreate(&evtDescriptor, 0, 0x1, 0, 0, 0, Opcode, 0); + EVENT_DATA_DESCRIPTOR evtData[1]; + EventDataDescCreate(&evtData[0], v.data(), static_cast<ULONG>(v.size())); + ULONG writeResponse = 0; + if ((ActivityId != nullptr) || (RelatedActivityId != nullptr)) + { + writeResponse = EventWriteTransfer(providerData.providerHandle, &evtDescriptor, ActivityId, + RelatedActivityId, 1, evtData); + } + else + { + writeResponse = EventWrite(providerData.providerHandle, &evtDescriptor, 1, evtData); + } + + switch (writeResponse) + { + case ERROR_INVALID_PARAMETER: + break; + case ERROR_INVALID_HANDLE: + break; + case ERROR_ARITHMETIC_OVERFLOW: + break; + case ERROR_MORE_DATA: + break; + case ERROR_NOT_ENOUGH_MEMORY: + break; + default: + break; + } + + if (writeResponse == ERROR_ARITHMETIC_OVERFLOW) + { + return STATUS_EFBIG; + } + return (unsigned long)(writeResponse); +#else + UNREFERENCED_PARAMETER(providerData); + UNREFERENCED_PARAMETER(eventData); + UNREFERENCED_PARAMETER(ActivityId); + UNREFERENCED_PARAMETER(RelatedActivityId); + UNREFERENCED_PARAMETER(Opcode); + return STATUS_ERROR; +#endif + } + + /// <summary> + /// Send event to Provider Id + /// </summary> + /// <param name="providerId"></param> + /// <param name="eventData"></param> + /// <returns></returns> + unsigned long writeTld(Handle &providerData, + Properties &eventData, + LPCGUID ActivityId = nullptr, + LPCGUID RelatedActivityId = nullptr, + uint8_t Opcode = 0 /* Information */) + { + // Make sure you stop sending event before register unregistering providerData + if (providerData.providerHandle == INVALID_HANDLE) + { + // Provider not registered! + return STATUS_ERROR; + } + + UINT32 eventTags = MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE; + + std::vector<BYTE> byteVector; + std::vector<BYTE> byteDataVector; + tld::EventMetadataBuilder<std::vector<BYTE>> builder(byteVector); + tld::EventDataBuilder<std::vector<BYTE>> dbuilder(byteDataVector); + + const std::string EVENT_NAME = ETW_FIELD_NAME; + std::string eventName = "NoName"; + auto nameField = eventData[EVENT_NAME]; + switch (nameField.index()) + { + case PropertyType::kTypeString: + eventName = (char *)(nostd::get<std::string>(nameField).data()); + break; + case PropertyType::kTypeCString: + eventName = (char *)(nostd::get<const char *>(nameField)); + break; + default: + // This is user error. Invalid event name! + // We supply default 'NoName' event name in this case. + break; + } + + builder.Begin(eventName.c_str(), eventTags); + + for (auto &kv : eventData) + { + const char *name = kv.first.data(); + auto &value = kv.second; + switch (value.index()) + { + case PropertyType::kTypeBool: { + builder.AddField(name, tld::TypeBool8); + UINT8 temp = static_cast<UINT8>(nostd::get<bool>(value)); + dbuilder.AddByte(temp); + break; + } + case PropertyType::kTypeInt: { + builder.AddField(name, tld::TypeInt32); + auto temp = nostd::get<int32_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeInt64: { + builder.AddField(name, tld::TypeInt64); + auto temp = nostd::get<int64_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeUInt: { + builder.AddField(name, tld::TypeUInt32); + auto temp = nostd::get<uint32_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeUInt64: { + builder.AddField(name, tld::TypeUInt64); + auto temp = nostd::get<uint64_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeDouble: { + builder.AddField(name, tld::TypeDouble); + auto temp = nostd::get<double>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeString: { + builder.AddField(name, tld::TypeUtf8String); + dbuilder.AddString(nostd::get<std::string>(value).data()); + break; + } + case PropertyType::kTypeCString: { + builder.AddField(name, tld::TypeUtf8String); + auto temp = nostd::get<const char *>(value); + dbuilder.AddString(temp); + break; + } +#if HAVE_TYPE_GUID + // TODO: consider adding UUID/GUID to spec + case PropertyType::kGUID: { + builder.AddField(name.c_str(), TypeGuid); + auto temp = nostd::get<GUID>(value); + dbuilder.AddBytes(&temp, sizeof(GUID)); + break; + } +#endif + + // TODO: arrays are not supported + case PropertyType::kTypeSpanByte: + case PropertyType::kTypeSpanBool: + case PropertyType::kTypeSpanInt: + case PropertyType::kTypeSpanInt64: + case PropertyType::kTypeSpanUInt: + case PropertyType::kTypeSpanUInt64: + case PropertyType::kTypeSpanDouble: + case PropertyType::kTypeSpanString: + default: + // TODO: unsupported type + break; + } + } + + if (!builder.End()) // Returns false if the metadata is too large. + { + return STATUS_EFBIG; // if event is too big for UTC to handle + } + + tld::EventDescriptor eventDescriptor; + eventDescriptor.Opcode = Opcode; + eventDescriptor.Level = 0; /* FIXME: Always on for now */ + + EVENT_DATA_DESCRIPTOR pDataDescriptors[3]; + EventDataDescCreate(&pDataDescriptors[2], byteDataVector.data(), + static_cast<ULONG>(byteDataVector.size())); + + HRESULT writeResponse = 0; + if ((ActivityId != nullptr) || (RelatedActivityId != nullptr)) + { + writeResponse = tld::WriteEvent(providerData.providerHandle, eventDescriptor, + providerData.providerMetaVector.data(), byteVector.data(), 3, + pDataDescriptors, ActivityId, RelatedActivityId); + } + else + { + writeResponse = tld::WriteEvent(providerData.providerHandle, eventDescriptor, + providerData.providerMetaVector.data(), byteVector.data(), 3, + pDataDescriptors); + } + + // Event is larger than ETW max sized of 64KB + if (writeResponse == HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW)) + { + return STATUS_EFBIG; + } + + return (unsigned long)(writeResponse); + } + + unsigned long write(Handle &providerData, + Properties &eventData, + LPCGUID ActivityId, + LPCGUID RelatedActivityId, + uint8_t Opcode, + ETWProvider::EventFormat format) + { + if (format == ETWProvider::EventFormat::ETW_MANIFEST) + { + return writeTld(providerData, eventData, ActivityId, RelatedActivityId, Opcode); + } + if (format == ETWProvider::EventFormat::ETW_MSGPACK) + { + return writeMsgPack(providerData, eventData, ActivityId, RelatedActivityId, Opcode); + } + if (format == ETWProvider::EventFormat::ETW_XML) + { + // TODO: not implemented + return STATUS_ERROR; + } + return STATUS_ERROR; + } + + static const REGHANDLE INVALID_HANDLE = _UI64_MAX; + +protected: + const unsigned int LargeEventSizeKB = 62; + + const GUID NULL_GUID = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}}; + + mutable std::mutex m_providerMapLock; + + using ProviderMap = std::map<std::string, Handle>; + + ProviderMap &providers() + { + static std::map<std::string, Handle> providers; + return providers; + } +}; + +OPENTELEMETRY_END_NAMESPACE + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_traceloggingdynamic.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_traceloggingdynamic.h new file mode 100644 index 000000000..a6243c46d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_traceloggingdynamic.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __has_include +# if __has_include("TraceLoggingDynamic.h") +# include "TraceLoggingDynamic.h" +# ifndef HAVE_TLD +# define HAVE_TLD +# endif +# endif +#else +# ifndef HAVE_TLD +# define HAVE_TLD +# endif +# include "TraceLoggingDynamic.h" +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h new file mode 100644 index 000000000..5db31a7a7 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h @@ -0,0 +1,995 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <algorithm> +#include <atomic> + +#include <cstdint> +#include <cstdio> +#include <cstdlib> + +#include <fstream> +#include <iomanip> +#include <iostream> +#include <map> +#include <memory> +#include <mutex> +#include <sstream> +#include <vector> + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/nostd/variant.h" + +#include "opentelemetry/common/key_value_iterable_view.h" + +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/exporters/etw/etw_config.h" +#include "opentelemetry/exporters/etw/etw_fields.h" +#include "opentelemetry/exporters/etw/etw_properties.h" +#include "opentelemetry/exporters/etw/etw_provider.h" +#include "opentelemetry/exporters/etw/utils.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ + +class Span; + +/** + * @brief Template that allows to instantiate new Span object for header-only forward-declared + * etw::Span type + * + * @tparam SpanType Expected to be etw::Span + * @tparam TracerType expected to be etw::Tracer + * @param objPtr Pointer to parent + * @param name Span Name + * @param options Span Options + * @return Span instance + */ +template <class SpanType, class TracerType> +SpanType *new_span(TracerType *objPtr, + nostd::string_view name, + const opentelemetry::trace::StartSpanOptions &options) +{ + return new (std::nothrow) SpanType{*objPtr, name, options}; +} + +/** + * @brief Template that allows to convert etw::Span pointer to smart shared pointer to + * `opentelemetry::trace::Span` + * @tparam SpanType Expected to be etw::Span + * @param ptr Pointer to etw::Span + * @return Smart shared pointer to `opentelemetry::trace::Span` + */ +template <class SpanType> +nostd::shared_ptr<opentelemetry::trace::Span> to_span_ptr(SpanType *ptr) +{ + return nostd::shared_ptr<opentelemetry::trace::Span>{ptr}; +} + +class TracerProvider; + +/** + * @brief Utility template for obtaining Span Name + * @tparam T etw::Span + * @param t instance of etw::Span + * @return Span Name + */ +template <class T> +std::string GetName(T &t) +{ + auto sV = t.GetName(); + return std::string(sV.data(), sV.length()); +} + +/** + * @brief Utility template to obtain Span start time + * @tparam T etw::Span + * @param t instance of etw::Span + * @return Span Start timestamp + */ +template <class T> +common::SystemTimestamp GetStartTime(T &t) +{ + return t.GetStartTime(); +} + +/** + * @brief Utility template to obtain Span end time + * @tparam T etw::Span + * @param t instance of etw::Span + * @return Span Stop timestamp + */ +template <class T> +common::SystemTimestamp GetEndTime(T &t) +{ + return t.GetEndTime(); +} + +class Properties; + +/** + * @brief Utility template to store Attributes on Span + * @tparam T etw::Span + * @param instance instance of etw::Span + * @param t Properties to store as Attributes + */ +template <class T> +void SetSpanAttributes(T &instance, Properties &t) +{ + instance.SetAttributes(t); +} + +/** + * @brief Utility template to obtain Span Attributes + * @tparam T etw::Span + * @param instance instance of etw::Span + * @return ref to Span Attributes + */ +template <class T> +Properties &GetSpanAttributes(T &instance) +{ + return instance.GetAttributes(); +} + +template <class T> +void UpdateStatus(T &t, Properties &props) +{ + t.UpdateStatus(props); +} + +/** + * @brief Tracer class that allows to send spans to ETW Provider. + */ +class Tracer : public opentelemetry::trace::Tracer +{ + + /** + * @brief Parent provider of this Tracer + */ + etw::TracerProvider &tracerProvider_; + + /** + * @brief ProviderId (Name or GUID) + */ + std::string provId; + + /** + * @brief Encoding (Manifest, MessagePack or XML) + */ + ETWProvider::EventFormat encoding; + + /** + * @brief Provider Handle + */ + ETWProvider::Handle &provHandle; + + opentelemetry::trace::TraceId traceId_; + + std::atomic<bool> isClosed_{true}; + + /** + * @brief ETWProvider is a singleton that aggregates all ETW writes. + * @return + */ + static ETWProvider &etwProvider() + { + static ETWProvider instance; // C++11 magic static + return instance; + } + + /** + * @brief Internal method that allows to populate Links to other Spans. + * Span links are in hexadecimal representation, comma-separated in their + * order of appearance. + * + * @param attributes + * @param links + */ + virtual void DecorateLinks(Properties &attributes, + const opentelemetry::trace::SpanContextKeyValueIterable &links) const + { + // Add `SpanLinks` attribute if the list is not empty + if (links.size()) + { + size_t idx = 0; + std::string linksValue; + links.ForEachKeyValue( + [&](opentelemetry::trace::SpanContext ctx, const common::KeyValueIterable &) { + if (!linksValue.empty()) + { + linksValue += ','; + linksValue += ToLowerBase16(ctx.span_id()); + } + idx++; + return true; + }); + attributes[ETW_FIELD_SPAN_LINKS] = linksValue; + } + } + + /** + * @brief Allow our friendly etw::Span to end itself on Tracer. + * @param span + * @param + */ + virtual void EndSpan(const Span &span, + const opentelemetry::trace::Span *parentSpan = nullptr, + const opentelemetry::trace::EndSpanOptions & = {}) + { + const auto &cfg = GetConfiguration(tracerProvider_); + const opentelemetry::trace::Span &spanBase = + reinterpret_cast<const opentelemetry::trace::Span &>(span); + auto spanContext = spanBase.GetContext(); + + // Populate Span with presaved attributes + Span ¤tSpan = const_cast<Span &>(span); + Properties evt = GetSpanAttributes(currentSpan); + evt[ETW_FIELD_NAME] = GetName(span); + + if (cfg.enableSpanId) + { + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(spanContext.span_id()); + } + + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(spanContext.trace_id()); + } + + // Populate ActivityId if enabled + GUID ActivityId; + LPGUID ActivityIdPtr = nullptr; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(spanBase.GetContext().span_id(), ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + + // Populate RelatedActivityId if enabled + GUID RelatedActivityId; + LPGUID RelatedActivityIdPtr = nullptr; + if (cfg.enableRelatedActivityId) + { + if (parentSpan != nullptr) + { + if (CopySpanIdToActivityId(parentSpan->GetContext().span_id(), RelatedActivityId)) + { + RelatedActivityIdPtr = &RelatedActivityId; + } + } + } + + if (cfg.enableActivityTracking) + { + // TODO: check what EndSpanOptions should be supported for this exporter. + // The only option available currently (end_steady_time) does not apply. + // + // This event on Span Stop enables generation of "non-transactional" + // OpCode=Stop in alignment with TraceLogging Activity "EventSource" + // spec. + etwProvider().write(provHandle, evt, ActivityIdPtr, RelatedActivityIdPtr, 2, encoding); + } + + { + // Now since the span has ended, we need to emit the "Span" event that + // contains the entire span information, attributes, time, etc. on it. + evt[ETW_FIELD_NAME] = ETW_VALUE_SPAN; + evt[ETW_FIELD_PAYLOAD_NAME] = GetName(span); + + // Add timing details in ISO8601 format, which adequately represents + // the actual time, taking Timezone into consideration. This is NOT + // local time, but rather UTC time (Z=0). + std::chrono::system_clock::time_point startTime = GetStartTime(currentSpan); + std::chrono::system_clock::time_point endTime = GetEndTime(currentSpan); + int64_t startTimeMs = + std::chrono::duration_cast<std::chrono::milliseconds>(startTime.time_since_epoch()) + .count(); + int64_t endTimeMs = + std::chrono::duration_cast<std::chrono::milliseconds>(endTime.time_since_epoch()).count(); + + // It may be more optimal to enable passing timestamps as UTC milliseconds + // since Unix epoch instead of string, but that implies additional tooling + // is needed to convert it, rendering it NOT human-readable. + evt[ETW_FIELD_STARTTIME] = utils::formatUtcTimestampMsAsISO8601(startTimeMs); +#ifdef ETW_FIELD_ENDTTIME + // ETW has its own precise timestamp at envelope layer for every event. + // However, in some scenarios it is easier to deal with ISO8601 strings. + // In that case we convert the app-created timestamp and place it into + // Payload[$ETW_FIELD_TIME] field. The option configurable at compile-time. + evt[ETW_FIELD_ENDTTIME] = utils::formatUtcTimestampMsAsISO8601(endTimeMs); +#endif + // Duration of Span in milliseconds + evt[ETW_FIELD_DURATION] = endTimeMs - startTimeMs; + // Presently we assume that all spans are server spans + evt[ETW_FIELD_SPAN_KIND] = uint32_t(opentelemetry::trace::SpanKind::kServer); + UpdateStatus(currentSpan, evt); + etwProvider().write(provHandle, evt, ActivityIdPtr, RelatedActivityIdPtr, 0, encoding); + } + } + + const opentelemetry::trace::TraceId &trace_id() { return traceId_; } + + friend class Span; + + /** + * @brief Init a reference to etw::ProviderHandle + * @return Provider Handle + */ + ETWProvider::Handle &initProvHandle() + { + isClosed_ = false; + return etwProvider().open(provId, encoding); + } + +public: + /** + * @brief Tracer constructor + * @param parent Parent TraceProvider + * @param providerId ProviderId - Name or GUID + * @param encoding ETW encoding format to use. + */ + Tracer(etw::TracerProvider &parent, + nostd::string_view providerId = "", + ETWProvider::EventFormat encoding = ETWProvider::EventFormat::ETW_MANIFEST) + : opentelemetry::trace::Tracer(), + tracerProvider_(parent), + provId(providerId.data(), providerId.size()), + encoding(encoding), + provHandle(initProvHandle()) + { + // Generate random GUID + GUID trace_id; + CoCreateGuid(&trace_id); + // Populate TraceId of the Tracer with the above GUID + const auto *traceIdPtr = reinterpret_cast<const uint8_t *>(std::addressof(trace_id)); + nostd::span<const uint8_t, opentelemetry::trace::TraceId::kSize> traceIdBytes( + traceIdPtr, traceIdPtr + opentelemetry::trace::TraceId::kSize); + traceId_ = opentelemetry::trace::TraceId(traceIdBytes); + } + + /** + * @brief Start Span + * @param name Span name + * @param attributes Span attributes + * @param links Span links + * @param options Span options + * @return + */ + nostd::shared_ptr<opentelemetry::trace::Span> StartSpan( + nostd::string_view name, + const common::KeyValueIterable &attributes, + const opentelemetry::trace::SpanContextKeyValueIterable &links, + const opentelemetry::trace::StartSpanOptions &options = {}) noexcept override + { +#ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return StartSpan(name, *evt, links, options); + } +#endif + Properties evtCopy = attributes; + return StartSpan(name, evtCopy, links, options); + } + + /** + * @brief Start Span + * @param name Span name + * @param attributes Span attributes + * @param links Span links + * @param options Span options + * @return + */ + virtual nostd::shared_ptr<opentelemetry::trace::Span> StartSpan( + nostd::string_view name, + Properties &evt, + const opentelemetry::trace::SpanContextKeyValueIterable &links, + const opentelemetry::trace::StartSpanOptions &options = {}) noexcept + { + const auto &cfg = GetConfiguration(tracerProvider_); + + // Parent Context: + // - either use current span + // - or attach to parent SpanContext specified in options + opentelemetry::trace::SpanContext parentContext = GetCurrentSpan()->GetContext(); + if (nostd::holds_alternative<opentelemetry::trace::SpanContext>(options.parent)) + { + auto span_context = nostd::get<opentelemetry::trace::SpanContext>(options.parent); + if (span_context.IsValid()) + { + parentContext = span_context; + } + } + + // Populate Etw.RelatedActivityId at envelope level if enabled + GUID RelatedActivityId; + LPCGUID RelatedActivityIdPtr = nullptr; + if (cfg.enableAutoParent) + { + if (cfg.enableRelatedActivityId) + { + if (CopySpanIdToActivityId(parentContext.span_id(), RelatedActivityId)) + { + RelatedActivityIdPtr = &RelatedActivityId; + } + } + } + + // This template pattern allows us to forward-declare the etw::Span, + // create an instance of it, then assign it to tracer::Span result. + auto currentSpan = new_span<Span, Tracer>(this, name, options); + nostd::shared_ptr<opentelemetry::trace::Span> result = to_span_ptr<Span>(currentSpan); + + auto spanContext = result->GetContext(); + + // Decorate with additional standard fields + std::string eventName = name.data(); + + // Populate Etw.EventName attribute at envelope level + evt[ETW_FIELD_NAME] = eventName; + + // Populate Payload["SpanId"] attribute + // Populate Payload["ParentSpanId"] attribute if parent Span is valid + if (cfg.enableSpanId) + { + if (parentContext.IsValid()) + { + evt[ETW_FIELD_SPAN_PARENTID] = ToLowerBase16(parentContext.span_id()); + } + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(spanContext.span_id()); + } + + // Populate Etw.Payload["TraceId"] attribute + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(spanContext.trace_id()); + } + + // Populate Etw.ActivityId at envelope level if enabled + GUID ActivityId; + LPCGUID ActivityIdPtr = nullptr; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(result.get()->GetContext().span_id(), ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + + // Links + DecorateLinks(evt, links); + + // Remember Span attributes to be passed down to ETW on Span end + SetSpanAttributes(*currentSpan, evt); + + if (cfg.enableActivityTracking) + { + // TODO: add support for options that are presently ignored : + // - options.kind + // - options.start_steady_time + // - options.start_system_time + etwProvider().write(provHandle, evt, ActivityIdPtr, RelatedActivityIdPtr, 1, encoding); + } + + return result; + } + + /** + * @brief Force flush data to Tracer, spending up to given amount of microseconds to flush. + * NOTE: this method has no effect for the realtime streaming Tracer. + * + * @param timeout Allow Tracer to drop data if timeout is reached + * @return + */ + void ForceFlushWithMicroseconds(uint64_t) noexcept override {} + + /** + * @brief Close tracer, spending up to given amount of microseconds to flush and close. + * NOTE: This method decrements the reference count on current ETW Provider Handle and + * closes it if reference count on that provider handle is zero. + * + * @param timeout Allow Tracer to drop data if timeout is reached. + * @return + */ + void CloseWithMicroseconds(uint64_t) noexcept override + { + // Close once only + if (!isClosed_.exchange(true)) + { + etwProvider().close(provHandle); + } + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Parent span. + * @param name Event name. + * @param timestamp Event timestamp. + * @param attributes Event attributes. + * @return + */ + void AddEvent(opentelemetry::trace::Span &span, + nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept + { +#ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return AddEvent(span, name, timestamp, *evt); + } +#endif + // Pass a copy converted to Properties object on stack + Properties evtCopy = attributes; + return AddEvent(span, name, timestamp, evtCopy); + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Parent span. + * @param name Event name. + * @param timestamp Event timestamp. + * @param attributes Event attributes. + * @return + */ + void AddEvent(opentelemetry::trace::Span &span, + nostd::string_view name, + common::SystemTimestamp timestamp, + Properties &evt) noexcept + { + // TODO: respect originating timestamp. Do we need to reserve + // a special 'Timestamp' field or is it an overkill? The delta + // between when `AddEvent` API is called and when ETW layer + // timestamp is appended is nanos- to micros-, thus handling + // the explicitly provided timestamp is only necessary in case + // if a process wants to submit back-dated or future-dated + // timestamp. Unless there is a strong ask from any ETW customer + // to have it, this feature (custom timestamp) remains unimplemented. + (void)timestamp; + + const auto &cfg = GetConfiguration(tracerProvider_); + + evt[ETW_FIELD_NAME] = name.data(); + + const auto &spanContext = span.GetContext(); + if (cfg.enableSpanId) + { + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(spanContext.span_id()); + } + + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(spanContext.trace_id()); + } + + LPGUID ActivityIdPtr = nullptr; + GUID ActivityId; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(spanContext.span_id(), ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + +#ifdef HAVE_FIELD_TIME + { + auto timeNow = std::chrono::system_clock::now().time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(timeNow).count(); + evt[ETW_FIELD_TIME] = utils::formatUtcTimestampMsAsISO8601(millis); + } +#endif + + etwProvider().write(provHandle, evt, ActivityIdPtr, nullptr, 0, encoding); + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Span. + * @param name Event name. + * @param timestamp Event timestamp. + * @return + */ + void AddEvent(opentelemetry::trace::Span &span, + nostd::string_view name, + common::SystemTimestamp timestamp) noexcept + { + AddEvent(span, name, timestamp, sdk::GetEmptyAttributes()); + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Spab. + * @param name Event name. + */ + void AddEvent(opentelemetry::trace::Span &span, nostd::string_view name) + { + AddEvent(span, name, std::chrono::system_clock::now(), sdk::GetEmptyAttributes()); + } + + /** + * @brief Tracer destructor. + */ + virtual ~Tracer() { CloseWithMicroseconds(0); } +}; + +/** + * @brief etw::Span allows to send event data to ETW listener. + */ +class Span : public opentelemetry::trace::Span +{ +protected: + friend class Tracer; + + /** + * @brief Span properties are attached on "Span" event on end of Span. + */ + Properties attributes_; + + common::SystemTimestamp start_time_; + common::SystemTimestamp end_time_; + + opentelemetry::trace::StatusCode status_code_{opentelemetry::trace::StatusCode::kUnset}; + std::string status_description_; + + /** + * @brief Owner Tracer of this Span + */ + Tracer &owner_; + + /** + * @brief Span name. + */ + nostd::string_view name_; + + /** + * @brief Attribute indicating that the span has ended. + */ + std::atomic<bool> has_ended_{false}; + + /** + * @brief Attribute indicating that the span has started. + */ + std::atomic<bool> has_started_{false}; + + /** + * @brief Parent Span of this nested Span (optional) + */ + Span *parent_{nullptr}; + + /** + * @brief Get Parent Span of this nested Span. + * @return Pointer to Parent or nullptr if no Parent. + */ + Span *GetParent() const { return parent_; } + + opentelemetry::trace::SpanContext context_; + + const opentelemetry::trace::SpanContext CreateContext() + { + GUID activity_id; + // Generate random GUID + CoCreateGuid(&activity_id); + const auto *activityIdPtr = reinterpret_cast<const uint8_t *>(std::addressof(activity_id)); + + // Populate SpanId with that GUID + nostd::span<const uint8_t, opentelemetry::trace::SpanId::kSize> spanIdBytes( + activityIdPtr, activityIdPtr + opentelemetry::trace::SpanId::kSize); + const opentelemetry::trace::SpanId spanId(spanIdBytes); + + // Inherit trace_id from Tracer + const opentelemetry::trace::TraceId traceId{owner_.trace_id()}; + // TODO: TraceFlags are not supported by ETW exporter. + const opentelemetry::trace::TraceFlags flags{0}; + // TODO: Remote parent is not supported by ETW exporter. + const bool hasRemoteParent = false; + return opentelemetry::trace::SpanContext{traceId, spanId, flags, hasRemoteParent}; + } + +public: + /** + * @brief Update Properties object with current Span status + * @param evt + */ + void UpdateStatus(Properties &evt) + { + /* Should we avoid populating this extra field if status is unset? */ + if ((status_code_ == opentelemetry::trace::StatusCode::kUnset) || + (status_code_ == opentelemetry::trace::StatusCode::kOk)) + { + evt[ETW_FIELD_SUCCESS] = "True"; + evt[ETW_FIELD_STATUSCODE] = uint32_t(status_code_); + evt[ETW_FIELD_STATUSMESSAGE] = status_description_; + } + else + { + evt[ETW_FIELD_SUCCESS] = "False"; + evt[ETW_FIELD_STATUSCODE] = uint32_t(status_code_); + evt[ETW_FIELD_STATUSMESSAGE] = status_description_; + } + } + + /** + * @brief Get start time of this Span. + * @return + */ + common::SystemTimestamp GetStartTime() { return start_time_; } + + /** + * @brief Get end time of this Span. + * @return + */ + common::SystemTimestamp GetEndTime() { return end_time_; } + + /** + * @brief Get Span Name. + * @return Span Name. + */ + nostd::string_view GetName() const { return name_; } + + /** + * @brief Span constructor + * @param owner Owner Tracer + * @param name Span name + * @param options Span options + * @param parent Parent Span (optional) + * @return + */ + Span(Tracer &owner, + nostd::string_view name, + const opentelemetry::trace::StartSpanOptions &options, + Span *parent = nullptr) noexcept + : opentelemetry::trace::Span(), + start_time_(std::chrono::system_clock::now()), + owner_(owner), + parent_(parent), + context_(CreateContext()) + { + name_ = name; + UNREFERENCED_PARAMETER(options); + } + + /** + * @brief Span Destructor + */ + ~Span() { End(); } + + /** + * @brief Add named event with no attributes. + * @param name Event name. + * @return + */ + void AddEvent(nostd::string_view name) noexcept override { owner_.AddEvent(*this, name); } + + /** + * @brief Add named event with custom timestamp. + * @param name + * @param timestamp + * @return + */ + void AddEvent(nostd::string_view name, common::SystemTimestamp timestamp) noexcept override + { + owner_.AddEvent(*this, name, timestamp); + } + + /** + * @brief Add named event with custom timestamp and attributes. + * @param name Event name. + * @param timestamp Event timestamp. + * @param attributes Event attributes. + * @return + */ + void AddEvent(nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept override + { + owner_.AddEvent(*this, name, timestamp, attributes); + } + + /** + * @brief Set Span status + * @param code Span status code. + * @param description Span description. + * @return + */ + void SetStatus(opentelemetry::trace::StatusCode code, + nostd::string_view description) noexcept override + { + status_code_ = code; + status_description_ = description.data(); + } + + void SetAttributes(Properties attributes) { attributes_ = attributes; } + + /** + * @brief Obtain span attributes specified at Span start. + * NOTE: please consider that this method is NOT thread-safe. + * + * @return ref to Properties collection + */ + Properties &GetAttributes() { return attributes_; } + + /** + * @brief Sets an attribute on the Span. If the Span previously contained a mapping + * for the key, the old value is replaced. + * + * @param key + * @param value + * @return + */ + void SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept override + { + // don't override fields propagated from span data. + if (key == ETW_FIELD_NAME || key == ETW_FIELD_SPAN_ID || key == ETW_FIELD_TRACE_ID) + { + return; + } + attributes_[std::string{key}].FromAttributeValue(value); + } + + /** + * @brief Update Span name. + * + * NOTE: this method is a no-op for streaming implementation. + * We cannot change the Span name after it started streaming. + * + * @param name + * @return + */ + void UpdateName(nostd::string_view) noexcept override + { + // We can't do that! + // name_ = name; + } + + /** + * @brief End Span. + * @param EndSpanOptions + * @return + */ + void End(const opentelemetry::trace::EndSpanOptions &options = {}) noexcept override + { + end_time_ = std::chrono::system_clock::now(); + + if (!has_ended_.exchange(true)) + { + owner_.EndSpan(*this, parent_, options); + } + } + + /** + * @brief Obtain SpanContext + * @return + */ + opentelemetry::trace::SpanContext GetContext() const noexcept override { return context_; } + + /** + * @brief Check if Span is recording data. + * @return + */ + bool IsRecording() const noexcept override + { + // For streaming implementation this should return the state of ETW Listener. + // In certain unprivileged environments, ex. containers, it is impossible + // to determine if a listener is registered. Thus, we always return true. + return true; + } + + virtual void SetToken(nostd::unique_ptr<context::Token> &&token) noexcept + { + // TODO: not implemented + UNREFERENCED_PARAMETER(token); + } + + /// <summary> + /// Get Owner tracer of this Span + /// </summary> + /// <returns></returns> + opentelemetry::trace::Tracer &tracer() const noexcept { return this->owner_; } +}; + +/** + * @brief ETW TracerProvider + */ +class TracerProvider : public opentelemetry::trace::TracerProvider +{ +public: + /** + * @brief TracerProvider options supplied during initialization. + */ + TelemetryProviderConfiguration config_; + + /** + * @brief Construct instance of TracerProvider with given options + * @param options Configuration options + */ + TracerProvider(TelemetryProviderOptions options) : opentelemetry::trace::TracerProvider() + { + // By default we ensure that all events carry their with TraceId and SpanId + GetOption(options, "enableTraceId", config_.enableTraceId, true); + GetOption(options, "enableSpanId", config_.enableSpanId, true); + + // Backwards-compatibility option that allows to reuse ETW-specific parenting described here: + // https://docs.microsoft.com/en-us/uwp/api/windows.foundation.diagnostics.loggingoptions.relatedactivityid + // https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventwritetransfer + + // Emit separate events compatible with TraceLogging Activity/Start and Activity/Stop + // format for every Span emitted. + GetOption(options, "enableActivityTracking", config_.enableActivityTracking, false); + + // Map current `SpanId` to ActivityId - GUID that uniquely identifies this activity. If NULL, + // ETW gets the identifier from the thread local storage. For details on getting this + // identifier, see EventActivityIdControl. + GetOption(options, "enableActivityId", config_.enableActivityId, false); + + // Map parent `SpanId` to RelatedActivityId - Activity identifier from the previous + // component. Use this parameter to link your component's events to the previous component's + // events. + GetOption(options, "enableRelatedActivityId", config_.enableRelatedActivityId, false); + + // When a new Span is started, the current span automatically becomes its parent. + GetOption(options, "enableAutoParent", config_.enableAutoParent, false); + + // Determines what encoding to use for ETW events: TraceLogging Dynamic, MsgPack, XML, etc. + config_.encoding = GetEncoding(options); + } + + TracerProvider() : opentelemetry::trace::TracerProvider() + { + config_.enableTraceId = true; + config_.enableSpanId = true; + config_.enableActivityId = false; + config_.enableActivityTracking = false; + config_.enableRelatedActivityId = false; + config_.enableAutoParent = false; + config_.encoding = ETWProvider::EventFormat::ETW_MANIFEST; + } + + /** + * @brief Obtain ETW Tracer. + * @param name ProviderId (instrumentation name) - Name or GUID + * + * @param args Additional arguments that controls `codec` of the provider. + * Possible values are: + * - "ETW" - 'classic' Trace Logging Dynamic manifest ETW events. + * - "MSGPACK" - MessagePack-encoded binary payload ETW events. + * - "XML" - XML events (reserved for future use) + * @return + */ + nostd::shared_ptr<opentelemetry::trace::Tracer> GetTracer( + nostd::string_view name, + nostd::string_view args = "", + nostd::string_view schema_url = "") noexcept override + { + UNREFERENCED_PARAMETER(args); + UNREFERENCED_PARAMETER(schema_url); + ETWProvider::EventFormat evtFmt = config_.encoding; + return nostd::shared_ptr<opentelemetry::trace::Tracer>{new (std::nothrow) + Tracer(*this, name, evtFmt)}; + } +}; + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h new file mode 100644 index 000000000..b28f1a021 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include <cstdint> +#include <cstdio> +#include <cstdlib> + +#include <mutex> + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" + +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/exporters/etw/etw_config.h" +#include "opentelemetry/exporters/etw/etw_provider.h" +#include "opentelemetry/exporters/etw/etw_tracer.h" + +#include "opentelemetry/exporters/etw/utils.h" diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/utils.h new file mode 100644 index 000000000..4d38c1fb6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/utils.h @@ -0,0 +1,274 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <algorithm> +#include <chrono> +#include <ctime> +#include <iomanip> +#include <locale> +#include <sstream> +#include <string> + +#include "opentelemetry/common/macros.h" +#include "opentelemetry/exporters/etw/uuid.h" +#include "opentelemetry/version.h" + +#ifdef _WIN32 +# include <Windows.h> +# include <evntprov.h> +# include <wincrypt.h> +# pragma comment(lib, "Advapi32.lib") +# pragma comment(lib, "Rpcrt4.lib") +# include <Objbase.h> +# pragma comment(lib, "Ole32.Lib") +#else +# include <codecvt> +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace utils +{ + +/// <summary> +/// Compile-time constexpr djb2 hash function for strings +/// </summary> +static constexpr uint32_t hashCode(const char *str, uint32_t h = 0) +{ + return (uint32_t)(!str[h] ? 5381 : ((uint32_t)hashCode(str, h + 1) * (uint32_t)33) ^ str[h]); +} + +#define CONST_UINT32_T(x) std::integral_constant<uint32_t, (uint32_t)x>::value + +#define CONST_HASHCODE(name) CONST_UINT32_T(OPENTELEMETRY_NAMESPACE::utils::hashCode(#name)) + +#ifdef _WIN32 + +/// <summary> +/// Compute SHA-1 hash of input buffer and save to output +/// </summary> +/// <param name="pData">Input buffer</param> +/// <param name="nData">Input buffer size</param> +/// <param name="pHashedData">Output buffer</param> +/// <param name="nHashedData">Output buffer size</param> +/// <returns></returns> +static inline bool sha1(const BYTE *pData, DWORD nData, BYTE *pHashedData, DWORD &nHashedData) +{ + bool bRet = false; + HCRYPTPROV hProv = NULL; + HCRYPTHASH hHash = NULL; + + if (!CryptAcquireContext(&hProv, // handle of the CSP + NULL, // key container name + NULL, // CSP name + PROV_RSA_FULL, // provider type + CRYPT_VERIFYCONTEXT)) // no key access is requested + { + bRet = false; + goto CleanUp; + } + + if (!CryptCreateHash(hProv, // handle of the CSP + CALG_SHA1, // hash algorithm to use + 0, // hash key + 0, // reserved + &hHash)) // + { + bRet = false; + goto CleanUp; + } + + if (!CryptHashData(hHash, // handle of the HMAC hash object + pData, // message to hash + nData, // number of bytes of data to add + 0)) // flags + { + bRet = false; + goto CleanUp; + } + + if (!CryptGetHashParam(hHash, // handle of the HMAC hash object + HP_HASHVAL, // query on the hash value + pHashedData, // filled on second call + &nHashedData, // length, in bytes,of the hash + 0)) + { + bRet = false; + goto CleanUp; + } + + bRet = true; + +CleanUp: + + if (hHash) + { + CryptDestroyHash(hHash); + } + + if (hProv) + { + CryptReleaseContext(hProv, 0); + } + return bRet; +} + +/// <summary> +/// Convert UTF-8 string to UTF-16 wide string. +/// +/// </summary> +/// <param name="in"></param> +/// <returns></returns> +static inline std::wstring to_utf16_string(const std::string &in) +{ +# ifdef _WIN32 + int in_length = static_cast<int>(in.size()); + int out_length = MultiByteToWideChar(CP_UTF8, 0, &in[0], in_length, NULL, 0); + std::wstring result(out_length, '\0'); + MultiByteToWideChar(CP_UTF8, 0, &in[0], in_length, &result[0], out_length); + return result; +# else + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; + return converter.from_bytes(in); +# endif +} + +/// <summary> +/// Transform ETW provider name to provider GUID as described here: +/// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ +/// </summary> +/// <param name="providerName"></param> +/// <returns></returns> +static inline GUID GetProviderGuid(const char *providerName) +{ + std::string name(providerName); + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c) { return (char)::toupper(c); }); + + size_t len = name.length() * 2 + 0x10; + uint8_t *buffer = new uint8_t[len]; + uint32_t num = 0x482c2db2; + uint32_t num2 = 0xc39047c8; + uint32_t num3 = 0x87f81a15; + uint32_t num4 = 0xbfc130fb; + + for (int i = 3; i >= 0; i--) + { + buffer[i] = (uint8_t)num; + num = num >> 8; + buffer[i + 4] = (uint8_t)num2; + num2 = num2 >> 8; + buffer[i + 8] = (uint8_t)num3; + num3 = num3 >> 8; + buffer[i + 12] = (uint8_t)num4; + num4 = num4 >> 8; + } + + for (size_t j = 0; j < name.length(); j++) + { + buffer[((2 * j) + 0x10) + 1] = (uint8_t)name[j]; + buffer[(2 * j) + 0x10] = (uint8_t)(name[j] >> 8); + } + + const size_t sha1_hash_size = 21; + uint8_t *buffer2 = new uint8_t[sha1_hash_size]; + DWORD len2 = sha1_hash_size; + sha1((const BYTE *)buffer, (DWORD)len, (BYTE *)buffer2, len2); + + unsigned long a = (((((buffer2[3] << 8) + buffer2[2]) << 8) + buffer2[1]) << 8) + buffer2[0]; + unsigned short b = (unsigned short)((buffer2[5] << 8) + buffer2[4]); + unsigned short num9 = (unsigned short)((buffer2[7] << 8) + buffer2[6]); + + GUID guid; + guid.Data1 = a; + guid.Data2 = b; + guid.Data3 = (unsigned short)((num9 & 0xfff) | 0x5000); + guid.Data4[0] = buffer2[8]; + guid.Data4[1] = buffer2[9]; + guid.Data4[2] = buffer2[10]; + guid.Data4[3] = buffer2[11]; + guid.Data4[4] = buffer2[12]; + guid.Data4[5] = buffer2[13]; + guid.Data4[6] = buffer2[14]; + guid.Data4[7] = buffer2[15]; + + delete[] buffer; + delete[] buffer2; + + return guid; +} +#endif + +static inline int64_t getUtcSystemTimeMs() +{ +#ifdef _WIN32 + ULARGE_INTEGER now; + ::GetSystemTimeAsFileTime(reinterpret_cast<FILETIME *>(&now)); + return (now.QuadPart - 116444736000000000ull) / 10000; +#else + return std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1); +#endif +} + +static inline int64_t getUtcSystemTimeinTicks() +{ +#ifdef _WIN32 + FILETIME tocks; + ::GetSystemTimeAsFileTime(&tocks); + ULONGLONG ticks = (ULONGLONG(tocks.dwHighDateTime) << 32) | tocks.dwLowDateTime; + // number of days from beginning to 1601 multiplied by ticks per day + return ticks + 0x701ce1722770000ULL; +#else + // On Un*x systems system_clock de-facto contains UTC time. Ref: + // https://en.cppreference.com/w/cpp/chrono/system_clock + // This UTC epoch contract has been signed in blood since C++20 + std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + uint64_t ticks = millis; + ticks *= 10000; // convert millis to ticks (1 tick = 100ns) + ticks += 0x89F7FF5F7B58000ULL; // UTC time 0 in .NET ticks + return ticks; +#endif +} + +/** + * @brief Convert local system milliseconds time to ISO8601 string UTC time + * + * @param timestampMs Milliseconds since epoch in system time + * + * @return ISO8601 UTC string with microseconds part set to 000 + */ +static inline std::string formatUtcTimestampMsAsISO8601(int64_t timestampMs) +{ + char buf[sizeof("YYYY-MM-DDTHH:MM:SS.ssssssZ") + 1] = {0}; +#ifdef _WIN32 + __time64_t seconds = static_cast<__time64_t>(timestampMs / 1000); + int milliseconds = static_cast<int>(timestampMs % 1000); + tm tm; + if (::_gmtime64_s(&tm, &seconds) != 0) + { + memset(&tm, 0, sizeof(tm)); + } + ::_snprintf_s(buf, _TRUNCATE, "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ", 1900 + tm.tm_year, + 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, 1000 * milliseconds); +#else + time_t seconds = static_cast<time_t>(timestampMs / 1000); + int milliseconds = static_cast<int>(timestampMs % 1000); + tm tm; + bool valid = (gmtime_r(&seconds, &tm) != NULL); + if (!valid) + { + memset(&tm, 0, sizeof(tm)); + } + (void)snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ", 1900 + tm.tm_year, + 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, 1000 * milliseconds); +#endif + return buf; +} + +}; // namespace utils + +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/uuid.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/uuid.h new file mode 100644 index 000000000..a004aec9e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/uuid.h @@ -0,0 +1,412 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/version.h" + +#include <cstddef> +#include <cstring> +#include <functional> +#include <initializer_list> +#include <string> +#include <vector> + +#ifdef _WIN32 +# include "Windows.h" +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace utils +{ + +/// <summary> +/// The UUID structure represents the portable cross-platform implementation of a GUID (Globally +/// Unique ID). +/// </summary> +/// <remarks> +/// UUIDs identify objects such as interfaces, manager entry-point vectors (EPVs), and class +/// objects. A UUID is a 128-bit value consisting of one group of eight hexadecimal digits, followed +/// by three groups of four hexadecimal digits, each followed by one group of 12 hexadecimal digits. +/// </remarks> +#pragma pack(push) /* push current alignment to stack */ +#pragma pack(1) /* set alignment to 1 byte boundary */ +struct UUID +{ + /// <summary> + /// Specifies the first eight hexadecimal digits of the GUID. + /// </summary> + uint32_t Data1; + + /// <summary> + /// Specifies the first group of four hexadecimal digits. + ///</summary> + uint16_t Data2; + + /// <summary> + /// Specifies the second group of four hexadecimal digits. + /// </summary> + uint16_t Data3; + + /// <summary> + /// An array of eight bytes. + /// The first two bytes contain the third group of four hexadecimal digits. + /// The remaining six bytes contain the final 12 hexadecimal digits. + /// </summary> + uint8_t Data4[8]; + + /// <summary> + /// The default UUID constructor. + /// Creates a null instance of the UUID object (initialized to all zeros). + /// {00000000-0000-0000-0000-000000000000}. + /// </summary> + UUID() : Data1(0), Data2(0), Data3(0) + { + for (size_t i = 0; i < 8; i++) + { + Data4[i] = 0; + } + } + + /// <summary> + /// A constructor that creates a UUID object from a hyphenated string as defined by + /// https://tools.ietf.org/html/rfc4122#page-4 + /// </summary> + /// <param name="uuid_string">A hyphenated string that contains the UUID (curly braces + /// optional).</param> + UUID(const char *uuid_string) + { + const char *str = uuid_string; + // Skip curly brace + if (str[0] == '{') + { + str++; + } + // Convert to set of integer values + unsigned long p0; + unsigned int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + if ( + // Parse input with dashes + (11 == sscanf_s(str, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, + &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10)) || + // Parse input without dashes + (11 == sscanf_s(str, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, &p3, + &p4, &p5, &p6, &p7, &p8, &p9, &p10))) + { + Data1 = static_cast<uint32_t>(p0); + Data2 = static_cast<uint16_t>(p1); + Data3 = static_cast<uint16_t>(p2); + Data4[0] = static_cast<uint8_t>(p3); + Data4[1] = static_cast<uint8_t>(p4); + Data4[2] = static_cast<uint8_t>(p5); + Data4[3] = static_cast<uint8_t>(p6); + Data4[4] = static_cast<uint8_t>(p7); + Data4[5] = static_cast<uint8_t>(p8); + Data4[6] = static_cast<uint8_t>(p9); + Data4[7] = static_cast<uint8_t>(p10); + } + else // Invalid input--use a safe default value + { + Data1 = 0; + Data2 = 0; + Data3 = 0; + Data4[0] = 0; + Data4[1] = 0; + Data4[2] = 0; + Data4[3] = 0; + Data4[4] = 0; + Data4[5] = 0; + Data4[6] = 0; + Data4[7] = 0; + } + } + + /// <summary> + /// A constructor that creates a UUID object from a byte array. + /// </summary> + /// <param name="guid_bytes">A byte array.</param> + /// <param name="bigEndian"> + /// A boolean value that specifies the byte order.<br> + /// A value of <i>true</i> specifies the more natural human-readable order.<br> + /// A value of <i>false</i> (the default) specifies the same order as the .NET GUID constructor. + /// </param> + UUID(const uint8_t guid_bytes[16], bool bigEndian = false) + { + if (bigEndian) + { + /* Use big endian - human-readable */ + // Part 1 + Data1 = guid_bytes[3]; + Data1 |= ((uint32_t)(guid_bytes[2])) << 8; + Data1 |= ((uint32_t)(guid_bytes[1])) << 16; + Data1 |= ((uint32_t)(guid_bytes[0])) << 24; + // Part 2 + Data2 = guid_bytes[5]; + Data2 |= ((uint16_t)(guid_bytes[4])) << 8; + // Part 3 + Data3 = guid_bytes[7]; + Data3 |= ((uint16_t)(guid_bytes[6])) << 8; + } + else + { + /* Use little endian - the same order as .NET C# Guid() class uses */ + // Part 1 + Data1 = guid_bytes[0]; + Data1 |= ((uint32_t)(guid_bytes[1])) << 8; + Data1 |= ((uint32_t)(guid_bytes[2])) << 16; + Data1 |= ((uint32_t)(guid_bytes[3])) << 24; + // Part 2 + Data2 = guid_bytes[4]; + Data2 |= ((uint16_t)(guid_bytes[5])) << 8; + // Part 3 + Data3 = guid_bytes[6]; + Data3 |= ((uint16_t)(guid_bytes[7])) << 8; + } + // Part 4 + for (size_t i = 0; i < 8; i++) + { + Data4[i] = guid_bytes[8 + i]; + } + } + + /// <summary> + /// A constructor that creates a UUID object from three integers and a byte array. + /// </summary> + /// <param name="d1">An integer that specifies the first eight hexadecimal digits of the + /// UUID.</param> <param name="d2">An integer that specifies the first group of four hexadecimal + /// digits.</param> <param name="d3">An integer that specifies the second group of four + /// hexadecimal digits.</param> <param name="v">A reference to an array of eight bytes. The first + /// two bytes contain the third group of four hexadecimal digits. The remaining six bytes contain + /// the final 12 hexadecimal digits.</param> + UUID(int d1, int d2, int d3, const std::initializer_list<uint8_t> &v) + : Data1((uint32_t)d1), Data2((uint16_t)d2), Data3((uint16_t)d3) + { + size_t i = 0; + for (auto val : v) + { + Data4[i] = val; + i++; + } + } + + /// <summary> + /// The UUID copy constructor. + /// </summary> + /// <param name="uuid">A UUID object.</param> + UUID(const UUID &uuid) + { + this->Data1 = uuid.Data1; + this->Data2 = uuid.Data2; + this->Data3 = uuid.Data3; + memcpy(&(this->Data4[0]), &(uuid.Data4[0]), sizeof(uuid.Data4)); + } + +#ifdef _WIN32 + + /// <summary> + /// A constructor that creates a UUID object from a Windows GUID object. + /// </summary> + /// <param name="guid">A Windows GUID object.</param> + UUID(GUID guid) + { + this->Data1 = guid.Data1; + this->Data2 = guid.Data2; + this->Data3 = guid.Data3; + std::memcpy(&(this->Data4[0]), &(guid.Data4[0]), sizeof(guid.Data4)); + } + + /// <summary> + /// Converts a standard vector of bytes into a Windows GUID object. + /// </summary> + /// <param name="bytes">A standard vector of bytes.</param> + /// <returns>A GUID.</returns> + static GUID to_GUID(std::vector<uint8_t> const &bytes) + { + UUID temp_t = UUID(bytes.data()); + GUID temp; + temp.Data1 = temp_t.Data1; + temp.Data2 = temp_t.Data2; + temp.Data3 = temp_t.Data3; + for (size_t i = 0; i < 8; i++) + { + temp.Data4[i] = temp_t.Data4[i]; + } + return temp; + } + + GUID to_GUID() + { + GUID temp; + temp.Data1 = Data1; + temp.Data2 = Data2; + temp.Data3 = Data3; + for (size_t i = 0; i < 8; i++) + { + temp.Data4[i] = Data4[i]; + } + return temp; + } + +#endif + + /// <summary> + /// Converts this UUID to an array of bytes. + /// </summary> + /// <param name="guid_bytes">A uint8_t array of 16 bytes.</param> + void to_bytes(uint8_t (&guid_bytes)[16]) const + { + // Part 1 + guid_bytes[0] = (uint8_t)((Data1)&0xFF); + guid_bytes[1] = (uint8_t)((Data1 >> 8) & 0xFF); + guid_bytes[2] = (uint8_t)((Data1 >> 16) & 0xFF); + guid_bytes[3] = (uint8_t)((Data1 >> 24) & 0xFF); + // Part 2 + guid_bytes[4] = (uint8_t)((Data2)&0xFF); + guid_bytes[5] = (uint8_t)((Data2 >> 8) & 0xFF); + // Part 3 + guid_bytes[6] = (uint8_t)((Data3)&0xFF); + guid_bytes[7] = (uint8_t)((Data3 >> 8) & 0xFF); + // Part 4 + for (size_t i = 0; i < 8; i++) + { + guid_bytes[8 + i] = Data4[i]; + } + } + + /// <summary> + /// Convert this UUID object to a string. + /// </summary> + /// <returns>This UUID object in a string.</returns> + std::string to_string() const + { + static char inttoHex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + const unsigned buffSize = 36 + 1; // 36 + null-terminator + char buf[buffSize] = {0}; + + int test = (Data1 >> 28 & 0x0000000F); + buf[0] = inttoHex[test]; + test = (int)(Data1 >> 24 & 0x0000000F); + buf[1] = inttoHex[test]; + test = (int)(Data1 >> 20 & 0x0000000F); + buf[2] = inttoHex[test]; + test = (int)(Data1 >> 16 & 0x0000000F); + buf[3] = inttoHex[test]; + test = (int)(Data1 >> 12 & 0x0000000F); + buf[4] = inttoHex[test]; + test = (int)(Data1 >> 8 & 0x0000000F); + buf[5] = inttoHex[test]; + test = (int)(Data1 >> 4 & 0x0000000F); + buf[6] = inttoHex[test]; + test = (int)(Data1 & 0x0000000F); + buf[7] = inttoHex[test]; + buf[8] = '-'; + test = (int)(Data2 >> 12 & 0x000F); + buf[9] = inttoHex[test]; + test = (int)(Data2 >> 8 & 0x000F); + buf[10] = inttoHex[test]; + test = (int)(Data2 >> 4 & 0x000F); + buf[11] = inttoHex[test]; + test = (int)(Data2 & 0x000F); + buf[12] = inttoHex[test]; + buf[13] = '-'; + test = (int)(Data3 >> 12 & 0x000F); + buf[14] = inttoHex[test]; + test = (int)(Data3 >> 8 & 0x000F); + buf[15] = inttoHex[test]; + test = (int)(Data3 >> 4 & 0x000F); + buf[16] = inttoHex[test]; + test = (int)(Data3 & 0x000F); + buf[17] = inttoHex[test]; + buf[18] = '-'; + test = (int)(Data4[0] >> 4 & 0x0F); + buf[19] = inttoHex[test]; + test = (int)(Data4[0] & 0x0F); + buf[20] = inttoHex[test]; + test = (int)(Data4[1] >> 4 & 0x0F); + buf[21] = inttoHex[test]; + test = (int)(Data4[1] & 0x0F); + buf[22] = inttoHex[test]; + buf[23] = '-'; + test = (int)(Data4[2] >> 4 & 0x0F); + buf[24] = inttoHex[test]; + test = (int)(Data4[2] & 0x0F); + buf[25] = inttoHex[test]; + test = (int)(Data4[3] >> 4 & 0x0F); + buf[26] = inttoHex[test]; + test = (int)(Data4[3] & 0x0F); + buf[27] = inttoHex[test]; + test = (int)(Data4[4] >> 4 & 0x0F); + buf[28] = inttoHex[test]; + test = (int)(Data4[4] & 0x0F); + buf[29] = inttoHex[test]; + test = (int)(Data4[5] >> 4 & 0x0F); + buf[30] = inttoHex[test]; + test = (int)(Data4[5] & 0x0F); + buf[31] = inttoHex[test]; + test = (int)(Data4[6] >> 4 & 0x0F); + buf[32] = inttoHex[test]; + test = (int)(Data4[6] & 0x0F); + buf[33] = inttoHex[test]; + test = (int)(Data4[7] >> 4 & 0x0F); + buf[34] = inttoHex[test]; + test = (int)(Data4[7] & 0x0F); + buf[35] = inttoHex[test]; + buf[36] = 0; + + return std::string(buf); + } + + /// <summary> + /// Calculates the size of this UUID object. + /// The output from this method is compatible with std::unordered_map. + /// </summary> + /// <returns>The size of the UUID object in bytes.</returns> + size_t Hash() const + { + // Compute individual hash values for Data1, Data2, Data3, and parts of Data4 + size_t res = 17; + res = res * 31 + Data1; + res = res * 31 + Data2; + res = res * 31 + Data3; + res = res * 31 + (Data4[0] << 24 | Data4[1] << 16 | Data4[6] << 8 | Data4[7]); + return res; + } + + /// <summary> + /// Tests to determine whether two UUID objects are equivalent (needed for maps). + /// </summary> + /// <returns>A boolean value that indicates success or failure.</returns> + bool operator==(UUID const &other) const + { + return Data1 == other.Data1 && Data2 == other.Data2 && Data3 == other.Data3 && + (0 == memcmp(Data4, other.Data4, sizeof(Data4))); + } + + /// <summary> + /// Tests to determine how to sort 2 UUID objects + /// </summary> + /// <returns>A boolean value that indicates success or failure.</returns> + bool operator<(UUID const &other) const + { + return Data1 < other.Data1 || Data2 < other.Data2 || Data3 == other.Data3 || + (memcmp(Data4, other.Data4, sizeof(Data4)) < 0); + } +}; +#pragma pack(pop) /* restore original alignment from stack */ + +/// <summary> +/// Declare UUIDComparer as the Comparer when using UUID as a key in a map or set +/// </summary> +struct UUIDComparer : std::less<UUID> +{ + inline size_t operator()(UUID const &key) const { return key.Hash(); } + + inline bool operator()(UUID const &lhs, UUID const &rhs) const { return lhs.Hash() < rhs.Hash(); } +}; + +} // namespace utils + +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_logger_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_logger_test.cc new file mode 100644 index 000000000..4e2210118 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_logger_test.cc @@ -0,0 +1,101 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW +# ifdef _WIN32 + +# include <gtest/gtest.h> +# include <map> +# include <string> + +# include "opentelemetry/exporters/etw/etw_logger_exporter.h" +# include "opentelemetry/sdk/trace/simple_processor.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +using namespace opentelemetry::exporter::etw; + +const char *kGlobalProviderName = "OpenTelemetry-ETW-TLD"; + +/** + * @brief Logger test with name and unstructured text + * { + * "Timestamp": "2021-09-30T16:40:40.0820563-07:00", + * "ProviderName": "OpenTelemetry-ETW-TLD", + * "Id": 2, + * "Message": null, + * "ProcessId": 23180, + * "Level": "Always", + * "Keywords": "0x0000000000000000", + * "EventName": "Log", + * "ActivityID": null, + * "RelatedActivityID": null, + * "Payload": { + * "Name": "test", + * "SpanId": "0000000000000000", + * "Timestamp": "2021-09-30T23:40:40.081000Z", + * "TraceId": "00000000000000000000000000000000", + * "_name": "Log", + * "body": "This is test message", + * "severityNumber": 5, + * "severityText": "DEBUG" + * } + * } + */ + +TEST(ETWLogger, LoggerCheckWithBody) +{ + std::string providerName = kGlobalProviderName; // supply unique instrumentation name here + exporter::etw::LoggerProvider lp; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = lp.GetLogger(providerName, "", schema_url); + Properties attribs = {{"attrib1", 1}, {"attrib2", 2}}; + EXPECT_NO_THROW(logger->Log(opentelemetry::logs::Severity::kDebug, "This is test log body")); +} + +/** + * @brief Logger Test with structured attributes + * + * Example Event for below test: + * { + * "Timestamp": "2021-09-30T15:04:15.4227815-07:00", + * "ProviderName": "OpenTelemetry-ETW-TLD", + * "Id": 1, + * "Message": null, + * "ProcessId": 33544, + * "Level": "Always", + * "Keywords": "0x0000000000000000", + * "EventName": "Log", + * "ActivityID": null, + * "RelatedActivityID": null, + * "Payload": { + * "Name": "test", + * "SpanId": "0000000000000000", + * "Timestamp": "2021-09-30T22:04:15.422000Z", + * "TraceId": "00000000000000000000000000000000", + * "_name": "Log", + * "attrib1": 1, + * "attrib2": 2, + * "body": "", + * "severityNumber": 5, + * "severityText": "DEBUG" + * } + * } + * + */ + +TEST(ETWLogger, LoggerCheckWithAttributes) +{ + std::string providerName = kGlobalProviderName; // supply unique instrumentation name here + exporter::etw::LoggerProvider lp; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = lp.GetLogger(providerName, "", schema_url); + // Log attributes + Properties attribs = {{"attrib1", 1}, {"attrib2", 2}}; + EXPECT_NO_THROW(logger->Log(opentelemetry::logs::Severity::kDebug, attribs)); +} + +# endif // _WIN32 +#endif // ENABLE_LOGS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_perf_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_perf_test.cc new file mode 100644 index 000000000..79052464e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_perf_test.cc @@ -0,0 +1,190 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef _WIN32 + +# include <benchmark/benchmark.h> +# include <gtest/gtest.h> +# include <map> +# include <string> +# include <unordered_map> + +# include "opentelemetry/exporters/etw/etw_tracer_exporter.h" +# include "opentelemetry/sdk/trace/simple_processor.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +using namespace opentelemetry::exporter::etw; + +namespace +{ +static constexpr const char *providerName = "OpenTelemetry-ETW-StressTest"; + +static exporter::etw::TelemetryProviderOptions providerOptions = { + {"enableTraceId", false}, + {"enableSpanId", false}, + {"enableActivityId", false}, + {"enableRelatedActivityId", false}, + {"enableAutoParent", false}}; + +class ETWProviderStressTest +{ + exporter::etw::TracerProvider provider_; + std::string mode_; + nostd::shared_ptr<trace::Tracer> tracer_; + nostd::shared_ptr<trace::Span> span_; + +public: + /** + * @brief Construct ETW Provider stress test object + * @param mode Operational mode: "TLD" or "MsgPack" + */ + ETWProviderStressTest(std::string mode = "TLD") : mode_(mode), provider_(providerOptions) {} + + /** + * @brief Initializer tracer and start a Span + */ + void Initialize() + { + tracer_ = provider_.GetTracer(providerName, mode_); + span_ = tracer_->StartSpan("Span"); + } + + /** + * @brief Obtain the tracer + */ + nostd::shared_ptr<trace::Tracer> GetTracer() { return tracer_; } + + /** + * @brief Add event using Properties container + */ + bool AddProperties() + { + std::string eventName = "MyEvent"; + Properties event = {{"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}; + span_->AddEvent(eventName, event); + return true; + } + + /** + * @brief Add event using static preallocated "reusable" Properties container. + * This approach works well for single-threaded flows, but may not be safe in + * some multithreaded scenarios in case if reusable `Properties` get concurrently + * modified by different threads (writes to Properties are not thread-safe). + */ + bool AddPropertiesStatic() + { + std::string eventName = "MyEvent"; + static Properties event = {{"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}; + span_->AddEvent(eventName, event); + return true; + } + + /** + * @brief Add event using initializer list + */ + bool AddInitList() + { + std::string eventName = "MyEvent"; + span_->AddEvent(eventName, {{"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}); + return true; + } + + /** + * @brief Add event using unordered_map + */ + bool AddMap() + { + std::string eventName = "MyEvent"; + std::unordered_map<const char *, common::AttributeValue> m = { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}; + span_->AddEvent(eventName, m); + return true; + } + + /** + * @brief End Span and close tracer. + */ + void Teardown() + { + span_->End(); + tracer_->CloseWithMicroseconds(0); + } +}; + +ETWProviderStressTest provider; + +/** + * @brief Create Properties and AddEvent(Properties) to Tracer + * @param state Benchmark state. + */ +void BM_AddPropertiesToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddProperties()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddPropertiesToTracer); + +/** + * @brief Create static Properties and AddEvent(Properties) to Tracer + * @param state Benchmark state. + */ +void BM_AddPropertiesStaticToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddPropertiesStatic()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddPropertiesStaticToTracer); + +/** + * @brief Create event via initializer list and AddEvent({...}) to Tracer + * @param state Benchmark state. + */ +void BM_AddInitListToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddInitList()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddInitListToTracer); + +/** + * @brief Create event as `std::map<std::string, common::AttributeValue>` + * and AddEvent(event) to Tracer. + * @param state Benchmark state. + */ +void BM_AddMapToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddMap()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddMapToTracer); + +} // namespace + +BENCHMARK_MAIN(); + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_provider_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_provider_test.cc new file mode 100644 index 000000000..d5ebbcad4 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_provider_test.cc @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef _WIN32 + +# include <gtest/gtest.h> +# include <string> + +# include "opentelemetry/exporters/etw/etw_provider.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +TEST(ETWProvider, ProviderIsRegisteredSuccessfully) +{ + std::string providerName = "OpenTelemetry-ETW-Provider"; + static ETWProvider etw; + auto handle = etw.open(providerName.c_str()); + + bool registered = etw.is_registered(providerName); + ASSERT_TRUE(registered); + etw.close(handle); +} + +TEST(ETWProvider, ProviderIsNotRegisteredSuccessfully) +{ + std::string providerName = "OpenTelemetry-ETW-Provider-NULL"; + static ETWProvider etw; + + bool registered = etw.is_registered(providerName); + ASSERT_FALSE(registered); +} + +TEST(ETWProvider, CheckOpenGUIDDataSuccessfully) +{ + std::string providerName = "OpenTelemetry-ETW-Provider"; + + // get GUID from the handle returned + static ETWProvider etw; + auto handle = etw.open(providerName.c_str()); + + utils::UUID uuid_handle(handle.providerGuid); + auto guidStrHandle = uuid_handle.to_string(); + + // get GUID from the providerName + auto guid = utils::GetProviderGuid(providerName.c_str()); + utils::UUID uuid_name(guid); + auto guidStrName = uuid_name.to_string(); + + ASSERT_STREQ(guidStrHandle.c_str(), guidStrName.c_str()); + etw.close(handle); +} + +TEST(ETWProvider, CheckCloseSuccess) +{ + std::string providerName = "OpenTelemetry-ETW-Provider"; + + static ETWProvider etw; + auto handle = etw.open(providerName.c_str(), ETWProvider::EventFormat::ETW_MANIFEST); + auto result = etw.close(handle); + ASSERT_NE(result, etw.STATUS_ERROR); + ASSERT_FALSE(etw.is_registered(providerName)); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_tracer_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_tracer_test.cc new file mode 100644 index 000000000..1c9c1f09b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_tracer_test.cc @@ -0,0 +1,388 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef _WIN32 + +# include <gtest/gtest.h> +# include <map> +# include <string> + +# include "opentelemetry/exporters/etw/etw_tracer_exporter.h" +# include "opentelemetry/sdk/trace/simple_processor.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +using namespace opentelemetry::exporter::etw; + +const char *kGlobalProviderName = "OpenTelemetry-ETW-TLD"; + +std::string getTemporaryValue() +{ + return std::string("Value from Temporary std::string"); +} + +/* clang-format off */ +TEST(ETWTracer, TracerCheck) +{ + // SDK customer specifies their unique ETW ProviderName. Every component or library + // is assumed to have its own instrumentation name. Traces are routed to dedicated + // provider. Standard hash function maps from ProviderName to ProviderGUID. + // + // Prominent naming examples from `logman query providers` : + // + // [Docker] {a3693192-9ed6-46d2-a981-f8226c8363bd} + // ... + // Intel-Autologger-iclsClient {B8D7E9A0-65D5-40BE-AFEA-83593FC0164E} + // Intel-Autologger-iclsProxy {301B773F-50F3-4C8E-83F0-53BA9590A13E} + // Intel-Autologger-PTTEKRecertification {F33E9E07-8792-47E8-B3FA-2C92AB32C5B3} + // ... + // NodeJS-ETW-provider {77754E9B-264B-4D8D-B981-E4135C1ECB0C} + // ... + // OpenSSH {C4B57D35-0636-4BC3-A262-370F249F9802} + // ... + // Windows Connect Now {C100BECE-D33A-4A4B-BF23-BBEF4663D017} + // Windows Defender Firewall API {28C9F48F-D244-45A8-842F-DC9FBC9B6E92} + // Windows Defender Firewall API - GP {0EFF663F-8B6E-4E6D-8182-087A8EAA29CB} + // Windows Defender Firewall Driver {D5E09122-D0B2-4235-ADC1-C89FAAAF1069} + + std::string providerName = kGlobalProviderName; // supply unique instrumentation name here + exporter::etw::TracerProvider tp; + + auto tracer = tp.GetTracer(providerName); + + // Span attributes + Properties attribs = + { + {"attrib1", 1}, + {"attrib2", 2} + }; + { + auto topSpan = tracer->StartSpan("MySpanTop"); + auto topScope = tracer->WithActiveSpan(topSpan); + { + auto outerSpan = tracer->StartSpan("MySpanL2", attribs); + auto outerScope = tracer->WithActiveSpan(outerSpan); + + // Create nested span. Note how we share the attributes here. + // It is Okay to either reuse/share or have your own attributes. + { + auto innerSpan = tracer->StartSpan("MySpanL3", attribs); + auto innerScope = tracer->WithActiveSpan(innerSpan); + + // Add span attribute + EXPECT_NO_THROW(outerSpan->SetAttribute("AttrName1", "AttrValue1")); + + // Add first event + std::string eventName1 = "MyEvent1"; + Properties event1 = + { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"} + }; + EXPECT_NO_THROW(outerSpan->AddEvent(eventName1, event1)); + + // Add second event + std::string eventName2 = "MyEvent2"; + Properties event2 = + { + {"uint32Key", (uint32_t)9876}, + {"uint64Key", (uint64_t)987654321}, + {"strKey", "anotherValue"} + }; + EXPECT_NO_THROW(outerSpan->AddEvent(eventName2, event2)); + + std::string eventName3= "MyEvent3"; + Properties event3 = + { + /* Extra metadata that allows event to flow to A.I. pipeline */ + {"metadata", "ai_event"}, + {"uint32Key", (uint32_t)9876}, + {"uint64Key", (uint64_t)987654321}, + // {"int32array", {{-1,0,1,2,3}} }, + {"tempString", getTemporaryValue() } + }; + EXPECT_NO_THROW(innerSpan->AddEvent(eventName3, event3)); + EXPECT_NO_THROW(innerSpan->End()); + + } + EXPECT_NO_THROW(outerSpan->End()); + + } + EXPECT_NO_THROW(topSpan->End()); + } + + EXPECT_NO_THROW(tracer->CloseWithMicroseconds(0)); +} + +// Lowest decoration level -> smaller ETW event size. +// Expected output in C# listener on the other side: +// no ActivityID GUID, no SpanId, no TraceId. +/* +{ + "Timestamp": "2021-03-19T21:04:38.411193-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 13, + "Message": null, + "ProcessId": 15120, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "C.min/Stop", + "ActivityID": null, + "RelatedActivityID": null, + "Payload": {} +} +*/ +TEST(ETWTracer, TracerCheckMinDecoration) +{ + std::string providerName = kGlobalProviderName; + exporter::etw::TracerProvider tp + ({ + {"enableTraceId", false}, + {"enableSpanId", false}, + {"enableActivityId", false}, + {"enableActivityTracking", true}, + {"enableRelatedActivityId", false}, + {"enableAutoParent", false} + }); + auto tracer = tp.GetTracer(providerName); + { + auto aSpan = tracer->StartSpan("A.min"); + auto aScope = tracer->WithActiveSpan(aSpan); + { + auto bSpan = tracer->StartSpan("B.min"); + auto bScope = tracer->WithActiveSpan(bSpan); + { + auto cSpan = tracer->StartSpan("C.min"); + auto cScope = tracer->WithActiveSpan(cSpan); + EXPECT_NO_THROW(cSpan->End()); + } + EXPECT_NO_THROW(bSpan->End()); + } + EXPECT_NO_THROW(aSpan->End()); +} + tracer->CloseWithMicroseconds(0); +} + +// Highest decoration level -> larger ETW event size +// Expected output in C# listener on the other side: +// ActivityID GUID (==SpanId), SpanId, TraceId. +/* +{ + "Timestamp": "2021-03-19T21:04:38.4120274-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 21, + "Message": null, + "ProcessId": 15120, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "C.max/Stop", + "ActivityID": "d55a2c25-8033-40ab-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "SpanId": "252c5ad53380ab40", + "TraceId": "4dea2a63c188894ea5ab979e5cd7ec36" + } +} +*/ +TEST(ETWTracer, TracerCheckMaxDecoration) +{ + std::string providerName = kGlobalProviderName; + exporter::etw::TracerProvider tp + ({ + {"enableTraceId", true}, + {"enableSpanId", true}, + {"enableActivityId", true}, + {"enableRelatedActivityId", true}, + {"enableAutoParent", true} + }); + auto tracer = tp.GetTracer(providerName); + { + auto aSpan = tracer->StartSpan("A.max"); + auto aScope = tracer->WithActiveSpan(aSpan); + { + auto bSpan = tracer->StartSpan("B.max"); + auto bScope = tracer->WithActiveSpan(bSpan); + { + auto cSpan = tracer->StartSpan("C.max"); + auto cScope = tracer->WithActiveSpan(cSpan); + EXPECT_NO_THROW(cSpan->End()); + } + EXPECT_NO_THROW(bSpan->End()); + } + EXPECT_NO_THROW(aSpan->End()); + } + tracer->CloseWithMicroseconds(0); +} + +TEST(ETWTracer, TracerCheckMsgPack) +{ + std::string providerName = "OpenTelemetry-ETW-MsgPack"; + exporter::etw::TracerProvider tp + ({ + {"enableTraceId", true}, + {"enableSpanId", true}, + {"enableActivityId", true}, + {"enableRelatedActivityId", true}, + {"enableAutoParent", true} + }); + auto tracer = tp.GetTracer(providerName); + { + auto aSpan = tracer->StartSpan("A.max"); + auto aScope = tracer->WithActiveSpan(aSpan); + { + auto bSpan = tracer->StartSpan("B.max"); + auto bScope = tracer->WithActiveSpan(bSpan); + { + auto cSpan = tracer->StartSpan("C.max"); + auto cScope = tracer->WithActiveSpan(cSpan); + std::string eventName = "MyMsgPackEvent"; + Properties event = + { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"} + }; + cSpan->AddEvent(eventName, event); + EXPECT_NO_THROW(cSpan->End()); + } + EXPECT_NO_THROW(bSpan->End()); + } + EXPECT_NO_THROW(aSpan->End()); + } + tracer->CloseWithMicroseconds(0); +} + +/** + * @brief Global Tracer singleton may be placed in .h header and + * shared across different compilation units. All would get the + * same object. + * + * @return Single global tracer instance. +*/ +static OPENTELEMETRY_NAMESPACE::trace::TracerProvider& GetGlobalTracerProvider() +{ + static exporter::etw::TracerProvider tp + ({ + {"enableTraceId", true}, + {"enableSpanId", true}, + {"enableActivityId", true}, + {"enableRelatedActivityId", true}, + {"enableAutoParent", true} + }); + return tp; +} + +static OPENTELEMETRY_NAMESPACE::trace::Tracer& GetGlobalTracer() +{ + static auto tracer = GetGlobalTracerProvider().GetTracer(kGlobalProviderName); + return (*tracer.get()); +} + +TEST(ETWTracer, GlobalSingletonTracer) +{ + // Obtain a global tracer using C++11 magic static. + auto& globalTracer = GetGlobalTracer(); + auto s1 = globalTracer.StartSpan("Span1"); + auto traceId1 = s1->GetContext().trace_id(); + s1->End(); +/* === Span 1 - "TraceId": "182a64258fb1864ca4e1a542eecbd9bf" +{ + "Timestamp": "2021-05-10T11:45:27.028827-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 5, + "Message": null, + "ProcessId": 23712, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "6ed94703-6b0a-4e76-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 0, + "Kind": 1, + "Name": "Span1", + "SpanId": "0347d96e0a6b764e", + "StartTime": "2021-05-10T18:45:27.028000Z", + "StatusCode": 0, + "StatusMessage": "", + "Success": "True", + "TraceId": "182a64258fb1864ca4e1a542eecbd9bf", + "_name": "Span" + } +} +*/ + + // Obtain a different tracer withs its own trace-id. + auto localTracer = GetGlobalTracerProvider().GetTracer(kGlobalProviderName); + auto s2 = localTracer->StartSpan("Span2"); + auto traceId2 = s2->GetContext().trace_id(); + s2->End(); +/* === Span 2 - "TraceId": "334bf9a1eed98d40a873a606295a9368" +{ + "Timestamp": "2021-05-10T11:45:27.0289654-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 5, + "Message": null, + "ProcessId": 23712, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "3b7b2ecb-2e84-4903-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 0, + "Kind": 1, + "Name": "Span2", + "SpanId": "cb2e7b3b842e0349", + "StartTime": "2021-05-10T18:45:27.028000Z", + "StatusCode": 0, + "StatusMessage": "", + "Success": "True", + "TraceId": "334bf9a1eed98d40a873a606295a9368", + "_name": "Span" + } +} +*/ + + // Obtain the same global tracer with the same trace-id as before. + auto& globalTracer2 = GetGlobalTracer(); + auto s3 = globalTracer2.StartSpan("Span3"); + auto traceId3 = s3->GetContext().trace_id(); + s3->End(); +/* === Span 3 - "TraceId": "182a64258fb1864ca4e1a542eecbd9bf" +{ + "Timestamp": "2021-05-10T11:45:27.0290936-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 5, + "Message": null, + "ProcessId": 23712, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "0a970247-ba0e-4d4b-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 1, + "Kind": 1, + "Name": "Span3", + "SpanId": "4702970a0eba4b4d", + "StartTime": "2021-05-10T18:45:27.028000Z", + "StatusCode": 0, + "StatusMessage": "", + "Success": "True", + "TraceId": "182a64258fb1864ca4e1a542eecbd9bf", + "_name": "Span" + } +} +*/ + EXPECT_NE(traceId1, traceId2); + EXPECT_EQ(traceId1, traceId3); + + localTracer->CloseWithMicroseconds(0); + globalTracer.CloseWithMicroseconds(0); +} + +/* clang-format on */ + +#endif |