diff --git a/CHANGELOG.md b/CHANGELOG.md index 9277990f5e..3671046656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Increment the: ## [Unreleased] +* [ETW Exporter]Support serialize span/log attributes into JSON + [#1991](https://github.com/open-telemetry/opentelemetry-cpp/pull/1991) * ETW Exporter]Do not overwrite ParentId when setting attribute on Span [#1989](https://github.com/open-telemetry/opentelemetry-cpp/pull/1989) * Convert Prometheus Exporter to Pull MetricReader [#1953](https://github.com/open-telemetry/opentelemetry-cpp/pull/1953) diff --git a/exporters/etw/CMakeLists.txt b/exporters/etw/CMakeLists.txt index a5da4859a6..43f8b6bdb9 100644 --- a/exporters/etw/CMakeLists.txt +++ b/exporters/etw/CMakeLists.txt @@ -34,6 +34,8 @@ 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_tracer_test_enable_env_properties test/etw_tracer_test.cc) + add_executable(etw_logger_test_enable_env_properties test/etw_logger_test.cc) target_link_libraries(etw_provider_test ${GTEST_BOTH_LIBRARIES} opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) @@ -44,6 +46,18 @@ if(BUILD_TESTING) target_link_libraries(etw_logger_test ${GTEST_BOTH_LIBRARIES} opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries( + etw_tracer_test_enable_env_properties ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + target_compile_definitions(etw_tracer_test_enable_env_properties + PRIVATE ENABLE_ENV_PROPERTIES) + + target_link_libraries( + etw_logger_test_enable_env_properties ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + target_compile_definitions(etw_logger_test_enable_env_properties + PRIVATE ENABLE_ENV_PROPERTIES) + if(WITH_BENCHMARK) add_executable(etw_perf_test test/etw_perf_test.cc) target_link_libraries( @@ -63,5 +77,13 @@ if(BUILD_TESTING) TARGET etw_logger_test TEST_PREFIX exporter. TEST_LIST etw_logger_test) + gtest_add_tests( + TARGET etw_tracer_test_enable_env_properties + TEST_PREFIX exporter.with_env_properties. + TEST_LIST etw_tracer_test_enable_env_properties) + gtest_add_tests( + TARGET etw_logger_test_enable_env_properties + TEST_PREFIX exporter.with_env_properties. + TEST_LIST etw_logger_test_enable_env_properties) endif() # BUILD_TESTING diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h index a2fd6c727c..041929d1c0 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h @@ -131,12 +131,13 @@ # define ETW_VALUE_SPAN_END "SpanEnd" /* ETW for Span Start */ +# define ETW_FIELD_ENV_PROPERTIES "env_properties" /* ETW event_properties with JSON string */ + /* 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/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h index 8019c36105..caa4fe5133 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h @@ -222,12 +222,49 @@ class Logger : public opentelemetry::logs::Logger virtual void Log(opentelemetry::logs::Severity severity, nostd::string_view name, nostd::string_view body, - Properties &evt, + Properties &input_evt, opentelemetry::trace::TraceId trace_id, opentelemetry::trace::SpanId span_id, opentelemetry::trace::TraceFlags trace_flags, common::SystemTimestamp timestamp) noexcept { +# if defined(ENABLE_ENV_PROPERTIES) + + Properties env_properties_env = {}; + bool has_customer_attribute = false; + if (input_evt.size() > 0) + { + nlohmann::json env_properties_json = nlohmann::json::object(); + for (auto &kv : input_evt) + { + nostd::string_view key = kv.first.data(); + + // don't serialize fields propagated from span data. + if (key == ETW_FIELD_NAME || key == ETW_FIELD_SPAN_ID || key == ETW_FIELD_TRACE_ID || + key == ETW_FIELD_SPAN_PARENTID) + { + env_properties_env[key.data()] = kv.second; + } + else + { + utils::PopulateAttribute(env_properties_json, key, kv.second); + has_customer_attribute = true; + } + } + if (has_customer_attribute) + { + env_properties_env[ETW_FIELD_ENV_PROPERTIES] = env_properties_json.dump(); + } + } + + Properties &evt = has_customer_attribute ? env_properties_env : input_evt; + +# else + + Properties &evt = input_evt; + +# endif // defined(ENABLE_ENV_PROPERTIES) + // Populate Etw.EventName attribute at envelope level evt[ETW_FIELD_NAME] = ETW_VALUE_LOG; diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h index d98373f374..039351f66e 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h @@ -250,8 +250,41 @@ class Tracer : public opentelemetry::trace::Tracer, auto spanContext = spanBase.GetContext(); // Populate Span with presaved attributes - Span ¤tSpan = const_cast(span); - Properties evt = GetSpanAttributes(currentSpan); + Span ¤tSpan = const_cast(span); + Properties evt = GetSpanAttributes(currentSpan); + +#if defined(ENABLE_ENV_PROPERTIES) + + Properties env_properties_env = {}; + if (evt.size() > 0) + { + bool has_customer_attribute = false; + nlohmann::json env_properties_json = nlohmann::json::object(); + for (auto &kv : evt) + { + nostd::string_view key = kv.first.data(); + + // don't serialize fields propagated from span data. + if (key == ETW_FIELD_NAME || key == ETW_FIELD_SPAN_ID || key == ETW_FIELD_TRACE_ID || + key == ETW_FIELD_SPAN_PARENTID) + { + env_properties_env[key.data()] = kv.second; + } + else + { + utils::PopulateAttribute(env_properties_json, key, kv.second); + has_customer_attribute = true; + } + } + if (has_customer_attribute) + { + env_properties_env[ETW_FIELD_ENV_PROPERTIES] = env_properties_json.dump(); + evt = std::move(env_properties_env); + } + } + +#endif // defined(ENABLE_ENV_PROPERTIES) + evt[ETW_FIELD_NAME] = GetName(span); if (cfg.enableSpanId) diff --git a/exporters/etw/include/opentelemetry/exporters/etw/utils.h b/exporters/etw/include/opentelemetry/exporters/etw/utils.h index ef6edc5ca2..6a887b6831 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/utils.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/utils.h @@ -27,6 +27,13 @@ # include #endif +#if defined(ENABLE_ENV_PROPERTIES) + +# include +# include "etw_properties.h" + +#endif + OPENTELEMETRY_BEGIN_NAMESPACE namespace utils @@ -270,6 +277,112 @@ static inline std::string formatUtcTimestampNsAsISO8601(int64_t timestampNs) return buf; } +#if defined(ENABLE_ENV_PROPERTIES) + +static inline void PopulateAttribute(nlohmann::json &attribute, + nostd::string_view key, + const exporter::etw::PropertyValue &value) +{ + if (nostd::holds_alternative(value)) + { + attribute[key.data()] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attribute[key.data()] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attribute[key.data()] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attribute[key.data()] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attribute[key.data()] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attribute[key.data()] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attribute[key.data()] = std::string(nostd::get(value)); + } + else if (nostd::holds_alternative(value)) + { + attribute[key.data()] = nostd::get(value); + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get>(value)) + { + attribute[key.data()].push_back(val); + } + } +} + +#endif // defined(ENABLE_ENV_PROPERTIES) + }; // namespace utils OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/etw/test/etw_tracer_test.cc b/exporters/etw/test/etw_tracer_test.cc index 9095de4961..a7aa6b168b 100644 --- a/exporters/etw/test/etw_tracer_test.cc +++ b/exporters/etw/test/etw_tracer_test.cc @@ -117,22 +117,25 @@ TEST(ETWTracer, TracerCheck) auto tracer = tp.GetTracer(providerName); // Span attributes - Properties attribs = + Properties outer_attribs = { {"attrib1", 1}, {"attrib2", 2} }; + // copy the outer attributes + Properties inner_attribs = outer_attribs; + { auto topSpan = tracer->StartSpan("MySpanTop"); auto topScope = tracer->WithActiveSpan(topSpan); { - auto outerSpan = tracer->StartSpan("MySpanL2", attribs); + auto outerSpan = tracer->StartSpan("MySpanL2", outer_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 innerSpan = tracer->StartSpan("MySpanL3", inner_attribs); auto innerScope = tracer->WithActiveSpan(innerSpan); // Add span attribute