diff --git a/examples/example.c b/examples/example.c index 392675a58..7a2742091 100644 --- a/examples/example.c +++ b/examples/example.c @@ -166,21 +166,14 @@ main(int argc, char **argv) sentry_capture_event(event); } if (has_arg(argc, argv, "capture-exception")) { - // TODO: Create a convenience API to create a new exception object, - // and to attach a stacktrace to the exception. - // See also https://github.com/getsentry/sentry-native/issues/235 + sentry_value_t exc = sentry_value_new_exception( + "ParseIntError", "invalid digit found in string"); + if (has_arg(argc, argv, "add-stacktrace")) { + sentry_value_t stacktrace = sentry_value_new_stacktrace(NULL, 0); + sentry_value_set_by_key(exc, "stacktrace", stacktrace); + } sentry_value_t event = sentry_value_new_event(); - sentry_value_t exception = sentry_value_new_object(); - // for example: - sentry_value_set_by_key( - exception, "type", sentry_value_new_string("ParseIntError")); - sentry_value_set_by_key(exception, "value", - sentry_value_new_string("invalid digit found in string")); - sentry_value_t exceptions = sentry_value_new_list(); - sentry_value_append(exceptions, exception); - sentry_value_t values = sentry_value_new_object(); - sentry_value_set_by_key(values, "values", exceptions); - sentry_value_set_by_key(event, "exception", values); + sentry_event_add_exception(event, exc); sentry_capture_event(event); } diff --git a/include/sentry.h b/include/sentry.h index 215963354..2abf08162 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -338,12 +338,18 @@ typedef enum sentry_level_e { } sentry_level_t; /** - * Creates a new empty event value. + * Creates a new empty Event value. + * + * See https://docs.sentry.io/platforms/native/enriching-events/ for how to + * further work with events, and https://develop.sentry.dev/sdk/event-payloads/ + * for a detailed overview of the possible properties of an Event. */ SENTRY_API sentry_value_t sentry_value_new_event(void); /** - * Creates a new message event value. + * Creates a new Message Event value. + * + * See https://develop.sentry.dev/sdk/event-payloads/message/ * * `logger` can be NULL to omit the logger value. */ @@ -351,13 +357,73 @@ SENTRY_API sentry_value_t sentry_value_new_message_event( sentry_level_t level, const char *logger, const char *text); /** - * Creates a new breadcrumb with a specific type and message. + * Creates a new Breadcrumb with a specific type and message. + * + * See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ * * Either parameter can be NULL in which case no such attributes is created. */ SENTRY_API sentry_value_t sentry_value_new_breadcrumb( const char *type, const char *message); +/** + * Creates a new Exception value. + * + * This is intended for capturing language-level exception, such as from a + * try-catch block. `type` and `value` here refer to the exception class and + * a possible description. + * + * See https://develop.sentry.dev/sdk/event-payloads/exception/ + * + * The returned value needs to be attached to an event via + * `sentry_event_add_exception`. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception( + const char *type, const char *value); + +/** + * Creates a new Thread value. + * + * See https://develop.sentry.dev/sdk/event-payloads/threads/ + * + * The returned value needs to be attached to an event via + * `sentry_event_add_thread`. + * + * `name` can be NULL. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( + uint64_t id, const char *name); + +/** + * Creates a new Stack Trace conforming to the Stack Trace Interface. + * + * See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ + * + * The returned object needs to be attached to either an exception + * event, or a thread object. + * + * If `ips` is NULL the current stack trace is captured, otherwise `len` + * stack trace instruction pointers are attached to the event. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_stacktrace( + void **ips, size_t len); + +/** + * Adds an Exception to an Event value. + * + * This takes ownership of the `exception`. + */ +SENTRY_EXPERIMENTAL_API void sentry_event_add_exception( + sentry_value_t event, sentry_value_t exception); + +/** + * Adds a Thread to an Event value. + * + * This takes ownership of the `thread`. + */ +SENTRY_EXPERIMENTAL_API void sentry_event_add_thread( + sentry_value_t event, sentry_value_t thread); + /* -- Experimental APIs -- */ /** @@ -371,10 +437,15 @@ SENTRY_EXPERIMENTAL_API char *sentry_value_to_msgpack( sentry_value_t value, size_t *size_out); /** - * Adds a stacktrace to an event. + * Adds a stack trace to an event. + * + * The stack trace is added as part of a new thread object. + * This function is **deprecated** in favor of using + * `sentry_value_new_stacktrace` in combination with `sentry_value_new_thread` + * and `sentry_event_add_thread`. * - * If `ips` is NULL the current stacktrace is captured, otherwise `len` - * stacktrace instruction pointers are attached to the event. + * If `ips` is NULL the current stack trace is captured, otherwise `len` + * stack trace instruction pointers are attached to the event. */ SENTRY_EXPERIMENTAL_API void sentry_event_value_add_stacktrace( sentry_value_t event, void **ips, size_t len); @@ -398,7 +469,7 @@ typedef struct sentry_ucontext_s { * * If the address is given in `addr` the stack is unwound form there. * Otherwise (NULL is passed) the current instruction pointer is used as - * start address. The stacktrace is written to `stacktrace_out` with upt o + * start address. The stack trace is written to `stacktrace_out` with up to * `max_len` frames being written. The actual number of unwound stackframes * is returned. */ @@ -408,7 +479,7 @@ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack( /** * Unwinds the stack from the given context. * - * The stacktrace is written to `stacktrace_out` with upt o `max_len` frames + * The stack trace is written to `stacktrace_out` with up to `max_len` frames * being written. The actual number of unwound stackframes is returned. */ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack_from_ucontext( diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index a0b84fdd9..d3b77fd40 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -177,13 +177,9 @@ make_signal_event( sentry_value_set_by_key( event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL)); - sentry_value_t exc = sentry_value_new_object(); - sentry_value_set_by_key(exc, "type", - sentry_value_new_string( - sig_slot ? sig_slot->signame : "UNKNOWN_SIGNAL")); - sentry_value_set_by_key(exc, "value", - sentry_value_new_string( - sig_slot ? sig_slot->sigdesc : "UnknownSignal")); + sentry_value_t exc = sentry_value_new_exception( + sig_slot ? sig_slot->signame : "UNKNOWN_SIGNAL", + sig_slot ? sig_slot->sigdesc : "UnknownSignal"); sentry_value_t mechanism = sentry_value_new_object(); sentry_value_set_by_key(exc, "mechanism", mechanism); @@ -217,25 +213,12 @@ make_signal_event( } SENTRY_TRACEF("captured backtrace with %lu frames", frame_count); - sentry_value_t frames = sentry__value_new_list_with_size(frame_count); - for (size_t i = 0; i < frame_count; i++) { - sentry_value_t frame = sentry_value_new_object(); - sentry_value_set_by_key(frame, "instruction_addr", - sentry__value_new_addr( - (uint64_t)(size_t)backtrace[frame_count - i - 1])); - sentry_value_append(frames, frame); - } - - sentry_value_t stacktrace = sentry_value_new_object(); - sentry_value_set_by_key(stacktrace, "frames", frames); + sentry_value_t stacktrace + = sentry_value_new_stacktrace(&backtrace[0], frame_count); sentry_value_set_by_key(exc, "stacktrace", stacktrace); - sentry_value_t exceptions = sentry_value_new_object(); - sentry_value_t values = sentry_value_new_list(); - sentry_value_set_by_key(exceptions, "values", values); - sentry_value_append(values, exc); - sentry_value_set_by_key(event, "exception", exceptions); + sentry_event_add_exception(event, exc); return event; } diff --git a/src/sentry_value.c b/src/sentry_value.c index 1e4b72525..7a14a980f 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -997,6 +997,8 @@ sentry_value_new_event(void) sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); + sentry_value_set_by_key(rv, "platform", sentry_value_new_string("native")); + return rv; } @@ -1036,8 +1038,38 @@ sentry_value_new_breadcrumb(const char *type, const char *message) return rv; } -void -sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) +sentry_value_t +sentry_value_new_exception(const char *type, const char *value) +{ + sentry_value_t exc = sentry_value_new_object(); + sentry_value_set_by_key(exc, "type", sentry_value_new_string(type)); + sentry_value_set_by_key(exc, "value", sentry_value_new_string(value)); + return exc; +} + +sentry_value_t +sentry_value_new_thread(uint64_t id, const char *name) +{ + sentry_value_t thread = sentry_value_new_object(); + + // NOTE: values end up as JSON, which has no support for `u64`. + char buf[20 + 1]; + size_t written + = (size_t)snprintf(buf, sizeof(buf), "%llu", (unsigned long long)id); + if (written < sizeof(buf)) { + buf[written] = '\0'; + sentry_value_set_by_key(thread, "id", sentry_value_new_string(buf)); + } + + if (name) { + sentry_value_set_by_key(thread, "name", sentry_value_new_string(name)); + } + + return thread; +} + +sentry_value_t +sentry_value_new_stacktrace(void **ips, size_t len) { void *walked_backtrace[256]; @@ -1058,14 +1090,56 @@ sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) sentry_value_t stacktrace = sentry_value_new_object(); sentry_value_set_by_key(stacktrace, "frames", frames); - sentry_value_t thread = sentry_value_new_object(); - sentry_value_set_by_key(thread, "stacktrace", stacktrace); + return stacktrace; +} + +static sentry_value_t +sentry__get_or_insert_values_list(sentry_value_t parent, const char *key) +{ + sentry_value_t obj = sentry_value_get_by_key(parent, key); + if (sentry_value_is_null(obj)) { + obj = sentry_value_new_object(); + sentry_value_set_by_key(parent, key, obj); + } + + sentry_value_type_t type = sentry_value_get_type(obj); + sentry_value_t values = sentry_value_new_null(); + if (type == SENTRY_VALUE_TYPE_OBJECT) { + values = sentry_value_get_by_key(obj, "values"); + if (sentry_value_is_null(values)) { + values = sentry_value_new_list(); + sentry_value_set_by_key(obj, "values", values); + } + } else if (type == SENTRY_VALUE_TYPE_LIST) { + values = obj; + } - sentry_value_t values = sentry_value_new_list(); - sentry_value_append(values, thread); + return values; +} + +void +sentry_event_add_exception(sentry_value_t event, sentry_value_t exception) +{ + sentry_value_t exceptions + = sentry__get_or_insert_values_list(event, "exception"); + sentry_value_append(exceptions, exception); +} - sentry_value_t threads = sentry_value_new_object(); - sentry_value_set_by_key(threads, "values", values); +void +sentry_event_add_thread(sentry_value_t event, sentry_value_t thread) +{ + sentry_value_t threads + = sentry__get_or_insert_values_list(event, "threads"); + sentry_value_append(threads, thread); +} + +void +sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) +{ + sentry_value_t stacktrace = sentry_value_new_stacktrace(ips, len); + + sentry_value_t thread = sentry_value_new_object(); + sentry_value_set_by_key(thread, "stacktrace", stacktrace); - sentry_value_set_by_key(event, "threads", threads); + sentry_event_add_thread(event, thread); } diff --git a/tests/assertions.py b/tests/assertions.py index c5b4563cc..6995aff54 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -163,8 +163,7 @@ def assert_exception(envelope): "type": "ParseIntError", "value": "invalid digit found in string", } - expected = {"exception": {"values": [exception]}} - assert matches(event, expected) + assert matches(event["exception"]["values"][0], exception) assert_timestamp(event["timestamp"]) diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index c02d03312..e32b2e922 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -119,7 +119,7 @@ def test_exception_and_session_http(cmake, httpserver): run( tmp_path, "sentry_example", - ["log", "start-session", "capture-exception"], + ["log", "start-session", "capture-exception", "add-stacktrace"], check=True, env=env, ) @@ -129,6 +129,7 @@ def test_exception_and_session_http(cmake, httpserver): envelope = Envelope.deserialize(output) assert_exception(envelope) + assert_stacktrace(envelope, inside_exception=True) assert_session(envelope, {"init": True, "status": "ok", "errors": 1}) output = httpserver.log[1][0].get_data()