React
A render error inside your own errorElement never reaches window. One onCaughtError line on createRoot logs it once.
An error boundary catches a render error before window sees it, so setupGlobalErrorHandler() alone never logs it. React Router’s default error page, every errorElement and every ErrorBoundary are boundaries. You hand the error to fsl where React catches it. This page assumes the client is initialized as in Get started.
Errors React catches
Section titled “Errors React catches”React 19 reports errors that a boundary caught through onCaughtError. Pass fsl’s handler:
import { createRoot } from 'react-dom/client'import { handleReactError } from '@dasasian/firebase-structured-logger/client'import { App } from './App'
createRoot(document.getElementById('root')!, { onCaughtError: handleReactError }).render(<App />)Each caught error is logged once as an ERROR with errorType ReactError and React’s component stack. When navigation is on, it also has the page labels.
Pass only onCaughtError. An error that no boundary catches already reaches window, where setupGlobalErrorHandler() logs it.
Before initLogger() has run, the handler logs nothing and says so once on the console. It never throws into React.
React 18, or a class boundary
Section titled “React 18, or a class boundary”Before React 19, createRoot has no onCaughtError. Call the same handler from the componentDidCatch of your own class boundary:
import { Component, type ErrorInfo, type ReactNode } from 'react'import { handleReactError } from '@dasasian/firebase-structured-logger/client'
export class ErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> { state = { failed: false }
static getDerivedStateFromError() { return { failed: true } }
componentDidCatch(error: Error, info: ErrorInfo) { handleReactError(error, info) }
render() { return this.state.failed ? <p>Something went wrong.</p> : this.props.children }}This logs only what that boundary catches. On React 19, use onCaughtError and leave componentDidCatch out, or the same error is written twice.
React Router
Section titled “React Router”With a data router (createBrowserRouter and friends), the adapter records each page change and logs route errors. It needs React Router 6.4 or later, or 7.
import { createBrowserRouter } from 'react-router'import { enableReactRouterNavigation } from '@dasasian/firebase-structured-logger/client/navigation/react-router'import { routes } from './routes'
export const router = createBrowserRouter(routes)
enableReactRouterNavigation(router)In React Router 6, import createBrowserRouter from react-router-dom. The fsl import is the same.
The adapter asks the router for each page’s pattern and name, so it does not guess from the path. It listens to the router’s own events and does not wrap history.
| Label | Value |
|---|---|
route |
the full pattern of the deepest match, such as /orders/:id/items, without the basename |
screen |
handle.screen of the deepest match that has one, otherwise the route |
path |
location.pathname, never the query or hash |
Name a screen on the route:
import { OrderItems } from './OrderItems'
export const routes = [ { path: '/orders/:id/items', element: <OrderItems />, handle: { screen: 'OrderItems' } },]One page change is one breadcrumb. A loader redirect() records only the page it ends on. A <Navigate> element records both the page that renders it and the page it ends on.
Route errors
Section titled “Route errors”A loader or action that throws, or returns an error response, puts the error in the router’s state. Neither window nor onCaughtError sees it there. The adapter logs each one once, after it records the page, so the entry has that page’s labels.
| What happened | Logged as |
|---|---|
a loader or action throws an Error |
ERROR |
| a 5xx response | ERROR |
| a 4xx response, a 404 included | WARNING |
All of them have errorType RouteError. A redirect() is a page change and never an error. Because 4xx responses are warnings, they stay out of Cloud Error Reporting.
<BrowserRouter> has no loaders, so there is nothing for the adapter to log. Use enableNavigation() for breadcrumbs there.
Clean personal data from paths
Section titled “Clean personal data from paths”adjust receives the router’s labels and returns the ones to log:
enableReactRouterNavigation(router, { adjust: (labels) => ({ ...labels, path: labels.path?.replace(/[^/]+@[^/]+/g, ':email') }),})The query string never leaves the browser. A fragment is dropped unless it is a route.
Stop an adapter
Section titled “Stop an adapter”The adapter returns a function that stops it, route errors included. Calling an adapter a second time stops the first. If you use both an adapter and enableNavigation(), the adapter wins and the console says so once.
React Router is not a dependency of this package. The adapter only reads the router you pass in.
Made by Dasasian