Skip to content

Commit 9e8a954

Browse files
jpnurmiclaude
andauthored
feat(crashpad): offline caching (#1493)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7604519 commit 9e8a954

5 files changed

Lines changed: 356 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
**Features**:
66

7-
- Add new offline caching options to persist envelopes locally, currently supported with the `inproc` and `breakpad` backends: `sentry_options_set_cache_keep`, `sentry_options_set_cache_max_items`, `sentry_options_set_cache_max_size`, and `sentry_options_set_cache_max_age`. ([#1490](https://github.com/getsentry/sentry-native/pull/1490))
7+
- Add new offline caching options to persist envelopes locally: `sentry_options_set_cache_keep`, `sentry_options_set_cache_max_items`, `sentry_options_set_cache_max_size`, and `sentry_options_set_cache_max_age`. ([#1490](https://github.com/getsentry/sentry-native/pull/1490), [#1493](https://github.com/getsentry/sentry-native/pull/1493))
88

99
**Fixes**:
1010

src/backends/sentry_backend_crashpad.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extern "C" {
1919
#include "sentry_screenshot.h"
2020
#include "sentry_sync.h"
2121
#include "sentry_transport.h"
22+
#include "sentry_value.h"
2223
#ifdef SENTRY_PLATFORM_LINUX
2324
# include "sentry_unix_pageallocator.h"
2425
#endif
@@ -436,6 +437,167 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context)
436437
}
437438
#endif
438439

440+
static sentry_value_t
441+
read_msgpack_file(const sentry_path_t *path)
442+
{
443+
size_t size;
444+
char *data = sentry__path_read_to_buffer(path, &size);
445+
if (!data) {
446+
return sentry_value_new_null();
447+
}
448+
sentry_value_t value = sentry__value_from_msgpack(data, size);
449+
sentry_free(data);
450+
return value;
451+
}
452+
453+
static sentry_path_t *
454+
report_attachments_dir(const crashpad::CrashReportDatabase::Report &report,
455+
const sentry_options_t *options)
456+
{
457+
sentry_path_t *attachments_root
458+
= sentry__path_join_str(options->database_path, "attachments");
459+
if (!attachments_root) {
460+
return nullptr;
461+
}
462+
463+
sentry_path_t *attachments_dir = sentry__path_join_str(
464+
attachments_root, report.uuid.ToString().c_str());
465+
466+
sentry__path_free(attachments_root);
467+
return attachments_dir;
468+
}
469+
470+
// Converts a completed crashpad report into a sentry envelope by reading the
471+
// event, breadcrumbs, and attachments from the report's attachments directory.
472+
static sentry_envelope_t *
473+
report_to_envelope(const crashpad::CrashReportDatabase::Report &report,
474+
const sentry_options_t *options)
475+
{
476+
#ifdef SENTRY_PLATFORM_WINDOWS
477+
sentry_path_t *minidump_path
478+
= sentry__path_from_wstr(report.file_path.value().c_str());
479+
#else
480+
sentry_path_t *minidump_path
481+
= sentry__path_from_str(report.file_path.value().c_str());
482+
#endif
483+
sentry_path_t *attachments_dir = report_attachments_dir(report, options);
484+
485+
if (!minidump_path || !attachments_dir) {
486+
sentry__path_free(minidump_path);
487+
sentry__path_free(attachments_dir);
488+
return nullptr;
489+
}
490+
491+
sentry_value_t event = sentry_value_new_null();
492+
sentry_value_t breadcrumbs1 = sentry_value_new_null();
493+
sentry_value_t breadcrumbs2 = sentry_value_new_null();
494+
sentry_attachment_t *attachments = nullptr;
495+
496+
sentry_pathiter_t *iter = sentry__path_iter_directory(attachments_dir);
497+
if (iter) {
498+
const sentry_path_t *path;
499+
while ((path = sentry__pathiter_next(iter)) != nullptr) {
500+
const char *filename = sentry__path_filename(path);
501+
if (strcmp(filename, "__sentry-event") == 0) {
502+
event = read_msgpack_file(path);
503+
} else if (strcmp(filename, "__sentry-breadcrumb1") == 0) {
504+
breadcrumbs1 = read_msgpack_file(path);
505+
} else if (strcmp(filename, "__sentry-breadcrumb2") == 0) {
506+
breadcrumbs2 = read_msgpack_file(path);
507+
} else {
508+
sentry__attachments_add_path(&attachments,
509+
sentry__path_clone(path), ATTACHMENT, nullptr);
510+
}
511+
}
512+
sentry__pathiter_free(iter);
513+
}
514+
sentry__path_free(attachments_dir);
515+
516+
sentry_envelope_t *envelope = nullptr;
517+
if (!sentry_value_is_null(event)) {
518+
envelope = sentry__envelope_new();
519+
if (envelope && options->dsn && options->dsn->is_valid) {
520+
sentry__envelope_set_header(envelope, "dsn",
521+
sentry_value_new_string(sentry_options_get_dsn(options)));
522+
}
523+
}
524+
if (envelope) {
525+
sentry_value_set_by_key(event, "breadcrumbs",
526+
sentry__value_merge_breadcrumbs(
527+
breadcrumbs1, breadcrumbs2, options->max_breadcrumbs));
528+
sentry__attachments_add_path(
529+
&attachments, minidump_path, MINIDUMP, nullptr);
530+
531+
if (sentry__envelope_add_event(envelope, event)) {
532+
sentry__envelope_add_attachments(envelope, attachments);
533+
} else {
534+
sentry_value_decref(event);
535+
sentry_envelope_free(envelope);
536+
envelope = nullptr;
537+
}
538+
} else {
539+
sentry__path_free(minidump_path);
540+
sentry_value_decref(event);
541+
}
542+
543+
sentry_value_decref(breadcrumbs1);
544+
sentry_value_decref(breadcrumbs2);
545+
sentry__attachments_free(attachments);
546+
547+
return envelope;
548+
}
549+
550+
// Caches completed crashpad reports as sentry envelopes and removes them from
551+
// the crashpad database. Called during startup before the handler is started.
552+
static void
553+
process_completed_reports(
554+
crashpad_state_t *state, const sentry_options_t *options)
555+
{
556+
if (!state || !state->db || !options || !options->cache_keep) {
557+
return;
558+
}
559+
560+
std::vector<crashpad::CrashReportDatabase::Report> reports;
561+
if (state->db->GetCompletedReports(&reports)
562+
!= crashpad::CrashReportDatabase::kNoError
563+
|| reports.empty()) {
564+
return;
565+
}
566+
567+
SENTRY_DEBUGF("caching %zu completed reports", reports.size());
568+
569+
sentry_path_t *cache_dir
570+
= sentry__path_join_str(options->database_path, "cache");
571+
if (!cache_dir || sentry__path_create_dir_all(cache_dir) != 0) {
572+
SENTRY_WARN("failed to create cache dir");
573+
sentry__path_free(cache_dir);
574+
return;
575+
}
576+
577+
for (const auto &report : reports) {
578+
std::string filename = report.uuid.ToString() + ".envelope";
579+
sentry_envelope_t *envelope = report_to_envelope(report, options);
580+
if (!envelope) {
581+
SENTRY_WARNF("failed to convert \"%s\"", filename.c_str());
582+
continue;
583+
}
584+
sentry_path_t *out_path
585+
= sentry__path_join_str(cache_dir, filename.c_str());
586+
if (!out_path
587+
|| (!sentry__path_is_file(out_path)
588+
&& sentry_envelope_write_to_path(envelope, out_path) != 0)) {
589+
SENTRY_WARNF("failed to cache \"%s\"", filename.c_str());
590+
} else if (state->db->DeleteReport(report.uuid)
591+
!= crashpad::CrashReportDatabase::kNoError) {
592+
SENTRY_WARNF("failed to delete \"%s\"", filename.c_str());
593+
}
594+
sentry__path_free(out_path);
595+
sentry_envelope_free(envelope);
596+
}
597+
598+
sentry__path_free(cache_dir);
599+
}
600+
439601
static int
440602
crashpad_backend_startup(
441603
sentry_backend_t *backend, const sentry_options_t *options)
@@ -549,6 +711,7 @@ crashpad_backend_startup(
549711
// Initialize database first, flushing the consent later on as part of
550712
// `sentry_init` will persist the upload flag.
551713
data->db = crashpad::CrashReportDatabase::Initialize(database).release();
714+
process_completed_reports(data, options);
552715
data->client = new crashpad::CrashpadClient;
553716
char *minidump_url
554717
= sentry__dsn_get_minidump_url(options->dsn, options->user_agent);

src/sentry_envelope.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ sentry_envelope_free(sentry_envelope_t *envelope)
169169
sentry_free(envelope);
170170
}
171171

172-
static void
172+
void
173173
sentry__envelope_set_header(
174174
sentry_envelope_t *envelope, const char *key, sentry_value_t value)
175175
{

src/sentry_envelope.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ sentry_envelope_item_t *sentry__envelope_add_from_buffer(
106106
sentry_envelope_t *envelope, const char *buf, size_t buf_len,
107107
const char *type);
108108

109+
/**
110+
* This sets an explicit header for the given envelope.
111+
*/
112+
void sentry__envelope_set_header(
113+
sentry_envelope_t *envelope, const char *key, sentry_value_t value);
114+
109115
/**
110116
* This sets an explicit header for the given envelope item.
111117
*/

0 commit comments

Comments
 (0)