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",