Scopes
A scope holds the data the SDK attaches to your events. Learn how the Native SDK's global scope works, and when to create a scope of your own.
Scopes store extra data that the SDK adds to your event when sending the event to Sentry. Some of it you set yourself and some the SDK fills in, so it's worth knowing what a scope holds and how one gets applied.
A scope holds the data that rides along with your telemetry: tags, contexts, breadcrumbs, user, severity level, fingerprint, attachments, and attributes carried by logs and metrics. Each has a sentry_scope_* setter taking the scope as its first argument, alongside the sentry_set_* function that writes the same data globally.
Two scopes can apply to a capture: the global scope, which the SDK creates during sentry_init() and applies to everything it sends, and, optionally, a scope you create and hand to a capture function. A scope you create doesn't replace the global scope, it layers on top of it, so the event still gets the global scope's release, environment, tags, and user.
Crash Events Only Carry the Global Scope
A scope you create reaches Sentry only through the sentry_scope_capture_* functions. Crash events don't use it because, at crash time, the SDK can only rely on the global scope.
Put anything a crash report needs — a build identifier, the signed-in user, or the asset being processed — on the global scope.
The global scope applies to every event, log, metric, and crash, so it's where long-lived data belongs — the signed-in user, the build channel, a machine identifier. The SDK owns it: it's created during sentry_init() and released by sentry_close().
#include <sentry.h>
sentry_set_tag("my-tag", "my value");
// Set a user with id, username, email, and ip_address
sentry_value_t user = sentry_value_new_user("42", "Jane Doe", "jane.doe@example.com", "{{auto}}");
sentry_set_user(user);
#include <sentry.h>
sentry_set_tag("my-tag", "my value");
// Set a user with id, username, email, and ip_address
sentry_value_t user = sentry_value_new_user("42", "Jane Doe", "jane.doe@example.com", "{{auto}}");
sentry_set_user(user);
You can also set data on scopes you create, using the sentry_scope_* setters that take the scope as their first argument:
sentry_scope_set_tag(scope, "my-tag", "my value");
sentry_scope_set_user(scope, user);
sentry_scope_set_tag(scope, "my-tag", "my value");
sentry_scope_set_user(scope, user);
Alongside tags and users, scopes accept breadcrumbs, contexts, extras, levels, fingerprints, attributes, and attachments:
sentry_scope_add_breadcrumb(scope, breadcrumb);
sentry_scope_set_context(scope, "character", character);
sentry_scope_update_context(scope, "character", partial_character);
sentry_scope_set_extra(scope, "character.name", name);
sentry_scope_set_level(scope, SENTRY_LEVEL_WARNING);
sentry_scope_set_fingerprint(scope, "my-fingerprint", NULL);
sentry_scope_set_attribute(scope, "cache.backend", attribute);
sentry_scope_attach_file(scope, "/var/local.log");
sentry_scope_add_breadcrumb(scope, breadcrumb);
sentry_scope_set_context(scope, "character", character);
sentry_scope_update_context(scope, "character", partial_character);
sentry_scope_set_extra(scope, "character.name", name);
sentry_scope_set_level(scope, SENTRY_LEVEL_WARNING);
sentry_scope_set_fingerprint(scope, "my-fingerprint", NULL);
sentry_scope_set_attribute(scope, "cache.backend", attribute);
sentry_scope_attach_file(scope, "/var/local.log");
The ones taking a string key or path also have an _n variant that takes explicit lengths, such as sentry_scope_set_tag_n and sentry_scope_attach_file_n.
One Global Scope, Shared by All Threads
The Native SDK keeps a single global scope shared by every thread, and no thread-local state. Writes to it are safe from a data-race perspective, but they aren't isolated: a tag one thread sets is visible to events captured on every other thread.
If your program handles several users or requests concurrently, capture with a scope per unit of work instead.
For how this affects tracing, see Connect Errors With Spans.
Create a scope when the data describes a unit of work rather than the whole program, such as the asset being decoded or the request being handled. Set the data on the scope, then pass it to a capture function.
The constructor you choose determines the scope's lifetime. Use sentry_local_scope_new for a one-shot scope when the data applies to a single capture:
sentry_scope_t *scope = sentry_local_scope_new();
sentry_scope_set_tag(scope, "asset.kind", "texture");
sentry_scope_attach_file(scope, "/var/log/asset-loader.log");
sentry_value_t event = sentry_value_new_message_event(
SENTRY_LEVEL_ERROR, "asset-loader", "Failed to decode texture");
// Takes ownership of the scope and frees it.
sentry_scope_capture_event(scope, event);
sentry_scope_t *scope = sentry_local_scope_new();
sentry_scope_set_tag(scope, "asset.kind", "texture");
sentry_scope_attach_file(scope, "/var/log/asset-loader.log");
sentry_value_t event = sentry_value_new_message_event(
SENTRY_LEVEL_ERROR, "asset-loader", "Failed to decode texture");
// Takes ownership of the scope and frees it.
sentry_scope_capture_event(scope, event);
sentry_scope_new creates a scope you own. Configure it once and reuse it for as many captures as you need when the same context would otherwise be rebuilt each time, such as for a worker thread, subsystem, or connected client:
sentry_scope_t *scope = sentry_scope_new();
sentry_scope_set_tag(scope, "worker", "asset-loader");
sentry_scope_set_user(scope, user);
// Applied, but not freed.
sentry_scope_capture_event(scope, first_event);
sentry_scope_capture_event(scope, second_event);
sentry_scope_free(scope);
sentry_scope_t *scope = sentry_scope_new();
sentry_scope_set_tag(scope, "worker", "asset-loader");
sentry_scope_set_user(scope, user);
// Applied, but not freed.
sentry_scope_capture_event(scope, first_event);
sentry_scope_capture_event(scope, second_event);
sentry_scope_free(scope);
Lifetime is the only thing that separates the two. Both are a sentry_scope_t * and take the same setters, so the constructor you chose is the only thing that decides who cleans up, and getting it wrong means either a double free or a leak. Every sentry_scope_capture_* function follows this rule.
Scope data isn't just for errors. Feedback, logs, and metrics each can take a scope through their own sentry_scope_capture_* function, so the context you build for one capture can enrich any of them:
sentry_scope_capture_event(scope, event);
sentry_scope_capture_feedback(scope, feedback, hint);
sentry_scope_capture_log(scope, SENTRY_LEVEL_INFO, "Cache miss", attributes);
sentry_scope_capture_metric(
scope, SENTRY_METRIC_COUNT, "cache.miss", sentry_value_new_int64(1), NULL, attributes);
sentry_scope_capture_event(scope, event);
sentry_scope_capture_feedback(scope, feedback, hint);
sentry_scope_capture_log(scope, SENTRY_LEVEL_INFO, "Cache miss", attributes);
sentry_scope_capture_metric(
scope, SENTRY_METRIC_COUNT, "cache.miss", sentry_value_new_int64(1), NULL, attributes);
When you capture with a scope, the SDK combines that scope with the global scope. Data set in only one place is still included, so events captured with their own scope continue to carry global values such as release, environment, and user. When the same key is set in more than one place, the most specific one wins: event data > passed scope > global scope. Data you set directly on the event value therefore survives whatever the scopes carry.
Logs and metrics resolve attributes from the call site as well, which is the most specific source of the three: per-call attributes > passed scope > global scope. See Attributes for how to build them.
To clear a scope, use sentry_scope_clear to discard most data while preserving the trace:
sentry_scope_clear(scope); // Ready for the next request.
sentry_scope_clear(scope); // Ready for the next request.
sentry_scope_clone copies a scope so you can branch off a configured base. Top-level fields are copied and nested values are shared by reference. The copy is always one you own, even when cloned from a scope created with sentry_local_scope_new, so you must free it yourself:
sentry_scope_t *request_scope = sentry_scope_clone(base_scope);
sentry_scope_set_tag(request_scope, "request.id", request_id);
sentry_scope_capture_event(request_scope, event);
sentry_scope_free(request_scope);
sentry_scope_t *request_scope = sentry_scope_clone(base_scope);
sentry_scope_set_tag(request_scope, "request.id", request_id);
sentry_scope_capture_event(request_scope, event);
sentry_scope_free(request_scope);
To learn what else you can put on a scope, see the context documentation.
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").