Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Enable structured logs by default; logs are now opt-out via `sentry_options_set_enable_logs(options, false)`. ([#1673](https://github.com/getsentry/sentry-native/pull/1673))
- Crashpad: add macOS support for the `crashpad_wait_for_upload` flag. ([#1679](https://github.com/getsentry/sentry-native/pull/1679), [crashpad#152](https://github.com/getsentry/crashpad/pull/152))
- Crashpad: add experimental support for large attachment uploads, opt-in via `sentry_options_set_enable_large_attachments`. ([#1674](https://github.com/getsentry/sentry-native/pull/1674), [crashpad#151](https://github.com/getsentry/crashpad/pull/151))

**Fixes**:

Expand Down
19 changes: 19 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,25 @@ main(int argc, char **argv)
sentry_options_add_attachment(options, "./CMakeCache.txt");
}

if (has_arg(argc, argv, "large-attachment")) {
sentry_options_set_enable_large_attachments(options, 1);
const char *large_file = ".sentry-large-attachment";
FILE *f = fopen(large_file, "wb");
if (f) {
char zeros[4096];
memset(zeros, 0, sizeof(zeros));
size_t remaining = 100 * 1024 * 1024;
while (remaining > 0) {
size_t chunk
= remaining < sizeof(zeros) ? remaining : sizeof(zeros);
fwrite(zeros, 1, chunk, f);
remaining -= chunk;
}
fclose(f);
sentry_options_add_attachment(options, large_file);
}
}

if (has_arg(argc, argv, "stdout")) {
sentry_options_set_transport(
options, sentry_transport_new(print_envelope));
Expand Down
15 changes: 15 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -2351,6 +2351,21 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_http_retry(
SENTRY_EXPERIMENTAL_API int sentry_options_get_http_retry(
const sentry_options_t *opts);

/**
* Enables or disables out-of-band upload of large attachments.
*
* When enabled, attachments above an internal size threshold are uploaded
* via a separate request before the envelope is sent, and referenced from
* the envelope by location instead of being embedded inline. When disabled,
* all attachments are embedded in the envelope regardless of size.
*
* Disabled by default.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_enable_large_attachments(
sentry_options_t *opts, int enable_large_attachments);
SENTRY_EXPERIMENTAL_API int sentry_options_get_enable_large_attachments(
const sentry_options_t *opts);

/**
* Enables or disables custom attributes parsing for structured logging.
*
Expand Down
3 changes: 3 additions & 0 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,9 @@ crashpad_backend_startup(
}

std::vector<std::string> arguments { "--no-rate-limit" };
if (options->enable_large_attachments) {
arguments.push_back("--enable-large-attachments");
}

// Map sentry's log level to mini_chromium's LogSeverity. They diverge at
// FATAL (sentry=3, mini_chromium=4); otherwise 1:1.
Expand Down
14 changes: 14 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ sentry_options_new(void)
// both worlds
opts->http_retry = false;
opts->send_client_reports = true;
opts->enable_large_attachments = false;

return opts;
}
Expand Down Expand Up @@ -873,6 +874,19 @@ sentry_options_get_enable_metrics(const sentry_options_t *opts)
return opts->enable_metrics;
}

void
sentry_options_set_enable_large_attachments(
sentry_options_t *opts, int enable_large_attachments)
{
opts->enable_large_attachments = !!enable_large_attachments;
}

int
sentry_options_get_enable_large_attachments(const sentry_options_t *opts)
{
return opts->enable_large_attachments;
}

void
sentry_options_set_before_send_metric(sentry_options_t *opts,
sentry_before_send_metric_function_t func, void *user_data)
Expand Down
1 change: 1 addition & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ struct sentry_options_s {
void *before_send_metric_data;
bool http_retry;
bool send_client_reports;
bool enable_large_attachments;

/* everything from here on down are options which are stored here but
not exposed through the options API */
Expand Down
25 changes: 15 additions & 10 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,16 +350,21 @@ def deserialize_from(
headers = json.loads(line)
length = headers["length"]
payload = f.read(length)
if headers.get("type") in [
"event",
"feedback",
"session",
"transaction",
"user_report",
"log",
"trace_metric",
"client_report",
]:
if (
headers.get("type")
in [
"event",
"feedback",
"session",
"transaction",
"user_report",
"log",
"trace_metric",
"client_report",
]
or headers.get("content_type")
== "application/vnd.sentry.attachment-ref+json"
):
rv = cls(headers=headers, payload=PayloadRef(json=json.loads(payload)))
else:
rv = cls(headers=headers, payload=payload)
Expand Down
97 changes: 97 additions & 0 deletions tests/test_integration_tus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import os

import pytest

from . import (
make_dsn,
run,
Envelope,
SENTRY_VERSION,
)
from .conditions import has_crashpad, has_http, is_qemu

pytestmark = pytest.mark.skipif(not has_http, reason="tests need http")

# fmt: off
auth_header = (
f"Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/{SENTRY_VERSION}"
)
# fmt: on


@pytest.mark.skipif(
not has_crashpad or is_qemu, reason="crashpad backend not available"
)
def test_tus_crash_crashpad(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})

upload_uri = "/api/123456/upload/abc123def456789/"
upload_qs = "length=104857600&signature=xyz"
location = httpserver.url_for(upload_uri) + "?" + upload_qs

httpserver.expect_oneshot_request(
"/api/123456/upload/",
headers={"tus-resumable": "1.0.0"},
).respond_with_data("OK", status=201, headers={"Location": location})

httpserver.expect_oneshot_request(
upload_uri,
method="PATCH",
headers={"tus-resumable": "1.0.0"},
query_string=upload_qs,
).respond_with_data("", status=204)

httpserver.expect_oneshot_request(
"/api/123456/envelope/",
headers={"x-sentry-auth": auth_header},
).respond_with_data("OK")

env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

with httpserver.wait(timeout=15) as waiting:
run(
tmp_path,
"sentry_example",
["log", "large-attachment", "crashpad-wait-for-upload", "crash"],
expect_failure=True,
env=env,
)
assert waiting.result

create_req = None
upload_req = None
envelope_req = None
for entry in httpserver.log:
req = entry[0]
if req.path == "/api/123456/upload/" and req.method == "POST":
create_req = req
elif upload_uri in req.path and req.method == "PATCH":
upload_req = req
elif "/envelope/" in req.path:
envelope_req = req

assert create_req is not None
assert upload_req is not None
assert envelope_req is not None
assert int(create_req.headers.get("upload-length")) == 100 * 1024 * 1024
assert upload_req.headers.get("content-type") == "application/offset+octet-stream"
assert upload_req.headers.get("upload-offset") == "0"

envelope = Envelope.deserialize(envelope_req.get_data())
attachment_ref = None
minidump_item = None
for item in envelope:
if item.headers.get("attachment_type") == "event.minidump":
minidump_item = item
if (
item.headers.get("content_type")
== "application/vnd.sentry.attachment-ref+json"
and item.headers.get("filename") == ".sentry-large-attachment"
):
if hasattr(item.payload, "json") and "location" in item.payload.json:
attachment_ref = item

assert minidump_item is not None
assert attachment_ref is not None
assert attachment_ref.payload.json["location"] == location
assert attachment_ref.headers.get("attachment_length") == 100 * 1024 * 1024
Loading