From c9e8d1b161bc515f6146b8ec415011a9bf29984c Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Tue, 1 Apr 2025 16:31:00 +0200 Subject: [PATCH] [part 4] fix(telemetry): report logs Changes: - Add unit tests. - Report `stack_trace` and `timestamp`. --- include/datadog/telemetry/telemetry.h | 6 ++ src/datadog/telemetry/log.h | 17 ++++++ src/datadog/telemetry/telemetry.cpp | 10 ++++ src/datadog/telemetry/telemetry_impl.cpp | 4 ++ src/datadog/telemetry/telemetry_impl.h | 1 + src/datadog/tracer_telemetry.cpp | 18 ++++-- src/datadog/tracer_telemetry.h | 9 ++- test/telemetry/test_telemetry.cpp | 70 ++++++++++++++++++++++++ 8 files changed, 129 insertions(+), 6 deletions(-) diff --git a/include/datadog/telemetry/telemetry.h b/include/datadog/telemetry/telemetry.h index 852d75f9..9f5fbc07 100644 --- a/include/datadog/telemetry/telemetry.h +++ b/include/datadog/telemetry/telemetry.h @@ -87,4 +87,10 @@ void report_warning_log(std::string message); /// @param message The error message. void report_error_log(std::string message); +/// Report internal error message to Datadog. +/// +/// @param message The error message. +/// @param stacktrace Stacktrace leading to the error. +void report_error_log(std::string message, std::string stacktrace); + } // namespace datadog::telemetry diff --git a/src/datadog/telemetry/log.h b/src/datadog/telemetry/log.h index 54225a40..ff0c5814 100644 --- a/src/datadog/telemetry/log.h +++ b/src/datadog/telemetry/log.h @@ -1,3 +1,6 @@ +#include +#include + #include namespace datadog::telemetry { @@ -7,6 +10,20 @@ enum class LogLevel : char { ERROR, WARNING }; struct LogMessage final { std::string message; LogLevel level; + tracing::Optional stacktrace; + std::chrono::seconds::rep timestamp; }; +inline tracing::StringView to_string(LogLevel level) { + switch (level) { + case LogLevel::ERROR: + return "ERROR"; + case LogLevel::WARNING: + return "WARNING"; + } + + // Unreachable. + return ""; +} + } // namespace datadog::telemetry diff --git a/src/datadog/telemetry/telemetry.cpp b/src/datadog/telemetry/telemetry.cpp index bcbc4afd..472c79ae 100644 --- a/src/datadog/telemetry/telemetry.cpp +++ b/src/datadog/telemetry/telemetry.cpp @@ -116,4 +116,14 @@ void report_error_log(std::string message) { instance()); } +void report_error_log(std::string message, std::string stacktrace) { + std::visit(details::Overload{ + [&](Telemetry& telemetry) { + telemetry.log_error(message, stacktrace); + }, + [](auto&&) {}, + }, + instance()); +} + } // namespace datadog::telemetry diff --git a/src/datadog/telemetry/telemetry_impl.cpp b/src/datadog/telemetry/telemetry_impl.cpp index 9e9cef8d..7eee29e1 100644 --- a/src/datadog/telemetry/telemetry_impl.cpp +++ b/src/datadog/telemetry/telemetry_impl.cpp @@ -201,6 +201,10 @@ void Telemetry::log_error(std::string message) { tracer_telemetry_->log(std::move(message), LogLevel::ERROR); } +void Telemetry::log_error(std::string message, std::string stacktrace) { + tracer_telemetry_->log(std::move(message), LogLevel::ERROR, stacktrace); +} + void Telemetry::log_warning(std::string message) { tracer_telemetry_->log(std::move(message), LogLevel::WARNING); } diff --git a/src/datadog/telemetry/telemetry_impl.h b/src/datadog/telemetry/telemetry_impl.h index 097cce0b..4c2f24ae 100644 --- a/src/datadog/telemetry/telemetry_impl.h +++ b/src/datadog/telemetry/telemetry_impl.h @@ -58,6 +58,7 @@ class Telemetry final { /// /// @param message The error message. void log_error(std::string message); + void log_error(std::string message, std::string stacktrace); /// capture and report internal warning message to Datadog. /// diff --git a/src/datadog/tracer_telemetry.cpp b/src/datadog/tracer_telemetry.cpp index ed7910b7..487fd2f6 100644 --- a/src/datadog/tracer_telemetry.cpp +++ b/src/datadog/tracer_telemetry.cpp @@ -55,6 +55,18 @@ std::string to_string(datadog::tracing::ConfigName name) { std::abort(); } +nlohmann::json encode_log(const telemetry::LogMessage& log) { + auto encoded = nlohmann::json{ + {"message", log.message}, + {"level", to_string(log.level)}, + {"tracer_time", log.timestamp}, + }; + if (log.stacktrace) { + encoded.emplace("stack_trace", *log.stacktrace); + } + return encoded; +} + } // namespace TracerTelemetry::TracerTelemetry( @@ -275,8 +287,7 @@ std::string TracerTelemetry::heartbeat_and_telemetry() { if (!logs_.empty()) { auto encoded_logs = nlohmann::json::array(); for (const auto& log : logs_) { - auto encoded = - nlohmann::json{{"message", log.message}, {"level", log.level}}; + auto encoded = encode_log(log); encoded_logs.emplace_back(std::move(encoded)); } @@ -352,8 +363,7 @@ std::string TracerTelemetry::app_closing() { if (!logs_.empty()) { auto encoded_logs = nlohmann::json::array(); for (const auto& log : logs_) { - auto encoded = - nlohmann::json{{"message", log.message}, {"level", log.level}}; + auto encoded = encode_log(log); encoded_logs.emplace_back(std::move(encoded)); } diff --git a/src/datadog/tracer_telemetry.h b/src/datadog/tracer_telemetry.h index ddd3d32e..e1ebc94e 100644 --- a/src/datadog/tracer_telemetry.h +++ b/src/datadog/tracer_telemetry.h @@ -111,8 +111,13 @@ class TracerTelemetry { // Construct an `app-client-configuration-change` message. Optional configuration_change(); - inline void log(std::string message, telemetry::LogLevel level) { - logs_.emplace_back(telemetry::LogMessage{std::move(message), level}); + inline void log(std::string message, telemetry::LogLevel level, + Optional stacktrace = nullopt) { + auto timestamp = std::chrono::duration_cast( + clock_().wall.time_since_epoch()) + .count(); + logs_.emplace_back(telemetry::LogMessage{std::move(message), level, + stacktrace, timestamp}); } }; diff --git a/test/telemetry/test_telemetry.cpp b/test/telemetry/test_telemetry.cpp index 3a8846c6..8d81c13f 100644 --- a/test/telemetry/test_telemetry.cpp +++ b/test/telemetry/test_telemetry.cpp @@ -274,4 +274,74 @@ TEST_CASE("Tracer telemetry", "[telemetry]") { auto heartbeat = message_batch["payload"][0]; REQUIRE(heartbeat["request_type"] == "app-closing"); } + + SECTION("logs serialization") { + SECTION("log level is correct") { + struct TestCase { + std::string_view name; + std::string input; + Optional stacktrace; + std::function& stacktrace)> + apply; + std::string expected_log_level; + }; + + auto test_case = GENERATE(values({ + { + "warning log", + "This is a warning log!", + nullopt, + [](Telemetry& telemetry, const std::string& input, + const Optional&) { + telemetry.log_warning(input); + }, + "WARNING", + }, + { + "error log", + "This is an error log!", + nullopt, + [](Telemetry& telemetry, const std::string& input, + const Optional&) { telemetry.log_error(input); }, + "ERROR", + }, + { + "error log with stacktrace", + "This is an error log with a fake stacktrace!", + "error here\nthen here\nfinally here\n", + [](Telemetry& telemetry, const std::string& input, + Optional stacktrace) { + telemetry.log_error(input, *stacktrace); + }, + "ERROR", + }, + })); + + CAPTURE(test_case.name); + + client->clear(); + test_case.apply(telemetry, test_case.input, test_case.stacktrace); + trigger_heartbeat(); + + auto message_batch = nlohmann::json::parse(client->request_body); + REQUIRE(is_valid_telemetry_payload(message_batch)); + REQUIRE(message_batch["payload"].size() == 2); + + auto logs_message = message_batch["payload"][1]; + REQUIRE(logs_message["request_type"] == "logs"); + + auto logs_payload = logs_message["payload"]["logs"]; + REQUIRE(logs_payload.size() == 1); + CHECK(logs_payload[0]["level"] == test_case.expected_log_level); + CHECK(logs_payload[0]["message"] == test_case.input); + CHECK(logs_payload[0].contains("tracer_time")); + + if (test_case.stacktrace) { + CHECK(logs_payload[0]["stack_trace"] == test_case.stacktrace); + } else { + CHECK(logs_payload[0].contains("stack_trace") == false); + } + } + } }