Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/datadog/telemetry/telemetry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
17 changes: 17 additions & 0 deletions src/datadog/telemetry/log.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#include <datadog/optional.h>
#include <datadog/string_view.h>

#include <string>

namespace datadog::telemetry {
Expand All @@ -7,6 +10,20 @@ enum class LogLevel : char { ERROR, WARNING };
struct LogMessage final {
std::string message;
LogLevel level;
tracing::Optional<std::string> 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
10 changes: 10 additions & 0 deletions src/datadog/telemetry/telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions src/datadog/telemetry/telemetry_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions src/datadog/telemetry/telemetry_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
18 changes: 14 additions & 4 deletions src/datadog/tracer_telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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));
}

Expand Down
9 changes: 7 additions & 2 deletions src/datadog/tracer_telemetry.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,13 @@ class TracerTelemetry {
// Construct an `app-client-configuration-change` message.
Optional<std::string> 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<std::string> stacktrace = nullopt) {
auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
clock_().wall.time_since_epoch())
.count();
logs_.emplace_back(telemetry::LogMessage{std::move(message), level,
stacktrace, timestamp});
}
};

Expand Down
70 changes: 70 additions & 0 deletions test/telemetry/test_telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> stacktrace;
std::function<void(Telemetry&, const std::string&,
const Optional<std::string>& stacktrace)>
apply;
std::string expected_log_level;
};

auto test_case = GENERATE(values<TestCase>({
{
"warning log",
"This is a warning log!",
nullopt,
[](Telemetry& telemetry, const std::string& input,
const Optional<std::string>&) {
telemetry.log_warning(input);
},
"WARNING",
},
{
"error log",
"This is an error log!",
nullopt,
[](Telemetry& telemetry, const std::string& input,
const Optional<std::string>&) { 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<std::string> 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);
}
}
}
}