Screenshots
Learn more about taking screenshots when an error occurs. Sentry pairs the screenshot with the original event, giving you additional insight into issues.
Sentry makes it possible to automatically take a screenshot and include it as an attachment when a user experiences an error, an exception or a crash.
This feature is only available for SDKs with a user interface, like the ones for mobile and desktop applications. It's also limited by whether taking a screenshot is possible or not. For example, in some environments, like native iOS, taking a screenshot requires the UI thread, which often isn't available in the event of a crash. Another example where a screenshot might not be available is when the event happens before the screen starts to load. So inherently, this feature is a best effort solution.
Because screenshots may contain PII, they are an opt-in feature. You can enable screenshots as shown below:
AndroidManifest.xml<application>
<meta-data
android:name="io.sentry.attach-screenshot"
android:value="true"
/>
</application>
Before enabling screenshots in production, verify your masking configuration to ensure no sensitive data is captured. If you find any masking issues or sensitive data that should be masked but isn't, please create a GitHub issue and avoid deploying to production with screenshots enabled until the issue is resolved.
Requires SDK version 6.24.0 or higher.
Because capturing screenshots can be a resource-intensive operation on Android, it's limited to one screenshot every 2 seconds using a debouncing mechanism. This behavior can be overruled if you supply a BeforeCaptureCallback for screenshots in the SentryAndroidOptions.
The BeforeCaptureCallback also allows you to customize the behavior based on event data, so you can decide when to capture a screenshot and when not to. For example, you can decide to only capture screenshots of crashed and fatal events:
import io.sentry.android.core.SentryAndroid;
SentryAndroid.init(this, options -> {
options.setBeforeScreenshotCaptureCallback((event, hint, debounce) -> {
// always capture crashed events
if (event.isCrashed()) {
return true;
}
// if debounce is active, skip capturing
if (debounce) {
return false;
} else {
// also capture fatal events
return event.getLevel() == SentryLevel.FATAL;
}
});
});
Screenshot masking allows you to mask sensitive data in screenshots before they are captured. You can customize this behavior to fit your needs.
Screenshot masking requires the sentry-android-replay module at runtime. This module is included by default if you use the sentry-android dependency. If you only depend on sentry-android-core, add the replay module explicitly:
app/build.gradledependencies {
implementation 'io.sentry:sentry-android-replay:8.38.0'
}
If masking options are configured but the module is not available at runtime, the SDK will skip capturing screenshots entirely to avoid leaking sensitive data.
Unlike Session Replay, screenshot masking is disabled by default. You can enable masking for all text and image content:
AndroidManifest.xml<application>
<meta-data
android:name="io.sentry.screenshot.mask-all-text"
android:value="true"
/>
<meta-data
android:name="io.sentry.screenshot.mask-all-images"
android:value="true"
/>
</application>
When setMaskAllImages(true) is set, the SDK will also mask WebView, VideoView, CameraX PreviewView, ExoPlayer, and Media3 player views in addition to ImageView.
You can choose which type of view to mask or unmask by using addMaskViewClass or addUnmaskViewClass. These accept fully-qualified class names and apply to all subclasses of the specified class as well.
import io.sentry.android.core.SentryAndroid
SentryAndroid.init(this) { options ->
options.screenshot.addMaskViewClass("com.example.MyCustomView")
options.screenshot.addUnmaskViewClass("com.example.PublicLabel")
}
If you use R8/ProGuard, make sure to keep the class names you reference in masking rules. Obfuscated class names will not match at runtime.
You can mask or unmask specific view instances using extension functions from the sentry-android-replay module:
import io.sentry.android.replay.sentryReplayMask
import io.sentry.android.replay.sentryReplayUnmask
myTextView.sentryReplayMask()
myImageView.sentryReplayUnmask()
You can also set masking via XML layout tags:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sensitive data"
>
<tag android:id="@id/sentry_privacy" android:value="mask" />
</TextView>
Alternatively, you can use the android:tag attribute with the sentry-mask or sentry-unmask string values:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="sentry-mask"
android:text="Sensitive data"
/>
In Java, you can use view tags with the sentry_privacy resource ID:
import io.sentry.android.replay.R;
view.setTag(R.id.sentry_privacy, "mask");
view.setTag(R.id.sentry_privacy, "unmask");
For Jetpack Compose, use the sentryReplayMask() and sentryReplayUnmask() modifiers:
import io.sentry.android.replay.sentryReplayMask
import io.sentry.android.replay.sentryReplayUnmask
@Composable
fun MyScreen() {
Column {
Text(
text = "Sensitive info",
modifier = Modifier.sentryReplayMask()
)
Text(
text = "Public info",
modifier = Modifier.sentryReplayUnmask()
)
}
}
If a
ViewGroupis marked as masked, all its child views will also be masked, even if some children would normally not be masked. This prioritizes safety and ensures no sensitive information is unintentionally exposed.If a
ViewGroupis marked as unmasked, its child views don't automatically inherit this behavior. You'll need to explicitly mark each child view as unmasked if you want them to appear unmasked.
Masking and unmasking rules are applied in the following order:
- Check if a view is marked as
unmaskedvia a tag/extension or modifier. - Check if a view is marked as
maskedvia a tag/extension or modifier. - Check if a view's class is marked as unmasked via
addUnmaskViewClass. - Check if a view's class is marked as masked via
addMaskViewClass.
If one is available, you'll see a thumbnail of the screenshot when you click on a specific issue from the Issues page.
Once you've clicked on the event ID of a specific issue, you'll be able to see an overview of all the attachments as well as associated events in the "Attachments" tab.
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").