-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Include RUM context in crash reports #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7283416
a0b2142
a29a4f1
09786c1
e7d639b
abbd19b
473bd16
73a2a76
9dd9597
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Unless explicitly stated otherwise all files in this repository are licensed | ||
| // under the Apache License Version 2.0. | ||
| // | ||
| // This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| // Copyright 2025-Present Datadog, Inc. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <cinttypes> | ||
|
|
||
| /** | ||
| * Example layout: | ||
| * | ||
| * 0x0000: <CrashContextHeaderMagic> | ||
| * 0x0008: version | ||
| * 0x0010: rum_application_id | ||
| * 0x0020: rum_session_id | ||
| * 0x0030: rum_view_id | ||
| * 0x0040: rum_action_id | ||
| * 0x0050: <CrashContextFooterMagic> | ||
| */ | ||
|
|
||
| namespace datadog::platform { | ||
|
|
||
| static const uint64_t CrashContextHeaderMagic = 0xdc01; | ||
| static const uint64_t CrashContextFileVersion = 1; | ||
|
|
||
| static const uint64_t CrashContextFooterMagic = 0xdcff; | ||
|
|
||
| } // namespace datadog::platform |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| // Unless explicitly stated otherwise all files in this repository are licensed | ||
| // under the Apache License Version 2.0. | ||
| // | ||
| // This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| // Copyright 2025-Present Datadog, Inc. | ||
|
|
||
| #include "datadog/impl/platform/crash_context_write.hpp" | ||
|
|
||
| #ifdef _WIN32 | ||
| #define WIN32_LEAN_AND_MEAN | ||
| #define NOMINMAX | ||
| #include <windows.h> | ||
| #else | ||
| #include <fcntl.h> | ||
| #include <sys/stat.h> | ||
| #include <unistd.h> | ||
| #endif | ||
|
|
||
| #include <cstddef> | ||
| #include <cstdint> | ||
| #include <cstdio> | ||
|
|
||
| #include "datadog/impl/core/feature_types/rum.hpp" | ||
| #include "datadog/impl/platform/crash_context.hpp" | ||
|
|
||
| namespace datadog::platform { | ||
|
|
||
| // NOLINTBEGIN(cppcoreguidelines-pro-bounds-array-to-pointer-decay) | ||
| // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) | ||
|
|
||
| #ifdef _WIN32 | ||
| using FileHandle = HANDLE; | ||
| static const FileHandle kInvalidHandle = INVALID_HANDLE_VALUE; | ||
| #else | ||
| using FileHandle = int; | ||
| static const FileHandle kInvalidHandle = -1; | ||
| #endif | ||
|
|
||
| static void write_bytes(FileHandle fd, const void* data, size_t size) { | ||
| #ifdef _WIN32 | ||
| DWORD written = 0; | ||
| WriteFile(fd, data, static_cast<DWORD>(size), &written, nullptr); | ||
| (void)written; | ||
| #else | ||
| ssize_t result = write(fd, data, size); | ||
| (void)result; | ||
| #endif | ||
| } | ||
|
|
||
| static void write_uint64(FileHandle fd, uint64_t value) { | ||
| write_bytes(fd, &value, sizeof(value)); | ||
| } | ||
|
|
||
| void WriteCrashContext(const char* filename, const impl::RumFeatureContext& rum_ctx) { | ||
| // Write to a .tmp file first, then atomically rename to the final path. This | ||
| // ensures readers on the next launch never observe a partially-written file. | ||
| char tmp[512]; | ||
| #ifdef _WIN32 | ||
| _snprintf_s(tmp, sizeof(tmp), _TRUNCATE, "%s.tmp", filename); | ||
| #else | ||
| snprintf(tmp, sizeof(tmp), "%s.tmp", filename); | ||
| #endif | ||
|
|
||
| #ifdef _WIN32 | ||
| FileHandle fd = CreateFileA( | ||
| tmp, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr | ||
| ); | ||
| #else | ||
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) | ||
| FileHandle fd = open(tmp, O_CREAT | O_WRONLY | O_TRUNC, 0644); | ||
| #endif | ||
|
|
||
| if (fd == kInvalidHandle) { | ||
| return; | ||
| } | ||
|
|
||
| write_uint64(fd, CrashContextHeaderMagic); | ||
| write_uint64(fd, CrashContextFileVersion); | ||
| write_bytes(fd, rum_ctx.application_id.bytes.data(), 16); | ||
| write_bytes(fd, rum_ctx.session_id.bytes.data(), 16); | ||
| write_bytes(fd, rum_ctx.view_id.bytes.data(), 16); | ||
| write_bytes(fd, rum_ctx.action_id.bytes.data(), 16); | ||
| write_uint64(fd, CrashContextFooterMagic); | ||
|
|
||
| #ifdef _WIN32 | ||
| CloseHandle(fd); | ||
| // MoveFileExA with MOVEFILE_REPLACE_EXISTING is the closest Windows equivalent | ||
| // to an atomic rename; it replaces the destination in a single operation | ||
| MoveFileExA(tmp, filename, MOVEFILE_REPLACE_EXISTING); | ||
| #else | ||
| close(fd); | ||
| // rename() is atomic on POSIX when src and dst are on the same filesystem, | ||
| // which is guaranteed here since both paths share the same .crashes/ directory | ||
| rename(tmp, filename); | ||
| #endif | ||
| } | ||
|
|
||
| void DeleteCrashContext(const char* filename) { | ||
| char tmp[512]; | ||
| #ifdef _WIN32 | ||
| DeleteFileA(filename); | ||
| _snprintf_s(tmp, sizeof(tmp), _TRUNCATE, "%s.tmp", filename); | ||
| DeleteFileA(tmp); | ||
| #else | ||
| unlink(filename); | ||
| snprintf(tmp, sizeof(tmp), "%s.tmp", filename); | ||
| unlink(tmp); | ||
| #endif | ||
| } | ||
|
|
||
| // NOLINTEND(cppcoreguidelines-pro-type-vararg) | ||
| // NOLINTEND(cppcoreguidelines-pro-bounds-array-to-pointer-decay) | ||
|
|
||
| } // namespace datadog::platform |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Unless explicitly stated otherwise all files in this repository are licensed | ||
| // under the Apache License Version 2.0. | ||
| // | ||
| // This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| // Copyright 2025-Present Datadog, Inc. | ||
|
|
||
| #pragma once | ||
|
|
||
| namespace datadog::impl { | ||
| struct RumFeatureContext; | ||
| } | ||
|
|
||
| namespace datadog::platform { | ||
|
|
||
| /** | ||
| * Creates or overwrites a crash context file at `filename`, writing the current RUM | ||
| * session identifiers in binary format. | ||
| * | ||
| * The file persists across crashes so the next SDK launch can recover the session | ||
| * context and attach it to any crash report found on disk. Call `DeleteCrashContext` on | ||
| * clean shutdown to remove it. | ||
| */ | ||
| void WriteCrashContext(const char* filename, const impl::RumFeatureContext& rum_ctx); | ||
|
|
||
| /** | ||
| * Deletes the crash context file at `filename`, if any such file exists. | ||
| */ | ||
| void DeleteCrashContext(const char* filename); | ||
|
|
||
| } // namespace datadog::platform |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
| #include "client/crashpad_client.h" | ||
| #include "client/settings.h" | ||
|
|
||
| #include "datadog/impl/core/feature_types/rum.hpp" | ||
| #include "datadog/impl/diagnostics.hpp" | ||
| #include "datadog/impl/platform/crash_handler.hpp" | ||
|
|
||
|
|
@@ -31,8 +32,10 @@ | |
| // them safe to read during a crash. The Crashpad handler will automatically resolve | ||
| // these values and include them as annotations when the crash dump is uploaded. | ||
| // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) | ||
| static crashpad::StringAnnotation<64> s_test_annotation1("test_annotation1"); | ||
| static crashpad::StringAnnotation<64> s_test_annotation2("test_annotation2"); | ||
| static crashpad::StringAnnotation<37> s_rum_application_id("rum.application_id"); | ||
| static crashpad::StringAnnotation<37> s_rum_session_id("rum.session_id"); | ||
| static crashpad::StringAnnotation<37> s_rum_view_id("rum.view_id"); | ||
| static crashpad::StringAnnotation<37> s_rum_action_id("rum.action_id"); | ||
| // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) | ||
|
|
||
| /** | ||
|
|
@@ -159,9 +162,11 @@ class CrashpadCrashHandler final : public ICrashHandler { | |
| return false; | ||
| } | ||
|
|
||
| // Set initial annotation values as a test | ||
| s_test_annotation1.Set("foo"); | ||
| s_test_annotation2.Set("bar"); | ||
| // Clear annotation values, if any were set previously | ||
| s_rum_application_id.Set(""); | ||
| s_rum_session_id.Set(""); | ||
| s_rum_view_id.Set(""); | ||
| s_rum_action_id.Set(""); | ||
|
|
||
| // When the Crashpad client is first initialized, it populates the configured | ||
| // database directory with configuration metadata and other state. By default, a | ||
|
|
@@ -225,9 +230,61 @@ class CrashpadCrashHandler final : public ICrashHandler { | |
| // and continue running after SDK shutdown. We don't forcibly terminate it here. | ||
| } | ||
|
|
||
| void SetRumContext(const impl::RumFeatureContext& rum_ctx) override { | ||
| // NOLINTBEGIN(cppcoreguidelines-pro-bounds-array-to-pointer-decay) | ||
| char buf[37] = {0}; | ||
|
|
||
| if (rum_ctx.application_id != _rum_application_id) { | ||
| _rum_application_id = rum_ctx.application_id; | ||
| if (_rum_application_id == UUID::Zero) { | ||
| s_rum_application_id.Set(""); | ||
| } else { | ||
| _rum_application_id.ToBytes(buf, std::size(buf)); | ||
| s_rum_application_id.Set(buf); | ||
|
Comment on lines
+242
to
+243
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed with 73a2a76; buffer is now zero-filled to ensure null-termination. |
||
| } | ||
| } | ||
|
|
||
| if (rum_ctx.session_id != _rum_session_id) { | ||
| _rum_session_id = rum_ctx.session_id; | ||
| if (_rum_session_id == UUID::Zero) { | ||
| s_rum_session_id.Set(""); | ||
| } else { | ||
| _rum_session_id.ToBytes(buf, std::size(buf)); | ||
| s_rum_session_id.Set(buf); | ||
| } | ||
| } | ||
|
|
||
| if (rum_ctx.view_id != _rum_view_id) { | ||
| _rum_view_id = rum_ctx.view_id; | ||
| if (_rum_view_id == UUID::Zero) { | ||
| s_rum_view_id.Set(""); | ||
| } else { | ||
| _rum_view_id.ToBytes(buf, std::size(buf)); | ||
| s_rum_view_id.Set(buf); | ||
| } | ||
| } | ||
|
|
||
| if (rum_ctx.action_id != _rum_action_id) { | ||
| _rum_action_id = rum_ctx.action_id; | ||
| if (_rum_action_id == UUID::Zero) { | ||
| s_rum_action_id.Set(""); | ||
| } else { | ||
| _rum_action_id.ToBytes(buf, std::size(buf)); | ||
| s_rum_action_id.Set(buf); | ||
| } | ||
| } | ||
| // NOLINTEND(cppcoreguidelines-pro-bounds-array-to-pointer-decay) | ||
| }; | ||
|
|
||
| private: | ||
| impl::DiagnosticLogger _logger; | ||
| std::string _handler_exe_path; | ||
|
|
||
| // Values cached on last call to SetRumContext | ||
| UUID _rum_application_id; | ||
| UUID _rum_session_id; | ||
| UUID _rum_view_id; | ||
| UUID _rum_action_id; | ||
| }; | ||
|
|
||
| std::unique_ptr<ICrashHandler> CrashHandler::Init( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This handler only checks
_crash_handlerfor non-null, butStart()keeps_crash_handlereven whenInitialize()returns false. In that failure mode, context-change messages still invokeSetRumContext; for the in-process backend this attempts file writes despite failed setup (for example after.crashescreation/open failure), causing repeated failed I/O and possible stray temp-file behavior. Track initialization success (or clear_crash_handleron failure) before forwarding context updates.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with 9dd9597;
impl::CrashReportingnow discards and destroys its_crash_handlerreference ifICrashHandler::Initialize()fails. Once the feature is started, it either has a valid, initialized handler or it has no handler; there is no longer an inbetween state.