Skip to content

Commit e5f0e3c

Browse files
committed
feat: read and capture envelopes (#1320)
1 parent b49b057 commit e5f0e3c

File tree

6 files changed

+288
-2
lines changed

6 files changed

+288
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Add `Dynamic Sampling Context (DSC)` to events. ([#1254](https://github.com/getsentry/sentry-native/pull/1254))
1414
- Add `sentry_value_new_feedback` and `sentry_capture_feedback` to allow capturing [User Feedback](https://develop.sentry.dev/sdk/data-model/envelope-items/#user-feedback). ([#1304](https://github.com/getsentry/sentry-native/pull/1304))
1515
- Deprecate `sentry_value_new_user_feedback` and `sentry_capture_user_feedback` in favor of the new API.
16+
- Add `sentry_envelope_read_from_file`, `sentry_envelope_get_header`, and `sentry_capture_envelope`. ([#1320](https://github.com/getsentry/sentry-native/pull/1320))
1617

1718
**Fixes**:
1819

include/sentry.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,16 @@ typedef struct sentry_envelope_s sentry_envelope_t;
662662
*/
663663
SENTRY_API void sentry_envelope_free(sentry_envelope_t *envelope);
664664

665+
/**
666+
* Given an Envelope, returns the header if present.
667+
*
668+
* This returns a borrowed value to the headers in the Envelope.
669+
*/
670+
SENTRY_API sentry_value_t sentry_envelope_get_header(
671+
const sentry_envelope_t *envelope, const char *key);
672+
SENTRY_API sentry_value_t sentry_envelope_get_header_n(
673+
const sentry_envelope_t *envelope, const char *key, size_t key_len);
674+
665675
/**
666676
* Given an Envelope, returns the embedded Event if there is one.
667677
*
@@ -698,6 +708,34 @@ SENTRY_API int sentry_envelope_write_to_file(
698708
SENTRY_API int sentry_envelope_write_to_file_n(
699709
const sentry_envelope_t *envelope, const char *path, size_t path_len);
700710

711+
/**
712+
* Reads an envelope from a file.
713+
*
714+
* `path` is assumed to be in platform-specific filesystem path encoding.
715+
*
716+
* API Users on windows are encouraged to use `sentry_envelope_read_from_filew`
717+
* instead.
718+
*/
719+
SENTRY_API sentry_envelope_t *sentry_envelope_read_from_file(const char *path);
720+
SENTRY_API sentry_envelope_t *sentry_envelope_read_from_file_n(
721+
const char *path, size_t path_len);
722+
723+
#ifdef SENTRY_PLATFORM_WINDOWS
724+
/**
725+
* Wide char versions of `sentry_envelope_read_from_file` and
726+
* `sentry_envelope_read_from_file_n`.
727+
*/
728+
SENTRY_API sentry_envelope_t *sentry_envelope_read_from_filew(
729+
const wchar_t *path);
730+
SENTRY_API sentry_envelope_t *sentry_envelope_read_from_filew_n(
731+
const wchar_t *path, size_t path_len);
732+
#endif
733+
734+
/**
735+
* Submits an envelope, first checking for consent.
736+
*/
737+
SENTRY_API void sentry_capture_envelope(sentry_envelope_t *envelope);
738+
701739
/**
702740
* The Sentry Client Options.
703741
*

src/sentry_core.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,14 @@ sentry__capture_envelope(
447447
sentry__transport_send_envelope(transport, envelope);
448448
}
449449

450+
void
451+
sentry_capture_envelope(sentry_envelope_t *envelope)
452+
{
453+
SENTRY_WITH_OPTIONS (options) {
454+
sentry__capture_envelope(options->transport, envelope);
455+
}
456+
}
457+
450458
static bool
451459
event_is_considered_error(sentry_value_t event)
452460
{

src/sentry_envelope.c

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,24 @@ envelope_item_cleanup(sentry_envelope_item_t *item)
6565
sentry_free(item->payload);
6666
}
6767

68+
sentry_value_t
69+
sentry_envelope_get_header(const sentry_envelope_t *envelope, const char *key)
70+
{
71+
return sentry_envelope_get_header_n(
72+
envelope, key, sentry__guarded_strlen(key));
73+
}
74+
75+
sentry_value_t
76+
sentry_envelope_get_header_n(
77+
const sentry_envelope_t *envelope, const char *key, size_t key_len)
78+
{
79+
if (envelope->is_raw) {
80+
return sentry_value_new_null();
81+
}
82+
return sentry_value_get_by_key_n(
83+
envelope->contents.items.headers, key, key_len);
84+
}
85+
6886
void
6987
sentry__envelope_item_set_header(
7088
sentry_envelope_item_t *item, const char *key, sentry_value_t value)
@@ -726,6 +744,150 @@ sentry_envelope_write_to_file(
726744
return sentry_envelope_write_to_file_n(envelope, path, strlen(path));
727745
}
728746

747+
// https://develop.sentry.dev/sdk/data-model/envelopes/
748+
static sentry_envelope_t *
749+
parse_envelope_from_file(sentry_path_t *path)
750+
{
751+
if (!path) {
752+
return NULL;
753+
}
754+
755+
size_t buf_len = 0;
756+
char *buf = sentry__path_read_to_buffer(path, &buf_len);
757+
sentry__path_free(path);
758+
if (!buf) {
759+
return NULL;
760+
}
761+
762+
sentry_envelope_t *envelope = sentry__envelope_new();
763+
if (!envelope) {
764+
goto fail;
765+
}
766+
767+
const char *ptr = buf;
768+
const char *end = buf + buf_len;
769+
770+
// headers
771+
const char *headers_end = memchr(ptr, '\n', (size_t)(end - ptr));
772+
if (!headers_end) {
773+
headers_end = end;
774+
}
775+
size_t headers_len = (size_t)(headers_end - ptr);
776+
sentry_value_decref(envelope->contents.items.headers);
777+
envelope->contents.items.headers
778+
= sentry__value_from_json(ptr, headers_len);
779+
if (sentry_value_is_null(envelope->contents.items.headers)) {
780+
envelope->contents.items.headers = sentry_value_new_object();
781+
}
782+
783+
ptr = headers_end + 1; // skip newline
784+
785+
// items
786+
while (ptr < end) {
787+
sentry_envelope_item_t *item = envelope_add_item(envelope);
788+
if (!item) {
789+
goto fail;
790+
}
791+
792+
// item headers
793+
const char *item_headers_end = memchr(ptr, '\n', (size_t)(end - ptr));
794+
if (!item_headers_end) {
795+
item_headers_end = end;
796+
}
797+
size_t item_headers_len = (size_t)(item_headers_end - ptr);
798+
sentry_value_decref(item->headers);
799+
item->headers = sentry__value_from_json(ptr, item_headers_len);
800+
if (sentry_value_is_null(item->headers)) {
801+
goto fail;
802+
}
803+
ptr = item_headers_end + 1; // skip newline
804+
805+
// item payload
806+
sentry_value_t length
807+
= sentry_value_get_by_key(item->headers, "length");
808+
if (sentry_value_is_null(length)) {
809+
// find newline or end of buffer
810+
const char *payload_end = memchr(ptr, '\n', (size_t)(end - ptr));
811+
if (!payload_end) {
812+
payload_end = end;
813+
}
814+
item->payload_len = (size_t)(payload_end - ptr);
815+
} else {
816+
item->payload_len = (size_t)sentry_value_as_int32(length);
817+
}
818+
if (item->payload_len <= 0 || ptr + item->payload_len > end) {
819+
goto fail;
820+
}
821+
item->payload = sentry_malloc(item->payload_len + 1);
822+
if (!item->payload) {
823+
goto fail;
824+
}
825+
memcpy(item->payload, ptr, item->payload_len);
826+
item->payload[item->payload_len] = '\0';
827+
828+
// item event/transaction
829+
const char *type = sentry_value_as_string(
830+
sentry_value_get_by_key(item->headers, "type"));
831+
if (type
832+
&& (sentry__string_eq(type, "event")
833+
|| sentry__string_eq(type, "transaction"))) {
834+
item->event
835+
= sentry__value_from_json(item->payload, item->payload_len);
836+
}
837+
838+
ptr += item->payload_len;
839+
while (ptr < end && *ptr == '\n') {
840+
ptr++;
841+
}
842+
}
843+
844+
sentry_free(buf);
845+
return envelope;
846+
847+
fail:
848+
sentry_envelope_free(envelope);
849+
sentry_free(buf);
850+
return NULL;
851+
}
852+
853+
sentry_envelope_t *
854+
sentry_envelope_read_from_file(const char *path)
855+
{
856+
if (!path) {
857+
return NULL;
858+
}
859+
return parse_envelope_from_file(sentry__path_from_str(path));
860+
}
861+
862+
sentry_envelope_t *
863+
sentry_envelope_read_from_file_n(const char *path, size_t path_len)
864+
{
865+
if (!path || path_len == 0) {
866+
return NULL;
867+
}
868+
return parse_envelope_from_file(sentry__path_from_str_n(path, path_len));
869+
}
870+
871+
#ifdef SENTRY_PLATFORM_WINDOWS
872+
sentry_envelope_t *
873+
sentry_envelope_read_from_filew(const wchar_t *path)
874+
{
875+
if (!path) {
876+
return NULL;
877+
}
878+
return parse_envelope_from_file(sentry__path_from_wstr(path));
879+
}
880+
881+
sentry_envelope_t *
882+
sentry_envelope_read_from_filew_n(const wchar_t *path, size_t path_len)
883+
{
884+
if (!path || path_len == 0) {
885+
return NULL;
886+
}
887+
return parse_envelope_from_file(sentry__path_from_wstr_n(path, path_len));
888+
}
889+
#endif
890+
729891
#ifdef SENTRY_UNITTEST
730892
size_t
731893
sentry__envelope_get_item_count(const sentry_envelope_t *envelope)

tests/unit/test_envelopes.c

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ static char *const SERIALIZED_ENVELOPE_STR
1414
"\"production\",\"sampled\":\"false\"}}\n"
1515
"{\"type\":\"event\",\"length\":71}\n"
1616
"{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"some-"
17-
"context\":null}\n"
17+
"context\":1234}\n"
1818
"{\"type\":\"minidump\",\"length\":4}\n"
1919
"MDMP\n"
2020
"{\"type\":\"attachment\",\"length\":12}\n"
@@ -263,7 +263,8 @@ create_test_envelope()
263263
sentry_value_t event = sentry_value_new_object();
264264
sentry_value_set_by_key(
265265
event, "event_id", sentry__value_new_uuid(&event_id));
266-
sentry_value_set_by_key(event, "some-context", sentry_value_new_null());
266+
sentry_value_set_by_key(
267+
event, "some-context", sentry_value_new_int32(1234));
267268
sentry__envelope_add_event(envelope, event);
268269

269270
char dmp[] = "MDMP";
@@ -319,6 +320,7 @@ SENTRY_TEST(write_envelope_to_file_null)
319320
{
320321
sentry_envelope_t *empty_envelope = sentry__envelope_new();
321322

323+
TEST_CHECK(!sentry_envelope_read_from_file(NULL));
322324
TEST_CHECK_INT_EQUAL(
323325
sentry_envelope_write_to_file(NULL, "irrelevant/path"), 1);
324326
TEST_CHECK_INT_EQUAL(
@@ -336,6 +338,7 @@ SENTRY_TEST(write_envelope_to_invalid_path)
336338
sentry_envelope_t *envelope = create_test_envelope();
337339
const char *test_file_str = SENTRY_TEST_PATH_PREFIX
338340
"./directory_that_does_not_exist/sentry_test_envelope";
341+
TEST_CHECK(!sentry_envelope_read_from_file(test_file_str));
339342
sentry_path_t *test_file_path = sentry__path_from_str(test_file_str);
340343
int rv = sentry_envelope_write_to_file(envelope, test_file_str);
341344
TEST_CHECK_INT_EQUAL(rv, 1);
@@ -372,3 +375,76 @@ SENTRY_TEST(write_raw_envelope_to_file)
372375
sentry_envelope_free(raw_envelope);
373376
sentry_close();
374377
}
378+
379+
SENTRY_TEST(parse_envelope)
380+
{
381+
sentry_envelope_t *test_envelope = create_test_envelope();
382+
const char *test_file_str = SENTRY_TEST_PATH_PREFIX "sentry_test_envelope";
383+
sentry_path_t *test_file_path = sentry__path_from_str(test_file_str);
384+
TEST_CHECK_INT_EQUAL(
385+
sentry_envelope_write_to_file(test_envelope, test_file_str), 0);
386+
387+
sentry_envelope_t *file_envelope
388+
= sentry_envelope_read_from_file(test_file_str);
389+
390+
TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header(
391+
file_envelope, "dsn")),
392+
"https://[email protected]/42");
393+
TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header(
394+
file_envelope, "event_id")),
395+
"c993afb6-b4ac-48a6-b61b-2558e601d65d");
396+
397+
sentry_uuid_t event_id = sentry__envelope_get_event_id(file_envelope);
398+
char event_id_str[37];
399+
sentry_uuid_as_string(&event_id, event_id_str);
400+
TEST_CHECK_STRING_EQUAL(
401+
event_id_str, "c993afb6-b4ac-48a6-b61b-2558e601d65d");
402+
403+
TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(file_envelope), 3);
404+
405+
sentry_value_t event = sentry_envelope_get_event(file_envelope);
406+
TEST_CHECK(!sentry_value_is_null(event));
407+
TEST_CHECK_INT_EQUAL(
408+
sentry_value_as_int32(sentry_value_get_by_key(event, "some-context")),
409+
1234);
410+
411+
const sentry_envelope_item_t *ev
412+
= sentry__envelope_get_item(file_envelope, 0);
413+
TEST_CHECK(!!ev);
414+
TEST_CHECK_STRING_EQUAL(
415+
sentry_value_as_string(sentry__envelope_item_get_header(ev, "type")),
416+
"event");
417+
size_t ev_len = 0;
418+
TEST_CHECK_STRING_EQUAL(sentry__envelope_item_get_payload(ev, &ev_len),
419+
"{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\","
420+
"\"some-context\":1234}");
421+
422+
const sentry_envelope_item_t *dmp
423+
= sentry__envelope_get_item(file_envelope, 1);
424+
TEST_CHECK(!!dmp);
425+
TEST_CHECK_STRING_EQUAL(
426+
sentry_value_as_string(sentry__envelope_item_get_header(dmp, "type")),
427+
"minidump");
428+
size_t dmp_len = 0;
429+
TEST_CHECK_STRING_EQUAL(
430+
sentry__envelope_item_get_payload(dmp, &dmp_len), "MDMP");
431+
TEST_CHECK_INT_EQUAL(dmp_len, 4);
432+
433+
const sentry_envelope_item_t *attachment
434+
= sentry__envelope_get_item(file_envelope, 2);
435+
TEST_CHECK(!!attachment);
436+
TEST_CHECK_STRING_EQUAL(
437+
sentry_value_as_string(
438+
sentry__envelope_item_get_header(attachment, "type")),
439+
"attachment");
440+
size_t attachment_len = 0;
441+
TEST_CHECK_STRING_EQUAL(
442+
sentry__envelope_item_get_payload(attachment, &attachment_len),
443+
"Hello World!");
444+
TEST_CHECK_INT_EQUAL(attachment_len, 12);
445+
446+
sentry__path_free(test_file_path);
447+
sentry_envelope_free(test_envelope);
448+
sentry_envelope_free(file_envelope);
449+
sentry_close();
450+
}

tests/unit/tests.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ XX(os_release_non_existent_files)
8686
XX(os_releases_snapshot)
8787
XX(overflow_spans)
8888
XX(page_allocator)
89+
XX(parse_envelope)
8990
XX(path_basics)
9091
XX(path_current_exe)
9192
XX(path_directory)

0 commit comments

Comments
 (0)