From b896cbd33c947a1255083428ce4c9e7129a23dbb Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Tue, 22 Sep 2026 14:22:52 +0500 Subject: [PATCH] docs(android): give ReportHandler its own page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API was covered in two places — a long section inside Manual reporting → Bug reporting, and most of the Privacy → Report page — which buried an API that applies to every report, not just manually filed ones, and meant two copies drifting apart. Moved to docs/sdk/android/report-handler.mdx, a top-level entry in the Android nav next to Manual reporting. Bug reporting keeps the heading and points at it; the privacy page keeps its scrubbing angle and defers the contract. Two things the previous text did not say, both taken from BugseeIssueReportingCoordinator: - Non-terminating callbacks run on the MAIN thread. The examples had callers doing file I/O there with a 30 s budget. The page now says so and shows the background-thread form, which works because the pipeline waits for completionCallback. - The terminating path differs in every respect worth knowing: background thread, a few seconds' cap, completionCallback no longer gating anything, scheduled work lost, and the timeout option not applying. Also documented that a throwing handler is caught and logged so the report still ships. Dropped the "3 attachments × 3 MB" quota rather than carrying it over. Those are 6.x numbers from AttachmentsHelper, which no longer exists at that path; the only cap in the 7.x source is a soft 1000-attachment ceiling in IssueReport, which is what the page now states. If a size limit is enforced server-side, it should be documented with a source. Retargeted the #attachments anchor references on docs/index.md and within bug-reporting.mdx, which the move would otherwise have broken. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Change-Id: Iaabe9d35aec2bfc19dc94c868dfa057a632fff50 --- docs/index.md | 2 +- docs/sdk/android/manual/bug-reporting.mdx | 86 +------ .../android/manual/crash-error-reporting.mdx | 2 +- docs/sdk/android/privacy/report.mdx | 15 +- docs/sdk/android/report-handler.mdx | 226 ++++++++++++++++++ sidebars.ts | 1 + 6 files changed, 238 insertions(+), 94 deletions(-) create mode 100644 docs/sdk/android/report-handler.mdx diff --git a/docs/index.md b/docs/index.md index 239c3e0..0e5843d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -197,7 +197,7 @@ Bugsee has a very rich feature set, yet on some platforms we managed to achieve File attachments View - View + View View View View diff --git a/docs/sdk/android/manual/bug-reporting.mdx b/docs/sdk/android/manual/bug-reporting.mdx index 05610b4..c91a86a 100644 --- a/docs/sdk/android/manual/bug-reporting.mdx +++ b/docs/sdk/android/manual/bug-reporting.mdx @@ -158,89 +158,9 @@ Crash, error, and bug reports get a default severity unless one is specified or ## Customize reports with `ReportHandler` -`ReportHandler` is the idiomatic way to inspect or rewrite reports before they are uploaded. It runs for **every** outgoing report — crashes and errors included, not just bug reports. +`ReportHandler` lets you inspect or rewrite a report before it is uploaded, and it runs for **every** outgoing report — crashes and handled errors included, not just the bug reports on this page. It is documented in full on its own page: -Two callbacks are provided as default methods on the interface: - -- `onBeforeReportCreated(Report, boolean isTerminating, Runnable completionCallback)` — runs before the report payload is assembled. -- `onAfterReportCreated(Report, boolean isTerminating, Runnable completionCallback)` — runs after assembly, ideal for attachments. - -You **must** invoke `completionCallback.run()` for the pipeline to proceed — unless `isTerminating` is `true`, in which case the pipeline continues regardless (the process is about to exit). - - - - -```java -Bugsee.setReportHandler(new ReportHandler() { - @Override - public void onBeforeReportCreated(Report report, boolean isTerminating, Runnable completionCallback) { - report.setSummary("[" + BuildConfig.FLAVOR + "] " + report.getSummary()); - report.setSeverity(IssueSeverity.High); - completionCallback.run(); - } - - @Override - public void onAfterReportCreated(Report report, boolean isTerminating, Runnable completionCallback) { - Attachment attachment = report.createAndAddAttachment("config") - .setName("config") - .setFileName("config.json") - .setMimeType("application/json"); - try (OutputStream out = attachment.openStream()) { - if (out != null) { - out.write(loadConfigSnapshot()); - } - } catch (IOException ignored) { - } - completionCallback.run(); - } -}); -``` - - - - -```kotlin -Bugsee.setReportHandler(object : ReportHandler { - override fun onBeforeReportCreated(report: Report, isTerminating: Boolean, completionCallback: Runnable) { - report.summary = "[${BuildConfig.FLAVOR}] ${report.summary}" - report.severity = IssueSeverity.High - completionCallback.run() - } - - override fun onAfterReportCreated(report: Report, isTerminating: Boolean, completionCallback: Runnable) { - report.createAndAddAttachment("config") - .setName("config") - .setFileName("config.json") - .setMimeType("application/json") - .openStream()?.use { it.write(loadConfigSnapshot()) } - completionCallback.run() - } -}) -``` - - - - -### Attachments - -Add attachments from `onAfterReportCreated`. `Report.createAndAddAttachment(name)` returns a mutable `Attachment` that you populate with fluent setters and write to via its output stream: - -- `Attachment setName(String)` — display name shown in the dashboard. -- `Attachment setFileName(String)` — file name (with extension) used for the download. -- `Attachment setMimeType(String)` — MIME type (e.g. `"application/json"`). -- `OutputStream openStream()` — opens the (truncating) stream to write the attachment bytes; may return `null` if the attachment cannot be opened. The caller owns closing the stream and should buffer writes. - -The setters return the same `Attachment`, so they can be chained. Reports allow up to **3 attachments × 3 MB**; enforce it inside your own handler if relevant. - -### Callback timeout - -The SDK waits at most `ReportHandlerCallbackTimeout` seconds (default `30`) for `completionCallback` to fire. Tune via manifest: - -```xml - -``` +- [Report handler](/sdk/android/report-handler) — the two callbacks, the completion contract and its timeout, threading, behaviour during a crash, and attachments. ## Programmatic report creation @@ -257,7 +177,7 @@ The `Report` you receive is mutable. The most commonly used members: - `setSummary(String)` / `setDescription(String)` — the report's title and body text. - `setSeverity(IssueSeverity)` — one of `VeryLow`, `Medium`, `High`, `Critical`, `Blocker`. - `addLabel(String)` / `addLabels(List)` / `setLabels(List)` / `clearLabels()` — dashboard labels. -- `getAttachments()` returns the current `List`; `createAndAddAttachment(name)` adds a new one (see [Attachments](#attachments) above). +- `getAttachments()` returns the current `List`; `createAndAddAttachment(name)` adds a new one (see [Attachments](/sdk/android/report-handler#attachments)). The same `Report` is what `ReportHandler.onBeforeReportCreated(...)` / `onAfterReportCreated(...)` hands you, so the same mutators apply there. diff --git a/docs/sdk/android/manual/crash-error-reporting.mdx b/docs/sdk/android/manual/crash-error-reporting.mdx index c33d4fe..576a7cf 100644 --- a/docs/sdk/android/manual/crash-error-reporting.mdx +++ b/docs/sdk/android/manual/crash-error-reporting.mdx @@ -97,4 +97,4 @@ Bugsee.testCrash(); ## Report severity -Crash and error reports receive a default severity — `IssueSeverity.Blocker` for crashes and `IssueSeverity.High` for errors — unless you override it. Change the defaults under [Default severities](/sdk/android/manual/bug-reporting#default-severities), set a severity per call via the `logException(ex, options)` overload, or adjust any report from a [`ReportHandler`](/sdk/android/manual/bug-reporting#customize-reports-with-reporthandler). See [Severity values](/sdk/android/manual/bug-reporting#severity-values) for the full list. +Crash and error reports receive a default severity — `IssueSeverity.Blocker` for crashes and `IssueSeverity.High` for errors — unless you override it. Change the defaults under [Default severities](/sdk/android/manual/bug-reporting#default-severities), set a severity per call via the `logException(ex, options)` overload, or adjust any report from a [`ReportHandler`](/sdk/android/report-handler). See [Severity values](/sdk/android/manual/bug-reporting#severity-values) for the full list. diff --git a/docs/sdk/android/privacy/report.mdx b/docs/sdk/android/privacy/report.mdx index 4f00a3c..2a0b8f7 100644 --- a/docs/sdk/android/privacy/report.mdx +++ b/docs/sdk/android/privacy/report.mdx @@ -49,7 +49,7 @@ Bugsee.setReportHandler(new ReportHandler() { @Override public void onAfterReportCreated(Report report, boolean isTerminating, Runnable completionCallback) { - // max 3 attachments × 3 MB — write your data via openStream() + // Write your data through the attachment's output stream Attachment attachment = report.createAndAddAttachment("config") .setFileName("config.json") .setMimeType("application/json"); @@ -80,7 +80,7 @@ Bugsee.setReportHandler(object : ReportHandler { isTerminating: Boolean, completionCallback: Runnable ) { - // max 3 attachments × 3 MB — write your data via openStream() + // Write your data through the attachment's output stream val attachment = report.createAndAddAttachment("config") .setFileName("config.json") .setMimeType("application/json") @@ -93,10 +93,7 @@ Bugsee.setReportHandler(object : ReportHandler { -The previous quota of **3 attachments × 3 MB each** is preserved — enforce -it inside your own `ReportHandler` if needed. - -For programmatic report creation see -[manual reports](/sdk/android/manual/bug-reporting). For the complete list of -listener interfaces see the -[public API reference](/sdk/android/configuration). +The full contract — when each callback runs, what happens during a crash, +which thread you are on, the completion timeout, and how attachments work — +is documented on the [report handler](/sdk/android/report-handler) page. This +page covers only the privacy side of it. diff --git a/docs/sdk/android/report-handler.mdx b/docs/sdk/android/report-handler.mdx new file mode 100644 index 0000000..f34d14d --- /dev/null +++ b/docs/sdk/android/report-handler.mdx @@ -0,0 +1,226 @@ +--- +title: "Report handler" +description: "Inspect, enrich, or scrub every Bugsee report before it is uploaded with the Android SDK's ReportHandler — callbacks, threading, timeouts, and attachments." +sidebar_position: 5 +slug: "/sdk/android/report-handler" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A **report handler** is the single place to inspect or rewrite a report before Bugsee uploads it. It runs for **every** outgoing report — crashes, handled errors, user-filed bug reports, and silent uploads alike — so it is the right hook for anything that must apply to all of them: tagging reports with your build variant, raising severity for a subset, attaching a config snapshot, or scrubbing text a user typed into the report form. + +Register one handler per process, as early as you can — typically right after `Bugsee.launch(...)`: + + + + +```java +Bugsee.setReportHandler(new ReportHandler() { + @Override + public void onBeforeReportCreated(Report report, boolean isTerminating, Runnable completionCallback) { + report.setSummary("[" + BuildConfig.FLAVOR + "] " + report.getSummary()); + completionCallback.run(); + } +}); +``` + + + + +```kotlin +Bugsee.setReportHandler(object : ReportHandler { + override fun onBeforeReportCreated(report: Report, isTerminating: Boolean, completionCallback: Runnable) { + report.summary = "[${BuildConfig.FLAVOR}] ${report.summary}" + completionCallback.run() + } +}) +``` + + + + +Setting a new handler replaces the previous one. Pass `null` to remove it. + +## The two callbacks + +Both are `default` methods on the interface, so implement only the one you need. + +|Callback|When it runs|Use it for| +|---|---|---| +|`onBeforeReportCreated`|Before the report payload is assembled|Rewriting summary, description, severity, labels, and attributes; scrubbing user-entered text| +|`onAfterReportCreated`|After assembly, before upload|Adding attachments, and any change that should see the assembled report| + +Each receives the `Report`, an `isTerminating` flag, and a `completionCallback`. + +## Completing the callback + +The reporting pipeline **waits** for you: nothing is uploaded until you call `completionCallback.run()`. This is what makes asynchronous work possible — you can hand the report off to a background thread and complete the callback when that work finishes. + +Because a handler that never completes would strand the report, the SDK arms a timeout. If `completionCallback` has not fired within `ReportHandlerCallbackTimeout` seconds (default **30**), it fires automatically and the report proceeds with whatever changes you had made by then. Set the option to `0` to disable the timeout. + +```xml + +``` + +The callback is safe to invoke more than once — only the first call advances the pipeline. + +## Threading + +Non-terminating reports invoke your callbacks on the **main thread**. Keep them short: anything slow — file I/O, network calls, compression — should run on your own background thread, with `completionCallback.run()` called when it finishes. + + + + +```java +@Override +public void onAfterReportCreated(Report report, boolean isTerminating, Runnable completionCallback) { + if (isTerminating) { + // The process is going away — do the minimum, inline. + completionCallback.run(); + return; + } + executor.execute(() -> { + writeDiagnosticsAttachment(report); + completionCallback.run(); + }); +} +``` + + + + +```kotlin +override fun onAfterReportCreated(report: Report, isTerminating: Boolean, completionCallback: Runnable) { + if (isTerminating) { + // The process is going away — do the minimum, inline. + completionCallback.run() + return + } + executor.execute { + writeDiagnosticsAttachment(report) + completionCallback.run() + } +} +``` + + + + +## Crashes and other terminating reports + +When `isTerminating` is `true` the process is about to exit, and the rules change: + +- Your callback runs on a background thread the SDK already had alive, and the SDK waits only a **few seconds** before finalizing the report anyway. +- Work you schedule for later is lost — the process will not be there to run it. Do the minimum inline and return. +- `completionCallback` no longer gates anything. Call it immediately or not at all; the pipeline continues either way. +- The `ReportHandlerCallbackTimeout` option does not apply to this path. + +A handler that behaves well on a crash therefore checks `isTerminating` first, as in the example above. + +## If your handler throws + +An exception thrown out of either callback is caught and logged, and the pipeline advances so the report is still delivered. Your changes up to the throw are kept. Don't rely on this — it exists so a bug in a handler cannot cost you reports. + +## What you can change + +The `Report` handed to you exposes: + +|Area|Members| +|---|---| +|Identity|`getId()`, `getType()`| +|Text|`getSummary()` / `setSummary(...)`, `getDescription()` / `setDescription(...)`, `getEmail()` / `setEmail(...)`| +|Severity|`getSeverity()` / `setSeverity(IssueSeverity)`| +|Labels|`getLabels()`, `addLabel(...)`, `addLabels(...)`, `setLabels(...)`, `clearLabels()`| +|Attributes|`getAttributes()`, `getAttribute(...)`, `setAttribute(...)`, `removeAttribute(...)`, `clearAllAttributes()`| +|Attachments|`getAttachments()`, `createAndAddAttachment(...)`, `clearAttachments()`| +|Screenshots|`getScreenshot(...)`, `setScreenshot(...)`, `setScreenshotAsync(...)`, `enumerateScreenshots(...)`| + +Attributes set here apply to this report only; for values that should ride along with every report, see [user & session data](/sdk/android/user-session-data). + +## Attachments + +Attachments are added from `onAfterReportCreated`. `createAndAddAttachment(name)` returns a mutable `Attachment` that you describe with fluent setters and fill through its output stream: + +- `setName(String)` — display name shown in the dashboard. +- `setFileName(String)` — file name, with extension, used for the download. +- `setMimeType(String)` — MIME type, for example `"application/json"`. +- `openStream()` — opens a truncating `OutputStream` for the attachment's bytes. It may return `null` if the attachment cannot be opened, and you own closing it. + + + + +```java +Attachment attachment = report.createAndAddAttachment("config") + .setName("config") + .setFileName("config.json") + .setMimeType("application/json"); + +try (OutputStream out = attachment.openStream()) { + if (out != null) { + out.write(loadConfigSnapshot()); + } +} catch (IOException ignored) { +} +``` + + + + +```kotlin +report.createAndAddAttachment("config") + .setName("config") + .setFileName("config.json") + .setMimeType("application/json") + .openStream()?.use { it.write(loadConfigSnapshot()) } +``` + + + + +A single report holds at most **1000** attachments. Past that the SDK logs a warning and skips the attachment — `createAndAddAttachment` still returns an object, but it is not part of the report. Keep attachments small regardless: they travel with the report on the user's connection. + +## Example: scrubbing sensitive text + +Anything a user types into the report form reaches you before it reaches Bugsee, which makes `onBeforeReportCreated` the place to redact it. + + + + +```java +Bugsee.setReportHandler(new ReportHandler() { + @Override + public void onBeforeReportCreated(Report report, boolean isTerminating, Runnable completionCallback) { + String description = report.getDescription(); + if (description != null) { + report.setDescription(description.replaceAll("\\d{16}", "[CARD]")); + } + report.removeAttribute("internal_session_token"); + completionCallback.run(); + } +}); +``` + + + + +```kotlin +Bugsee.setReportHandler(object : ReportHandler { + override fun onBeforeReportCreated(report: Report, isTerminating: Boolean, completionCallback: Runnable) { + report.description = report.description?.replace(Regex("\\d{16}"), "[CARD]") + report.removeAttribute("internal_session_token") + completionCallback.run() + } +}) +``` + + + + +## See also + +- [Bug reporting](/sdk/android/manual/bug-reporting) — triggers, the report dialog, and programmatic report creation. +- [Crash & error reporting](/sdk/android/manual/crash-error-reporting) — default severities and handled errors. +- [Privacy and report fields](/sdk/android/privacy/report) — what reaches Bugsee, and the other privacy controls. diff --git a/sidebars.ts b/sidebars.ts index aec4712..7340430 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -151,6 +151,7 @@ const sidebars: SidebarsConfig = { { type: "doc", id: "sdk/android/manual/crash-error-reporting", label: "Crash & error reporting" }, ], }, + { type: "doc", id: "sdk/android/report-handler", label: "Report handler" }, { type: "category", label: "Data capture",