From 5e55a5be790356bbf3c0734271bf78c2226ceffe Mon Sep 17 00:00:00 2001 From: DJ Gregor Date: Fri, 3 Dec 2021 12:34:47 -0500 Subject: [PATCH] Add JSON support to OStreamSpanExporter --- exporters/ostream/CMakeLists.txt | 8 +- .../exporters/ostream/span_exporter.h | 29 +- exporters/ostream/src/span_exporter.cc | 358 ++++++++++++++++-- 3 files changed, 354 insertions(+), 41 deletions(-) diff --git a/exporters/ostream/CMakeLists.txt b/exporters/ostream/CMakeLists.txt index e83719cce3..7d9c53c84a 100644 --- a/exporters/ostream/CMakeLists.txt +++ b/exporters/ostream/CMakeLists.txt @@ -8,7 +8,13 @@ target_include_directories( PUBLIC "$") target_link_libraries(opentelemetry_exporter_ostream_span - PUBLIC opentelemetry_trace) + PUBLIC opentelemetry_trace + nlohmann_json::nlohmann_json) + +if(nlohmann_json_clone) + add_dependencies(opentelemetry_exporter_otlp_http_client + nlohmann_json::nlohmann_json) +endif() install( TARGETS opentelemetry_exporter_ostream_span diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h index 8122b6777a..a5833bacc5 100644 --- a/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h @@ -10,9 +10,12 @@ #include "opentelemetry/version.h" #include +#include #include #include +#include "nlohmann/json.hpp" + OPENTELEMETRY_BEGIN_NAMESPACE namespace exporter { @@ -30,7 +33,7 @@ class OStreamSpanExporter final : public opentelemetry::sdk::trace::SpanExporter * export() function will send span data into. * The default ostream is set to stdout */ - explicit OStreamSpanExporter(std::ostream &sout = std::cout) noexcept; + explicit OStreamSpanExporter(std::ostream &sout = std::cout, bool isJson = false) noexcept; std::unique_ptr MakeRecordable() noexcept override; @@ -43,6 +46,7 @@ class OStreamSpanExporter final : public opentelemetry::sdk::trace::SpanExporter private: std::ostream &sout_; + bool isJson_; bool is_shutdown_ = false; mutable opentelemetry::common::SpinLockMutex lock_; bool isShutdown() const noexcept; @@ -112,7 +116,30 @@ class OStreamSpanExporter final : public opentelemetry::sdk::trace::SpanExporter #endif } + // various format helpers + std::string formatTraceId(opentelemetry::trace::TraceId trace_id); + + std::string formatSpanId(opentelemetry::trace::SpanId span_id); + + // various json helpers + nlohmann::basic_json formatContext(const opentelemetry::trace::SpanContext &context); + + nlohmann::basic_json formatAttributes(const std::unordered_map &attributes); + + std::list> formatEvents(const std::vector &events); + + std::list> formatLinks(const std::vector &links); + + void PopulateAttribute(nostd::string_view key, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + nlohmann::basic_json &attributes); + // various print helpers + void printSpanJson(const std::unique_ptr &span) noexcept; + + void printSpanText(const std::unique_ptr &span) noexcept; + void printAttributes( const std::unordered_map &map, const std::string prefix = "\n\t"); diff --git a/exporters/ostream/src/span_exporter.cc b/exporters/ostream/src/span_exporter.cc index dea72f57f8..eb9abda8fd 100644 --- a/exporters/ostream/src/span_exporter.cc +++ b/exporters/ostream/src/span_exporter.cc @@ -5,6 +5,9 @@ #include #include #include "opentelemetry/sdk_config.h" +#include + +#include "nlohmann/json.hpp" namespace nostd = opentelemetry::nostd; namespace trace_sdk = opentelemetry::sdk::trace; @@ -17,25 +20,66 @@ namespace exporter namespace trace { -std::ostream &operator<<(std::ostream &os, trace_api::SpanKind span_kind) +// This is copied/tweaked from what is in etw/utils.h +/** + * @brief Convert local system nanoseconds time to ISO8601 string UTC time + * + * @param nanoseconds Nanoseconds since epoch + * + * @return ISO8601 UTC string with nanosecond precision + */ +static inline std::string formatUtcTimestampNsAsISO8601(std::chrono::nanoseconds nanoseconds) +{ + char buf[sizeof("YYYY-MM-DDTHH:MM:SS.sssssssssZ") + 1] = {0}; +#ifdef _WIN32 + __time64_t seconds = static_cast<__time64_t>(nanoseconds.count() / 1000000000); + int nano_part = static_cast(nanoseconds.count() % 1000000000); + tm tm; + if (::_gmtime64_s(&tm, &seconds) != 0) + { + memset(&tm, 0, sizeof(tm)); + } + ::_snprintf_s(buf, _TRUNCATE, "%04d-%02d-%02dT%02d:%02d:%02d.%09dZ", 1900 + tm.tm_year, + 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, nano_part); +#else + time_t seconds = static_cast(nanoseconds.count() / 1000000000); + int nano_part = static_cast(nanoseconds.count() % 1000000000); + 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.%09dZ", 1900 + tm.tm_year, + 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, nano_part); +#endif + return buf; +} + +std::string formatKind(trace_api::SpanKind span_kind) { switch (span_kind) { case trace_api::SpanKind::kClient: - return os << "Client"; + return "Client"; case trace_api::SpanKind::kInternal: - return os << "Internal"; + return "Internal"; case trace_api::SpanKind::kServer: - return os << "Server"; + return "Server"; case trace_api::SpanKind::kProducer: - return os << "Producer"; + return "Producer"; case trace_api::SpanKind::kConsumer: - return os << "Consumer"; + return "Consumer"; }; - return os << ""; + return ""; } -OStreamSpanExporter::OStreamSpanExporter(std::ostream &sout) noexcept : sout_(sout) {} +std::ostream &operator<<(std::ostream &os, trace_api::SpanKind span_kind) +{ + return os << formatKind(span_kind); +} + +OStreamSpanExporter::OStreamSpanExporter(std::ostream &sout, bool isJson) noexcept : sout_(sout) { isJson_ = isJson; } std::unique_ptr OStreamSpanExporter::MakeRecordable() noexcept { @@ -59,43 +103,46 @@ sdk::common::ExportResult OStreamSpanExporter::Export( if (span != nullptr) { - - char trace_id[32] = {0}; - char span_id[16] = {0}; - char parent_span_id[16] = {0}; - - span->GetTraceId().ToLowerBase16(trace_id); - span->GetSpanId().ToLowerBase16(span_id); - span->GetParentSpanId().ToLowerBase16(parent_span_id); - - sout_ << "{" - << "\n name : " << span->GetName() - << "\n trace_id : " << std::string(trace_id, 32) - << "\n span_id : " << std::string(span_id, 16) - << "\n tracestate : " << span->GetSpanContext().trace_state()->ToHeader() - << "\n parent_span_id: " << std::string(parent_span_id, 16) - << "\n start : " << span->GetStartTime().time_since_epoch().count() - << "\n duration : " << span->GetDuration().count() - << "\n description : " << span->GetDescription() - << "\n span kind : " << span->GetSpanKind() - << "\n status : " << statusMap[int(span->GetStatus())] - << "\n attributes : "; - printAttributes(span->GetAttributes()); - sout_ << "\n events : "; - printEvents(span->GetEvents()); - sout_ << "\n links : "; - printLinks(span->GetLinks()); - sout_ << "\n resources : "; - printResources(span->GetResource()); - sout_ << "\n instr-lib : "; - printInstrumentationLibrary(span->GetInstrumentationLibrary()); - sout_ << "\n}\n"; + if (isJson_) + { + printSpanJson(span); + } + else + { + printSpanText(span); + } } } return sdk::common::ExportResult::kSuccess; } +void OStreamSpanExporter::printSpanText(const std::unique_ptr &span) noexcept +{ + sout_ << "{" + << "\n name : " << span->GetName() + << "\n trace_id : " << formatTraceId(span->GetTraceId()) + << "\n span_id : " << formatSpanId(span->GetSpanId()) + << "\n tracestate : " << span->GetSpanContext().trace_state()->ToHeader() + << "\n parent_span_id: " << formatSpanId(span->GetParentSpanId()) + << "\n start : " << span->GetStartTime().time_since_epoch().count() + << "\n duration : " << span->GetDuration().count() + << "\n description : " << span->GetDescription() + << "\n span kind : " << span->GetSpanKind() + << "\n status : " << statusMap[int(span->GetStatus())] + << "\n attributes : "; + printAttributes(span->GetAttributes()); + sout_ << "\n events : "; + printEvents(span->GetEvents()); + sout_ << "\n links : "; + printLinks(span->GetLinks()); + sout_ << "\n resources : "; + printResources(span->GetResource()); + sout_ << "\n instr-lib : "; + printInstrumentationLibrary(span->GetInstrumentationLibrary()); + sout_ << "\n}\n"; +} + bool OStreamSpanExporter::Shutdown(std::chrono::microseconds timeout) noexcept { const std::lock_guard locked(lock_); @@ -171,6 +218,239 @@ void OStreamSpanExporter::printInstrumentationLibrary( } } +std::string OStreamSpanExporter::formatTraceId(opentelemetry::trace::TraceId trace_id) +{ + char t[2 * trace_api::TraceId::kSize] = {0}; + + trace_id.ToLowerBase16(t); + + return std::string(t, 2 * trace_api::TraceId::kSize); +} + +std::string OStreamSpanExporter::formatSpanId(opentelemetry::trace::SpanId span_id) +{ + char s[2 * trace_api::SpanId::kSize] = {0}; + + span_id.ToLowerBase16(s); + + return std::string(s, 2 * trace_api::SpanId::kSize); +} + +void OStreamSpanExporter::printSpanJson(const std::unique_ptr &span) noexcept +{ + nlohmann::ordered_json j = nlohmann::ordered_json::object(); + + j["name"] = span->GetName(); + j["context"] = formatContext(span->GetSpanContext()); + j["kind"] = formatKind(span->GetSpanKind()); + + if (span->GetParentSpanId().IsValid()) + { + j["parent_id"] = "0x" + formatSpanId(span->GetParentSpanId()); + } + + j["start_time"] = formatUtcTimestampNsAsISO8601(span->GetStartTime().time_since_epoch()); + j["end_time"] = formatUtcTimestampNsAsISO8601(span->GetStartTime().time_since_epoch() + span->GetDuration()); + + if (span->GetStatus() != trace_api::StatusCode::kUnset) + { + j["status"] = statusMap[int(span->GetStatus())]; + if (span->GetDescription().size() > 0) + { + j["description"] = span->GetDescription(); + } + } + + if (span->GetAttributes().size() > 0) + { + j["attributes"] = formatAttributes(span->GetAttributes()); + } + + if (span->GetEvents().size() > 0) + { + j["events"] = formatEvents(span->GetEvents()); + } + + if (span->GetLinks().size() > 0) + { + j["links"] = formatLinks(span->GetLinks()); + } + + if (span->GetResource().GetAttributes().size() > 0) + { + j["resource"] = formatAttributes(span->GetResource().GetAttributes()); + } + + sout_ << j.dump(4, ' ', false, nlohmann::detail::error_handler_t::replace) << "\n"; +} + +// This is copied/tweaked from what is in otlp_recordable_utils.cc +// +// See `attribute_value.h` for details. +// +const int kOwnedAttributeValueSize = 15; + +void OStreamSpanExporter::PopulateAttribute(nostd::string_view key, + const sdk::common::OwnedAttributeValue &value, + nlohmann::basic_json &attributes) +{ + if (nullptr == attributes) + { + return; + } + + // Assert size of variant to ensure that this method gets updated if the variant + // definition changes + static_assert(nostd::variant_size::value == + kOwnedAttributeValueSize, + "OwnedAttributeValue contains unknown type"); + + if (nostd::holds_alternative(value)) + { + attributes[std::string{key}] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attributes[std::string{key}] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attributes[std::string{key}] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attributes[std::string{key}] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attributes[std::string{key}] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attributes[std::string{key}] = nostd::get(value); + } + else if (nostd::holds_alternative(value)) + { + attributes[std::string{key}] = nostd::get(value); + } + /* + else if (nostd::holds_alternative>(value)) + { + for (const auto &val : nostd::get>(value)) + { + ... + } + } + else if (nostd::holds_alternative>(value)) + { + for (const auto &val : nostd::get>(value)) + { + ... + } + } + else if (nostd::holds_alternative>(value)) + { + for (const auto &val : nostd::get>(value)) + { + ... + } + } + else if (nostd::holds_alternative>(value)) + { + for (const auto &val : nostd::get>(value)) + { + ... + } + } + else if (nostd::holds_alternative>(value)) + { + for (const auto &val : nostd::get>(value)) + { + ... + } + } + else if (nostd::holds_alternative>(value)) + { + for (const auto &val : nostd::get>(value)) + { + ... + } + } + else if (nostd::holds_alternative>(value)) + { + for (const auto &val : nostd::get>(value)) + { + ... + } + } + */ +} + +nlohmann::basic_json OStreamSpanExporter::formatContext(const trace_api::SpanContext &context) +{ + auto c = nlohmann::basic_json(); + + c["trace_id"] = "0x" + formatTraceId(context.trace_id()); + c["span_id"] = "0x" + formatSpanId(context.span_id()); + c["trace_state"] = context.trace_state()->ToHeader(); + + return c; +} + +nlohmann::basic_json OStreamSpanExporter::formatAttributes(const std::unordered_map &attributes) +{ + auto c = nlohmann::basic_json(); + + for (const auto &kv : attributes) + { + PopulateAttribute(kv.first, kv.second, c); + } + + return c; +} + +std::list> OStreamSpanExporter::formatEvents(const std::vector &events) +{ + auto json_events = std::list>(); + + for (const auto &event : events) + { + auto e = nlohmann::basic_json(); + + e["name"] = event.GetName(); + e["timestamp"] = formatUtcTimestampNsAsISO8601(event.GetTimestamp().time_since_epoch()); + + if (event.GetAttributes().size() > 0) + { + e["attributes"] = formatAttributes(event.GetAttributes()); + } + + json_events.push_back(e); + } + + return json_events; +} + +std::list> OStreamSpanExporter::formatLinks(const std::vector &links) +{ + auto json_links = std::list>(); + + for (const auto &link : links) + { + auto l = nlohmann::basic_json(); + + l["context"] = formatContext(link.GetSpanContext()); + if (link.GetAttributes().size() > 0) + { + l["attributes"] = formatAttributes(link.GetAttributes()); + } + + json_links.push_back(l); + } + + return json_links; +} + } // namespace trace } // namespace exporter OPENTELEMETRY_END_NAMESPACE