diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h index d343589699..29e411ce61 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h @@ -4,6 +4,10 @@ #pragma once #include +#if defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) +# include +#endif // defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" @@ -23,10 +27,23 @@ namespace etw /** * @brief TelemetryProvider Options passed via SDK API. */ + +#if defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) +using TelemetryProviderOptions = std::map, + std::set>>; + +#else using TelemetryProviderOptions = std::map< std::string, nostd::variant>>; +#endif // defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + /** * @brief TelemetryProvider runtime configuration class. Internal representation * of TelemetryProviderOptions used by various components of SDK. @@ -45,6 +62,13 @@ typedef struct bool enableTableNameMappings; // Map instrumentation scope name to table name with // `tableNameMappings` std::map tableNameMappings; + +#if defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + + std::set timestampAttributes; // Attributes to use as timestamp + +#endif // defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + } TelemetryProviderConfiguration; /** diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h index c464d572d5..d0aff9324a 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h @@ -323,6 +323,35 @@ class Logger : public opentelemetry::logs::Logger } evt[ETW_FIELD_LOG_SEVERITY_NUM] = static_cast(severity); evt[ETW_FIELD_LOG_BODY] = std::string(body.data(), body.length()); + +#if defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + + for (const auto &attr : cfg.timestampAttributes) + { + auto it = evt.find(attr); + if (it != evt.end()) + { + auto value_index = it->second.index(); + if (value_index != exporter_etw::PropertyType::kTypeInt64 && + value_index != exporter_etw::PropertyType::kTypeUInt64) + { + continue; + } + int64_t filetime = value_index == exporter_etw::PropertyType::kTypeUInt64 + ? nostd::get(it->second) + : nostd::get(it->second); + constexpr int64_t FILETIME_EPOCH_DIFF = 11644473600LL; // Seconds from 1601 to 1970 + constexpr int64_t HUNDRED_NANOSECONDS_PER_SECOND = 10000000LL; + int64_t unix_time_seconds = + (filetime / HUNDRED_NANOSECONDS_PER_SECOND) - FILETIME_EPOCH_DIFF; + int64_t unix_time_nanos = + unix_time_seconds * 1'000'000'000 + (filetime % HUNDRED_NANOSECONDS_PER_SECOND) * 100; + it->second = utils::formatUtcTimestampNsAsISO8601(unix_time_nanos); + } + } + +#endif // defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + etwProvider().write(provHandle, evt, nullptr, nullptr, 0, encoding); } @@ -354,6 +383,12 @@ class LoggerProvider : public opentelemetry::logs::LoggerProvider GetOption(options, "enableTableNameMappings", config_.enableTableNameMappings, false); GetOption(options, "tableNameMappings", config_.tableNameMappings, {}); +#if defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + + GetOption(options, "timestampAttributes", config_.timestampAttributes, {}); + +#endif // defined(OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW) + // Determines what encoding to use for ETW events: TraceLogging Dynamic, MsgPack, XML, etc. config_.encoding = GetEncoding(options); } diff --git a/exporters/etw/test/etw_logger_test.cc b/exporters/etw/test/etw_logger_test.cc index c64769d6ee..f901d784b3 100644 --- a/exporters/etw/test/etw_logger_test.cc +++ b/exporters/etw/test/etw_logger_test.cc @@ -5,8 +5,11 @@ # include # include +# include # include +# define OPENTELEMETRY_ATTRIBUTE_TIMESTAMP_PREVIEW + # include "opentelemetry/exporters/etw/etw_logger_exporter.h" # include "opentelemetry/sdk/trace/simple_processor.h" @@ -146,4 +149,50 @@ TEST(ETWLogger, LoggerCheckWithTableNameMappings) logger->Debug("This is a debug log body", opentelemetry::common::MakeAttributes(attribs))); } +/** + * @brief Logger Test with structured attributes + * + * Example Event for below test: + * { + * "Timestamp": "2024-06-02T15:04:15.4227815-07:00", + * "ProviderName": "OpenTelemetry-ETW-TLD", + * "Id": 1, + * "Message": null, + * "ProcessId": 37696, + * "Level": "Always", + * "Keywords": "0x0000000000000000", + * "EventName": "table1", + * "ActivityID": null, + * "RelatedActivityID": null, + * "Payload": { + * "SpanId": "0000000000000000", + * "Timestamp": "2021-09-30T22:04:15.066411500Z", + * "TraceId": "00000000000000000000000000000000", + * "_name": "table1", + * "tiemstamp1": "2025-02-20T19:18:11.048166700Z", + * "attrib2": "value2", + * "body": "This is a debug log body", + * "severityNumber": 5, + * "severityText": "DEBUG" + * } + * } + * + */ + +TEST(ETWLogger, LoggerCheckWithTimestampAttributes) +{ + std::string providerName = kGlobalProviderName; // supply unique instrumentation name here + std::set timestampAttributes = {{"timestamp1"}}; + exporter::etw::TelemetryProviderOptions options = {{"timestampAttributes", timestampAttributes}}; + exporter::etw::LoggerProvider lp{options}; + + auto logger = lp.GetLogger(providerName, "name1"); + + // Log attributes + Properties attribs = {{"timestamp1", 133845526910481667ULL}, {"attrib2", "value2"}}; + + EXPECT_NO_THROW( + logger->Debug("This is a debug log body", opentelemetry::common::MakeAttributes(attribs))); +} + #endif // _WIN32