@newinstance/bugwatch-tanstack
v0.1.0
Published
BugWatch adapter for TanStack Query. Captures query and mutation failures, leaves a breadcrumb trail, and traces requests, on React Web and React Native.
Maintainers
Readme
@newinstance/bugwatch-tanstack
BugWatch adapter for TanStack Query. Point it at a
QueryClient and every failed query and mutation is reported to BugWatch, with
the operation that failed, a breadcrumb trail of what led there, and a span for
the request.
Works on React Web and React Native.
npm install @newinstance/bugwatch-tanstackYou also need TanStack Query and the BugWatch SDK for your platform:
# React Web
npm install @tanstack/react-query @newinstance/bugwatch
# React Native
npm install @tanstack/react-query @newinstance/bugwatch-react-nativeQuick start
Initialise BugWatch as you normally would, then instrument the client once, where you create it.
React Web
import { QueryClient } from '@tanstack/react-query';
import { BugWatch } from '@newinstance/bugwatch';
import { instrumentQueryClientWithBugWatch } from '@newinstance/bugwatch-tanstack/web';
BugWatch.init({ dsn: process.env.BUGWATCH_DSN! });
export const queryClient = new QueryClient();
instrumentQueryClientWithBugWatch(queryClient);React Native
import { QueryClient } from '@tanstack/react-query';
import { BugWatch } from '@newinstance/bugwatch-react-native';
import { instrumentQueryClientWithBugWatch } from '@newinstance/bugwatch-tanstack/native';
BugWatch.init({ dsn: BUGWATCH_DSN });
export const queryClient = new QueryClient();
instrumentQueryClientWithBugWatch(queryClient);That is the whole integration. Nothing else in your app changes, and no query or mutation needs to know BugWatch exists.
What you get
| | Reported | |---|---| | Failed query | The error, tagged with the operation name and retry count, with the query key as context | | Failed mutation | The same, from the mutation key | | Breadcrumbs | Every query and mutation start, success and failure, in order, so a captured error arrives with the trail that led to it | | Spans | One span per query and mutation, with duration and failure status (web only, see below) |
Instrumenting returns a function that removes it again:
const stop = instrumentQueryClientWithBugWatch(queryClient);
stop();Platform differences, stated plainly
The two BugWatch SDKs are not identical, and this adapter does not pretend otherwise.
| | Web (@newinstance/bugwatch) | React Native (@newinstance/bugwatch-react-native) |
|---|---|---|
| Error capture | Yes | Yes |
| Breadcrumbs | Yes | Yes |
| Per-error tags | Yes | No: the native SDK's captureException takes a level only |
| Structured context | Yes, an object | Serialized to a string, since the native SDK takes strings |
| Spans / tracing | Yes | No: the native SDK has no span API |
Tracing is on by default. On React Native it is silently unavailable, so pass
onWarning if you want to hear about it rather than wonder:
instrumentQueryClientWithBugWatch(queryClient, {
onWarning: (message) => console.warn(message),
});Errors and breadcrumbs still work on React Native; only spans are missing. The
returned function carries tracingActive if you want to branch on it.
Keeping personal data out of BugWatch
Query keys routinely contain user ids, emails and search terms, and they are
recorded with every captured error. redactKey runs before anything leaves your
app:
instrumentQueryClientWithBugWatch(queryClient, {
redactKey: (key) =>
key.map((part) =>
typeof part === 'string' && part.includes('@') ? '[redacted]' : part,
),
});Mutation variables are never recorded by default, because that is where card numbers and passwords live. Opt in only where you know the payload is safe:
instrumentQueryClientWithBugWatch(queryClient, { includeMutationVariables: true });redactKey is applied to variables too when you do.
Not reporting expected failures
A 404 from a lookup is usually not a bug. shouldCapture decides:
instrumentQueryClientWithBugWatch(queryClient, {
shouldCapture: (error, context) => {
if (error instanceof HttpError && error.status === 404) return false;
if (context.kind === 'query' && context.operationName === 'presence') return false;
return true;
},
});Filtered errors still leave a breadcrumb, so the trail stays complete even when the error itself is not worth an issue.
Options
| Option | Default | Notes |
|---|---|---|
| captureQueryErrors | true | Report failed queries |
| captureMutationErrors | true | Report failed mutations |
| breadcrumbs | true | Record the lifecycle trail |
| tracing | true | Span per operation. Web only |
| redactKey | none | (key, kind) => key, applied before recording |
| shouldCapture | none | (error, context) => boolean |
| includeMutationVariables | false | Off because payloads carry secrets |
| maxKeyLength | 256 | Serialized keys are truncated past this |
| onWarning | none | Called when a requested feature is unavailable |
Using a custom target
instrumentQueryClient takes any object implementing BugWatchTarget, which is
useful for tests or for routing to your own sink:
import { instrumentQueryClient } from '@newinstance/bugwatch-tanstack';
instrumentQueryClient(queryClient, {
platform: 'web',
capabilities: { captureTags: true, tracing: false, richContext: true },
captureException: (error) => myReporter.report(error),
addBreadcrumb: (crumb) => myReporter.trail(crumb),
setContext: (key, data) => myReporter.context(key, data),
});It will not break your app
Every call into BugWatch is wrapped. If the SDK is missing, misconfigured or throws, your queries and mutations resolve and reject exactly as they would without this package. A monitoring tool that can take down the thing it monitors is worse than no monitoring, so that behaviour is covered by tests.
One limitation worth knowing: TanStack Query hashes query keys with
JSON.stringify, so a circular query key throws inside TanStack itself, before
this adapter is ever involved. Keys with Date, Map or undefined values are
handled.
License
MIT
