Scopes

SDKs will typically automatically manage the scopes for you in the framework integrations. Learn what a scope is and how you can use it to your advantage.

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 to your advantage.

Scopes hold useful information that gets sent along with the event. For instance, contexts and breadcrumbs are stored on the scope. When a scope is forked, it inherits all data from its parent scope.

The default SDK integrations will fork scopes intelligently. For instance, web framework integrations will fork scopes around your routes or request handlers.

Scopes are basically a stacks of data that are attached to events. When an event is captured, the SDK will merge the data from the active scopes into the event. This allows you to attach data to events that is relevant to the context in which the event was captured.

A scope is generally valid inside of a callback or an execution context. This means that multiple parts of your application may have different scopes active at the same time. For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

When you call a global function such as Sentry.captureException, Sentry automatically discovers the active scopes and applies them when capturing the event.

The Sentry SDK has three different kinds of scopes:

The global scope is applied to all events, no matter where they originate. You can use it to store data that should apply to all events, such as environmental information.

You can access the global scope via Sentry.getGlobalScope() or Sentry.configureScope(ScopeType.GLOBAL, globalScope -> { ... }).

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope.

You can modify the isolation scope via Sentry.configureScope(ScopeType.ISOLATION, isolationScope -> { ... }), but usually you'll just use the Sentry.setXXX methods to set data on the currently active isolation scope:

Copied
Sentry.setTag("my-tag", "my value");
// Is identical to:
Sentry.configureScope(ScopeType.ISOLATION, scope -> {
  scope.setTag("my-tag", "my value");
});

The current scope is the local scope that is currently active. Unlike the rarely-forked isolation scope, the current scope may be forked more frequently and under the hood. It can be used to store data that should only be applied to specific events. In most cases, you should not access this scope directly, but use Sentry.withScope to create a new scope that is only active for a specific part of your code:

Copied
Sentry.withScope(scope -> {
  // scope is the current scope inside of this callback!
  scope.setTag("my-tag", "my value");
  // this tag will only be applied to events captured inside of this callback
  // the following event will have the tag:
  Sentry.captureException(new Exception("my error"));
});
// this event will not have the tag:
Sentry.captureException(new Exception("my other error"));

You can modify the current scope via Sentry.configureScope(ScopeType.CURRENT, scope -> { ... }), but usually you should use Sentry.withScope() to interact with local scopes instead.

Global scope, isolation scope, and current scope are combined before an event (like an error or transaction) gets sent to Sentry. In most cases, setting something on current scope replaces the same thing that may have been set on isolation or global scope. If it hasn't been set on current scope, the value on isolation scope takes precedence over the one from global scope. If there also isn't any value on isolation scope, the one from global scope is used if present.

Note, there are exceptions to this, where values from all scopes are merged. This is the case for breadcrumbs, tags, extras, contexts, attachments and event processors.

Copied
Sentry.configureScope(ScopeType.GLOBAL, scope -> {
  scope.setExtra("shared", "global");
  scope.setExtra("global", "data");
});

Sentry.configureScope(ScopeType.ISOLATION, scope -> {
  scope.setExtra("shared", "isolation");
  scope.setExtra("isolation", "data");
});

Sentry.configureScope(ScopeType.CURRENT, scope -> {
  scope.setExtra("shared", "current");
  scope.setExtra("current", "data");
});

Sentry.captureException(new Exception("my error"));
// --> Will have the following extra:
// { shared: 'current', global: 'data', isolation: 'data', current: 'data' }

There are two main ways to interact with the scope. You can modify the current scope via Sentry.configureScope(ScopeType.CURRENT, scope -> { ... }) and use setters on the resulting scope, or you can use global methods like Sentry.setTag() directly, which will set on the respective scope under the hood (which will be the isolation scope).

You can, for instance, add custom tags or inform Sentry about the currently authenticated user.

Copied
import io.sentry.Sentry;
import io.sentry.protocol.User;

Sentry.configureScope(scope -> {
  scope.setTag("my-tag", "my value");
  User user = new User();
  user.setId("42");
  user.setEmail("john.doe@example.com");
  scope.setUser(user);
});

You can also apply this configuration when unsetting a user at logout:

Copied
import io.sentry.Sentry;

Sentry.configureScope(scope -> {
  scope.setUser(null);
});

To learn what useful information can be associated with scopes see context, tags, users and breadcrumbs.

In the following example we use withScope to attach a level and a tag to only one specific error:

Copied
import io.sentry.Sentry;
import io.sentry.SentryLevel;

Sentry.withScope(scope -> {
  scope.setTag("my-tag", "my value");
  scope.setLevel(SentryLevel.WARNING);

  // will be tagged with my-tag="my value"
  Sentry.captureException(new Exception("my error"));
});

// will not be tagged with my-tag
Sentry.captureException(new Exception("my error"));

While this example looks similar to configureScope, it is actually very different. Calls to configureScope change the current active scope; all successive calls to configureScope will maintain previously set changes unless they are explicitly unset.

On the other hand, withScope creates a clone of the current scope, and the changes made will stay isolated within the withScope callback function. This allows you to more easily isolate pieces of context information to specific locations in your code or even call clear to briefly remove all context information.

withIsolationScope works fundamentally the same as withScope, but it will fork the isolation scope instead of the current scope. Generally, the isolation scope is meant to be forked less frequently than the current scope, and in most cases the SDK will handle this automatically for you.

But in cases where you want to isolate a non-request process (for example, a background job), you can use withIsolationScope to create a new isolation scope that is only active for the duration of the callback:

Copied
import io.sentry.Sentry;
import io.sentry.SentryLevel;

Sentry.withIsolationScope(scope -> {
  Sentry.setTag("my-tag", "my value");
  scope.setLevel(SentryLevel.WARNING);

  // will be tagged with my-tag="my value"
  Sentry.captureException(new Exception("my error"));
});

// will not be tagged with my-tag
Sentry.captureException(new Exception("my other error"));

In the following example we use the scope callback parameter that is available for all capture methods to attach a level and a tag to only one specific error:

Copied
import io.sentry.Sentry;
import io.sentry.SentryLevel;

// will be tagged with my-tag="my value"
Sentry.captureException(new Exception("my error"), scope -> {
  scope.setTag("my-tag", "my value");
  scope.setLevel(SentryLevel.WARNING);
});

// will not be tagged with my-tag
Sentry.captureException(new Exception("my error"));

Before the callback is invoked the SDK creates a clone of the current scope, and the changes made will stay isolated within the callback function. This allows you to more easily isolate pieces of context information to specific locations in your code or even call clear to briefly remove all context information.

Sentry's SDK for Java stores the scope and the context in a thread-local variable. To make sure that a coroutine has access to the correct Sentry context, an instance of SentryContext must be provided when launching a coroutine.

Copied
<dependency>
    <groupId>io.sentry</groupId>
    <artifactId>sentry-kotlin-extensions</artifactId>
    <version>8.0.0</version>
</dependency>

Copied
import io.sentry.kotlin.SentryContext
import io.sentry.Sentry

launch(SentryContext()) {
  // tag set in parent coroutine is visible to child coroutine
  Sentry.setTag("parent-tag", "value")
  launch() {
    // tag set in child coroutine is not visible in parent coroutine
    Sentry.setTag("child-tag", "value")
  }
}
Help improve this content
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").