Skip to content
Draft
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
# Changelog

## Unreleased

**Features**:

- Add new offline caching options to persist envelopes locally: `sentry_options_set_cache_keep`, `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))

## 0.12.4

**Features**:

- 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_size`, and `sentry_options_set_cache_max_age`. ([#1490](https://github.com/getsentry/sentry-native/pull/1490))

**Fixes**:

- Crashpad: namespace mpack to avoid ODR violation. ([#1476](https://github.com/getsentry/sentry-native/pull/1476), [crashpad#143](https://github.com/getsentry/crashpad/pull/143))
Expand Down
5 changes: 5 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,11 @@ main(int argc, char **argv)
if (has_arg(argc, argv, "log-attributes")) {
sentry_options_set_logs_with_attributes(options, true);
}
if (has_arg(argc, argv, "cache-keep")) {
sentry_options_set_cache_keep(options, true);
sentry_options_set_cache_max_size(options, 4 * 1024 * 1024);
sentry_options_set_cache_max_age(options, 5 * 24 * 60 * 60);
}

if (0 != sentry_init(options)) {
return EXIT_FAILURE;
Expand Down
31 changes: 31 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,37 @@ SENTRY_API void sentry_options_set_symbolize_stacktraces(
SENTRY_API int sentry_options_get_symbolize_stacktraces(
const sentry_options_t *opts);

/**
* Enables or disables storing envelopes in a persistent cache.
*
* When enabled, envelopes are written to a `cache/` subdirectory within the
* database directory and retained regardless of send success or failure.
* The cache is cleared on startup based on the cache_max_size and cache_max_age
* options.
*/
SENTRY_API void sentry_options_set_cache_keep(
sentry_options_t *opts, int enabled);

/**
* Sets the maximum size (in bytes) for the cache directory.
* On startup, cached entries are removed from oldest to newest until the
* directory size is within the max size limit.
*/
SENTRY_API void sentry_options_set_cache_max_size(
sentry_options_t *opts, size_t bytes);

/**
* Sets the maximum age (in seconds) for cache entries in the cache directory.
* On startup, cached entries exceeding the max age limit are removed.
*/
SENTRY_API void sentry_options_set_cache_max_age(
sentry_options_t *opts, uint64_t seconds);

/**
* Gets the caching mode for crash reports.
*/
SENTRY_API int sentry_options_get_cache_keep(const sentry_options_t *opts);

/**
* Adds a new attachment to be sent along.
*
Expand Down
168 changes: 162 additions & 6 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extern "C" {
#include "sentry_screenshot.h"
#include "sentry_sync.h"
#include "sentry_transport.h"
#include "sentry_value.h"
#ifdef SENTRY_PLATFORM_LINUX
# include "sentry_unix_pageallocator.h"
#endif
Expand Down Expand Up @@ -432,6 +433,151 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context)
}
#endif

static sentry_value_t
read_msgpack_file(const sentry_path_t *path)
{
size_t size;
char *data = sentry__path_read_to_buffer(path, &size);
if (!data) {
return sentry_value_new_null();
}
sentry_value_t value = sentry__value_from_msgpack(data, size);
sentry_free(data);
return value;
}

static sentry_path_t *
report_minidump_path(const crashpad::CrashReportDatabase::Report &report)
{
return
#ifdef SENTRY_PLATFORM_WINDOWS
sentry__path_from_wstr(report.file_path.value().c_str());
#else
sentry__path_from_str(report.file_path.value().c_str());
#endif
}

static sentry_path_t *
report_attachments_dir(const crashpad::CrashReportDatabase::Report &report,
const sentry_options_t *options)
{
sentry_path_t *attachments_root
= sentry__path_join_str(options->database_path, "attachments");
if (!attachments_root) {
return nullptr;
}

sentry_path_t *attachments_dir = sentry__path_join_str(
attachments_root, report.uuid.ToString().c_str());

sentry__path_free(attachments_root);
return attachments_dir;
}

static sentry_envelope_t *
report_to_envelope(const crashpad::CrashReportDatabase::Report &report,
const sentry_options_t *options)
{
sentry_envelope_t *envelope = sentry__envelope_new();
sentry_path_t *minidump_path = report_minidump_path(report);
sentry_path_t *attachments_dir = report_attachments_dir(report, options);

if (!envelope || !minidump_path || !attachments_dir) {
sentry_envelope_free(envelope);
sentry__path_free(minidump_path);
sentry__path_free(attachments_dir);
return nullptr;
}

sentry_value_t event = sentry_value_new_null();
sentry_value_t breadcrumbs1 = sentry_value_new_null();
sentry_value_t breadcrumbs2 = sentry_value_new_null();
sentry_attachment_t *attachments = nullptr;

sentry_pathiter_t *iter = sentry__path_iter_directory(attachments_dir);
if (iter) {
const sentry_path_t *path;
while ((path = sentry__pathiter_next(iter)) != nullptr) {
const char *filename = sentry__path_filename(path);
if (strcmp(filename, "__sentry-event") == 0) {
event = read_msgpack_file(path);
} else if (strcmp(filename, "__sentry-breadcrumb1") == 0) {
breadcrumbs1 = read_msgpack_file(path);
} else if (strcmp(filename, "__sentry-breadcrumb2") == 0) {
breadcrumbs2 = read_msgpack_file(path);
} else {
sentry__attachments_add_path(&attachments,
sentry__path_clone(path), ATTACHMENT, nullptr);
}
}
sentry__pathiter_free(iter);
}
sentry__path_free(attachments_dir);

sentry_value_set_by_key(event, "breadcrumbs",
sentry__value_merge_breadcrumbs(
breadcrumbs1, breadcrumbs2, options->max_breadcrumbs));
sentry__attachments_add_path(
&attachments, minidump_path, MINIDUMP, nullptr);

sentry__envelope_add_event(envelope, event);
sentry__envelope_add_attachments(envelope, attachments);

sentry_value_decref(breadcrumbs1);
sentry_value_decref(breadcrumbs2);
sentry__attachments_free(attachments);

return envelope;
}

static void
process_completed_reports(
crashpad_state_t *state, const sentry_options_t *options)
{
if (!state || !state->db || !options || !options->cache_keep) {
return;
}

std::vector<crashpad::CrashReportDatabase::Report> reports;
if (state->db->GetCompletedReports(&reports)
!= crashpad::CrashReportDatabase::kNoError
|| reports.empty()) {
return;
}

SENTRY_DEBUGF("caching %zu completed reports", reports.size());

sentry_path_t *cache_dir
= sentry__path_join_str(options->database_path, "cache");
if (!cache_dir || sentry__path_create_dir_all(cache_dir) != 0) {
SENTRY_WARN("failed to create cache dir");
sentry__path_free(cache_dir);
return;
}

for (const auto &report : reports) {
sentry_envelope_t *envelope = report_to_envelope(report, options);
if (!envelope) {
continue;
}

std::string filename = report.uuid.ToString() + ".envelope";
sentry_path_t *out_path
= sentry__path_join_str(cache_dir, filename.c_str());
if (!out_path
|| sentry_envelope_write_to_path(envelope, out_path) != 0) {
SENTRY_WARNF("failed to cache \"%s\"", filename.c_str());
} else if (state->db->DeleteReport(report.uuid)
!= crashpad::CrashReportDatabase::kNoError) {
SENTRY_WARNF("failed to delete \"%s\"", filename.c_str());
}
sentry__path_free(out_path);
sentry_envelope_free(envelope);
}

sentry__path_free(cache_dir);
}

static int
crashpad_backend_startup(
sentry_backend_t *backend, const sentry_options_t *options)
Expand Down Expand Up @@ -539,9 +685,13 @@ crashpad_backend_startup(

std::vector<std::string> arguments { "--no-rate-limit" };

char report_id[37];
sentry_uuid_as_string(&data->crash_event_id, report_id);

// Initialize database first, flushing the consent later on as part of
// `sentry_init` will persist the upload flag.
data->db = crashpad::CrashReportDatabase::Initialize(database).release();
process_completed_reports(data, options);
data->client = new crashpad::CrashpadClient;
char *minidump_url
= sentry__dsn_get_minidump_url(options->dsn, options->user_agent);
Expand All @@ -564,7 +714,8 @@ crashpad_backend_startup(
minidump_url ? minidump_url : "", proxy_url, annotations, arguments,
/* restartable */ true,
/* asynchronous_start */ false, attachments, screenshot,
options->crashpad_wait_for_upload, crash_reporter, crash_envelope);
options->crashpad_wait_for_upload, crash_reporter, crash_envelope,
report_id);
sentry_free(minidump_url);

#ifdef SENTRY_PLATFORM_WINDOWS
Expand Down Expand Up @@ -745,11 +896,16 @@ crashpad_backend_prune_database(sentry_backend_t *backend)
// complete database to a maximum of 8M. That might still be a lot for
// an embedded use-case, but minidumps on desktop can sometimes be quite
// large.
data->db->CleanDatabase(60 * 60 * 24 * 2);
crashpad::BinaryPruneCondition condition(crashpad::BinaryPruneCondition::OR,
new crashpad::DatabaseSizePruneCondition(1024 * 8),
new crashpad::AgePruneCondition(2));
crashpad::PruneCrashReportDatabase(data->db, &condition);
SENTRY_WITH_OPTIONS (options) {
data->db->CleanDatabase(static_cast<time_t>(options->cache_max_age));
crashpad::BinaryPruneCondition condition(
crashpad::BinaryPruneCondition::OR,
new crashpad::DatabaseSizePruneCondition(
options->cache_max_size / 1024),
new crashpad::AgePruneCondition(
static_cast<int>(options->cache_max_age / (24 * 60 * 60))));
crashpad::PruneCrashReportDatabase(data->db, &condition);
}
}

#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX)
Expand Down
8 changes: 8 additions & 0 deletions src/path/sentry_path_unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ sentry__path_remove(const sentry_path_t *path)
return 1;
}

int
sentry__path_rename(const sentry_path_t *src, const sentry_path_t *dst)
{
int status;
EINTR_RETRY(rename(src->path, dst->path), &status);
return status == 0 ? 0 : 1;
}

int
sentry__path_create_dir_all(const sentry_path_t *path)
{
Expand Down
12 changes: 12 additions & 0 deletions src/path/sentry_path_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,18 @@ sentry__path_remove(const sentry_path_t *path)
return removal_success ? 0 : !is_last_error_path_not_found();
}

int
sentry__path_rename(const sentry_path_t *src, const sentry_path_t *dst)
{
wchar_t *src_w = src->path_w;
wchar_t *dst_w = dst->path_w;
if (!src_w || !dst_w) {
return 1;
}
// MOVEFILE_REPLACE_EXISTING allows overwriting the destination if it exists
return MoveFileExW(src_w, dst_w, MOVEFILE_REPLACE_EXISTING) ? 0 : 1;
}

int
sentry__path_create_dir_all(const sentry_path_t *path)
{
Expand Down
4 changes: 4 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ sentry_init(sentry_options_t *options)
backend->prune_database_func(backend);
}

if (options->cache_keep) {
sentry__cleanup_cache(options);
}

if (options->auto_session_tracking) {
sentry_start_session();
}
Expand Down
Loading
Loading