Skip to content

Commit 18afd17

Browse files
authored
[part 4] fix(telemetry): report logs (#199)
Changes: - Add unit tests. - Report `stack_trace` and `timestamp`.
1 parent c6ca2f0 commit 18afd17

File tree

8 files changed

+129
-6
lines changed

8 files changed

+129
-6
lines changed

include/datadog/telemetry/telemetry.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,10 @@ void report_warning_log(std::string message);
8787
/// @param message The error message.
8888
void report_error_log(std::string message);
8989

90+
/// Report internal error message to Datadog.
91+
///
92+
/// @param message The error message.
93+
/// @param stacktrace Stacktrace leading to the error.
94+
void report_error_log(std::string message, std::string stacktrace);
95+
9096
} // namespace datadog::telemetry

src/datadog/telemetry/log.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#include <datadog/optional.h>
2+
#include <datadog/string_view.h>
3+
14
#include <string>
25

36
namespace datadog::telemetry {
@@ -7,6 +10,20 @@ enum class LogLevel : char { ERROR, WARNING };
710
struct LogMessage final {
811
std::string message;
912
LogLevel level;
13+
tracing::Optional<std::string> stacktrace;
14+
std::chrono::seconds::rep timestamp;
1015
};
1116

17+
inline tracing::StringView to_string(LogLevel level) {
18+
switch (level) {
19+
case LogLevel::ERROR:
20+
return "ERROR";
21+
case LogLevel::WARNING:
22+
return "WARNING";
23+
}
24+
25+
// Unreachable.
26+
return "";
27+
}
28+
1229
} // namespace datadog::telemetry

src/datadog/telemetry/telemetry.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,14 @@ void report_error_log(std::string message) {
116116
instance());
117117
}
118118

119+
void report_error_log(std::string message, std::string stacktrace) {
120+
std::visit(details::Overload{
121+
[&](Telemetry& telemetry) {
122+
telemetry.log_error(message, stacktrace);
123+
},
124+
[](auto&&) {},
125+
},
126+
instance());
127+
}
128+
119129
} // namespace datadog::telemetry

src/datadog/telemetry/telemetry_impl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ void Telemetry::log_error(std::string message) {
201201
tracer_telemetry_->log(std::move(message), LogLevel::ERROR);
202202
}
203203

204+
void Telemetry::log_error(std::string message, std::string stacktrace) {
205+
tracer_telemetry_->log(std::move(message), LogLevel::ERROR, stacktrace);
206+
}
207+
204208
void Telemetry::log_warning(std::string message) {
205209
tracer_telemetry_->log(std::move(message), LogLevel::WARNING);
206210
}

src/datadog/telemetry/telemetry_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class Telemetry final {
5858
///
5959
/// @param message The error message.
6060
void log_error(std::string message);
61+
void log_error(std::string message, std::string stacktrace);
6162

6263
/// capture and report internal warning message to Datadog.
6364
///

src/datadog/tracer_telemetry.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ std::string to_string(datadog::tracing::ConfigName name) {
5555
std::abort();
5656
}
5757

58+
nlohmann::json encode_log(const telemetry::LogMessage& log) {
59+
auto encoded = nlohmann::json{
60+
{"message", log.message},
61+
{"level", to_string(log.level)},
62+
{"tracer_time", log.timestamp},
63+
};
64+
if (log.stacktrace) {
65+
encoded.emplace("stack_trace", *log.stacktrace);
66+
}
67+
return encoded;
68+
}
69+
5870
} // namespace
5971

