Skip to content

Commit

Permalink
feat: Add more Event Payload convenience methods (#517)
Browse files Browse the repository at this point in the history
Adds:
* `sentry_value_new_exception`
* `sentry_value_new_thread`
* `sentry_value_new_stacktrace`
* `sentry_event_add_exception`
* `sentry_event_add_thread`

Deprecates `sentry_event_value_add_stacktrace`
  • Loading branch information
Swatinem authored Apr 19, 2021
1 parent 6f54c67 commit 60afb29
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 57 deletions.
21 changes: 7 additions & 14 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
87 changes: 79 additions & 8 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,26 +338,92 @@ 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.
*/
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 -- */

/**
Expand All @@ -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);
Expand All @@ -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.
*/
Expand All @@ -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(
Expand Down
29 changes: 6 additions & 23 deletions src/backends/sentry_backend_inproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
92 changes: 83 additions & 9 deletions src/sentry_value.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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];

Expand All @@ -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);
}
3 changes: 1 addition & 2 deletions tests/assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])


Expand Down
3 changes: 2 additions & 1 deletion tests/test_integration_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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()
Expand Down

0 comments on commit 60afb29

Please sign in to comment.