structured_log_go_router
Читать на русском.
Where your go_router app goes —
every navigation, redirect and routing error — as a
structured_log entry, with the
route pattern next to the location.
Why
The first question about most bug reports is which screen was the user on, and how did they get there? The answer is usually missing: navigation happens inside the router, and a stack trace only shows the widget that crashed, not the path that led to it.
StructuredLogGoRouter listens to a GoRouter and writes an entry each
time it settles on a new location — go, push, pop, a deep link, the
browser's back button — with the matched route pattern and where the user
came from. Routing errors are logged too, and redirects once you wrap your
redirect. Next to the bloc entries from structured_log_bloc
and the http ones from structured_log_dio / structured_log_http_client,
this gives a bug report its context: the screen, then the state, then the
call that failed.
Features
What you see
- Every navigation —
go,push,pop, deep links and the browser's back button all count; a notification that does not change the location is not logged twice. - Pattern next to location —
/users/42is logged with its route/users/:idand route name, so screens group without parsing URLs. - Where the user came from —
previous_locationandprevious_routeon each navigation after the first. - Redirects and routing errors — wrap
redirectandonExceptionto log them too; the not-found page is logged without any wrapping. - Its own category — every entry carries
category: 'navigation'.
What stays out
- Tokens — token-like query parameters, and the OAuth
codea sign-in callback carries, are redacted, in the query and in an OAuth-style fragment (#access_token=...). - Route state —
extraand other state objects are never logged. - Screens you choose — a
filterleaves out the navigations to a route whose path itself is sensitive, and the redirects into or out of it.
What doesn't get in the way
- Attach to any router —
routeLog.attach(router), anddetach()to stop. - Never breaks navigation — a filter that throws costs the entry; a wrapped redirect's answer and exceptions reach the router unchanged, and a synchronous redirect stays synchronous.
- Follows reconfiguration — without an explicit logger it picks up a
later
StructlogConfiguration.configure.
Where it fits
structured_log_go_router is one of the integrations around
structured_log: a Flutter
package that depends on nothing but the core and go_router, and writes
through the sinks you have already configured. No server is needed: the
entries go to the console, a file, or the in-app log viewer
(structured_log_flutter
with a Material, Fluent or Cupertino skin), and — if you run the
self-hosted structured_log_server — to it through
structured_log_remote_sync,
where your team can follow one user's path through the app next to the
calls it made. More at
structured-log.openidealab.com.
Installation
Published on pub.dev as a pre-release:
dependencies:
go_router: ">=17.0.0 <19.0.0"
structured_log: ^0.3.0
structured_log_go_router: ^0.1.0-dev.3
Quick Start
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:structured_log/structured_log.dart';
import 'package:structured_log_go_router/structured_log_go_router.dart';
void main() {
StructlogConfiguration.configure(
sinks: [LogSink(name: 'console', output: coloredConsoleOutput)],
);
final routeLog = StructuredLogGoRouter();
final router = GoRouter(
routes: [/* ... */],
redirect: routeLog.redirect(authRedirect), // optional
);
routeLog.attach(router);
runApp(MaterialApp.router(routerConfig: router));
}
INFO: route_changed {"category":"navigation","location":"/","route":"/","route_name":"home"}
DEBUG: route_redirected {"category":"navigation","from":"/settings","to":"/login"}
INFO: route_changed {"category":"navigation","location":"/login","route":"/login","previous_location":"/","previous_route":"/"}
example/main.dart is a small app with three screens,
a redirect and a not-found page. The package has no platform folders of its
own, so run it as the lib/main.dart of any Flutter app that depends on
this package.
What gets logged
| Entry | When | Default level | Fields |
|---|---|---|---|
route_changed |
the router settled on a new location — go, push, pop, a deep link, the browser's back button |
info |
location, route (the pattern), route_name if the route has one; previous_location, previous_route after the first navigation |
route_redirected |
a redirect wrapped with routeLog.redirect(...) sent the navigation elsewhere |
debug |
from, to (to_route instead, when filter rejects the target) |
route_error |
a location matched no route, or routing failed | warning |
location, error |
route_error reaches the log in either of go_router's two error modes:
with errorBuilder/errorPageBuilder (or neither), attach sees the
router land on its error page; with onException, the router stays where
it was, so wrap the handler — onException: routeLog.onException(handler).
Only the router's own pages are seen. A dialog or bottom sheet opened with
showDialog/showModalBottomSheet is not a location and does not appear.
Keeping secrets out of the log
Locations are logged as they are — path parameters included, since that is what identifies the screen — with two exceptions:
- the values of
defaultRedactedQueryParameters—access_token,refresh_token,id_token,token,api_key,apikey,password,client_secret, andcode(the OAuth authorization code a sign-in callback carries) — becomeREDACTED, in the query and in a fragment shaped like one (#access_token=...). PassredactedQueryParametersto change the set (lower-case names; compared case-insensitively). The same goes for the locationsgo_routerquotes in an error's message (no routes for location: ..., the history of a redirect loop); extraand other route state objects are never logged.
If a path parameter itself is sensitive (an email in /invite/:email),
leave that route out with filter. Its location then appears in no
entry: the next navigation names it by its route alone
(previous_route: /invite/:email, no previous_location), a redirect that
starts there is not logged, and one that ends there names it by
to_route alone. The target of a redirect is judged by the state the
attached router builds for it; while no router is attached it cannot be
judged, so with a filter set to is left out and to_route is not known.
Routing errors are the exception: a location that matched no route has no
route to filter by, so route_error is not filtered (its location is still
redacted like any other).
Configuration
StructuredLogGoRouter(
// Which entries are logged, and at what level; null turns one off.
levels: const RouteLogLevels(navigation: LogLevel.debug),
// Leave a screen out — its navigations and the redirects into or out of it;
// routing errors stay.
filter: (state) => state.fullPath != '/invite/:email',
category: 'ui',
);
attach again to move to another router; detach before disposing the
router it is attached to.
API Reference
| Symbol | Description |
|---|---|
StructuredLogGoRouter({logger, loggerName, category, levels, redactedQueryParameters, filter}) |
The logger. Without logger it calls getLogger(loggerName) (router) on every entry, so a later StructlogConfiguration.configure reaches it too. category: null binds no category. |
attach(router) / detach() |
Start logging a router's navigations — including the location it is already on — or stop. |
redirect(inner) |
Wraps a top-level or route-level GoRouterRedirect, logging each redirect it makes; a synchronous redirect stays synchronous. |
onException(inner) |
Wraps a GoExceptionHandler, logging each routing error before handing it on. |
RouteLogLevels({navigation, redirect, error}) |
A LogLevel? per entry; null turns it off. |
defaultRedactedQueryParameters, redactedValue |
The default redaction set and the value (REDACTED) that replaces what it matches. |
Related packages
The rest of the structured_log family:
Core
structured_log— structured JSON logging with context binding, processors and multi-sink routing
In-app log viewer
structured_log_flutter— headless viewer core:LogBufferandLogViewerControllerstructured_log_material— Material 3 log viewerstructured_log_fluent— Fluent UI (WinUI-style) log viewerstructured_log_cupertino— Cupertino (iOS-style) log viewer
Shipping logs to a server
structured_log_remote_sync— batching, retrying sink for a self-hostedstructured_log_server
Integrations
structured_log_bloc—BlocObserverforbloc/flutter_blocstructured_log_dio—diointerceptor that logs HTTP callsstructured_log_http_client—package:httpclient wrapper that logs HTTP callsstructured_log_auto_route— logsauto_routenavigationstructured_log_cherrypick— observer for thecherrypickDI containerstructured_log_drift— driftQueryInterceptorthat logs queries, failures and transactionsstructured_log_logging— bridge that routespackage:loggingrecords intostructured_logstructured_log_riverpod—ProviderObserverforriverpod/flutter_riverpod
License
See LICENSE.
Libraries
- structured_log_go_router
- Logs what a
GoRouterdoes — navigations, redirects, routing errors — throughstructured_log.