6072
TracerTelemetry::TracerTelemetry(
@@ -275,8 +287,7 @@ std::string TracerTelemetry::heartbeat_and_telemetry() {
275287
if (!logs_.empty()) {
276288
auto encoded_logs = nlohmann::json::array();
277289
for (const auto& log : logs_) {
278-
auto encoded =
279-
nlohmann::json{{"message", log.message}, {"level", log.level}};
290+
auto encoded = encode_log(log);
280291
encoded_logs.emplace_back(std::move(encoded));
281292
}
282293

@@ -352,8 +363,7 @@ std::string TracerTelemetry::app_closing() {
352363
if (!logs_.empty()) {
353364
auto encoded_logs = nlohmann::json::array();
354365
for (const auto& log : logs_) {
355-
auto encoded =
356-
nlohmann::json{{"message", log.message}, {"level", log.level}};
366+
auto encoded = encode_log(log);
357367
encoded_logs.emplace_back(std::move(encoded));
358368
}
359369

src/datadog/tracer_telemetry.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,13 @@ class TracerTelemetry {
111111
// Construct an `app-client-configuration-change` message.
112112
Optional<std::string> configuration_change();
113113

114-
inline void log(std::string message, telemetry::LogLevel level) {
115-
logs_.emplace_back(telemetry::LogMessage{std::move(message), level});
114+
inline void log(std::string message, telemetry::LogLevel level,
115+
Optional<std::string> stacktrace = nullopt) {
116+
auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
117+
clock_().wall.time_since_epoch())
118+
.count();
119+
logs_.emplace_back(telemetry::LogMessage{std::move(message), level,
120+
stacktrace, timestamp});
116121
}
117122
};
118123

test/telemetry/test_telemetry.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,4 +274,74 @@ TEST_CASE("Tracer telemetry", "[telemetry]") {
274274
auto heartbeat = message_batch["payload"][0];
275275
REQUIRE(heartbeat["request_type"] == "app-closing");
276276
}
277+
278+
SECTION("logs serialization") {
279+
SECTION("log level is correct") {
280+
struct TestCase {
281+
std::string_view name;
282+
std::string input;
283+
Optional<std::string> stacktrace;
284+
std::function<void(Telemetry&, const std::string&,
285+
const Optional<std::string>& stacktrace)>
286+
apply;
287+
std::string expected_log_level;
288+
};
289+
290+
auto test_case = GENERATE(values<TestCase>({
291+
{
292+
"warning log",
293+
"This is a warning log!",
294+
nullopt,
295+
[](Telemetry& telemetry, const std::string& input,
296+
const Optional<std::string>&) {
297+
telemetry.log_warning(input);
298+
},
299+
"WARNING",
300+
},
301+
{
302+
"error log",
303+
"This is an error log!",
304+
nullopt,
305+
[](Telemetry& telemetry, const std::string& input,
306+
const Optional<std::string>&) { telemetry.log_error(input); },
307+
"ERROR",
308+
},
309+
{
310+
"error log with stacktrace",
311+
"This is an error log with a fake stacktrace!",
312+
"error here\nthen here\nfinally here\n",
313+
[](Telemetry& telemetry, const std::string& input,
314+
Optional<std::string> stacktrace) {
315+
telemetry.log_error(input, *stacktrace);
316+
},
317+
"ERROR",
318+
},
319+
}));
320+
321+
CAPTURE(test_case.name);
322+
323+
client->clear();
324+
test_case.apply(telemetry, test_case.input, test_case.stacktrace);
325+
trigger_heartbeat();
326+
327+
auto message_batch = nlohmann::json::parse(client->request_body);
328+
REQUIRE(is_valid_telemetry_payload(message_batch));
329+
REQUIRE(message_batch["payload"].size() == 2);
330+
331+
auto logs_message = message_batch["payload"][1];
332+
REQUIRE(logs_message["request_type"] == "logs");
333+
334+
auto logs_payload = logs_message["payload"]["logs"];
335+
REQUIRE(logs_payload.size() == 1);
336+
CHECK(logs_payload[0]["level"] == test_case.expected_log_level);
337+
CHECK(logs_payload[0]["message"] == test_case.input);
338+
CHECK(logs_payload[0].contains("tracer_time"));
339+
340+
if (test_case.stacktrace) {
341+
CHECK(logs_payload[0]["stack_trace"] == test_case.stacktrace);
342+
} else {
343+
CHECK(logs_payload[0].contains("stack_trace") == false);
344+
}
345+
}
346+
}
277347
}

0 commit comments

Comments
 (0)