Scopes and Hubs

When an event is captured and sent to Sentry, SDKs will merge that event data with extra information from the current scope. SDKs will typically automatically manage the scopes for you in the framework integrations and you don’t need to think about them. However, you should know what a scope is and how you can use it for your advantage.

What’s a Scope, what’s a Hub

You can think of the hub as the central point that our SDKs use to route an event to Sentry. When you call init() a hub is created and a client and a blank scope are created on it. That hub is then associated with the current thread and will internally hold a stack of scopes.

The scope will hold useful information that should be sent along with the event. For instance contexts or breadcrumbs are stored on the scope. When a scope is pushed, it inherits all data from the parent scope and when it pops all modifications are reverted.

The default SDK integrations will push and pop scopes intelligently. For instance web framework integrations will create and destroy scopes around your routes or controllers.

How do the Scope and Hub Work

As you start using an SDK a scope and hub are automatically created for you out of the box. The hub you are unlikely to be interacting with directly unless you are writing an integration or you want to create or destroy scopes. Scopes on the other hand are more user facing. You can at any point in time call configure-scope to modify data stored on the scope. This is for instance used to modify the context.

When you call a global function such as capture_event internally Sentry discovers the current hub and asks it to capture an event. Internally the hub will then merge the event with the topmost scope’s data.

Configuring the Scope

The most useful operation when working with scopes is the configure-scope function. It can be used to reconfigure the current scope. This for instance can be used to add custom tags or to inform sentry about the currently authenticated user.

from sentry_sdk import configure_scope

with configure_scope() as scope:
    scope.set_tag("my-tag", "my value")
    scope.user = {'id': 42, 'email': 'john.doe@example.com'}
Sentry.configureScope((scope) => {
  scope.setTag("my-tag", "my value");
  scope.setUser({
    id: 42,
    email: "john.doe@example.com"
  });
});
using Sentry;

SentrySdk.ConfigureScope(scope =>
{
    scope.SetTag("my-tag", "my value");
    scope.User = new User
    {
        Id = "42",
        Email = "john.doe@example.com"
    };
});

Or when an asynchronous call is required:

await SentrySdk.ConfigureScopeAsync(scope =>
{
    scope.User = await _context.Users.FindAsync(id);
});
use sentry::{configure_scope, User};

configure_scope(|scope| {
    scope.set_tag("my-tag", "my value");
    scope.set_user(Some(User {
        id: Some(42.to_string()),
        email: Some("john.doe@exmaple.com".into()),
        ..Default::default()
    }));
});

To learn what useful information can be associated with scopes see the context documentation.

Local Scopes

We also have support for pushing and configuring a scope in one go. This is typically called with-scope or push-scope which is also very helpful if you only want to send data with one specific event. In the following example we are using that function to attach a level and a tag to only one specific error:

from sentry_sdk import push_scope, capture_exception

with push_scope() as scope:
    scope.set_tag("my-tag", "my value")
    scope.level = 'warning'
    # will be tagged with my-tag="my value"
    capture_exception(Exception("my error"))

# will not be tagged with my-tag
capture_exception(Exception("my other error"))
Sentry.withScope(scope => {
  scope.setTag("my-tag", "my value");
  scope.setLevel('warning');
  // will be tagged with my-tag="my value"
  Sentry.captureException(new Error('my error'));
});

// will not be tagged with my-tag
Sentry.captureException(new Error('my other error'));
using Sentry;
using Sentry.Protocol;

SentrySdk.WithScope(scope =>
{
    scope.SetTag("my-tag", "my value");
    scope.Level = SentryLevel.Warning;
    // will be tagged with my-tag="my value"
    SentrySdk.CaptureException(new Exception("my error"));
});

// will not be tagged with my-tag
SentrySdk.CaptureException(new Exception("my other error"));
use sentry::{Level, with_scope};
use sentry::integrations::failure::capture_error;
use failure::err_msg;

with_scope(|scope| {
    scope.set_tag("my-tag", "my value");
    scope.set_level(Some(Level::Warning));
}, || {
    // will be tagged with my-tag="my value"
    capture_error(err_msg("my error"))
});

// will not be tagged with my-tag
capture_error(err_msg("my other error"))

Note that in Rust two callbacks are invoked. One for configuring the scope, and a second which is executed in the context of the scope.

While this example looks similar to configure-scope it’s very different, in the sense that configure-scope actually changes the current active scope, all successive calls to configure-scope will keep the changes.

While on the other hand using with-scope creates a clone of the current scope and will stay isolated until the function call is completed.
So you can either set context information in there that you don’t want to be somewhere else or create do not attach any context information at all by calling clear on the scope, while the “global” scope remains unchanged.