Debug Logging
Routier plugins and the core library emit logs (logger.debug, logger.warn, logger.error and friends) for query lifecycle, sync operations, hydration, and errors. Logging is off unless you ask for it, apart from in a development environment. This guide explains how to control it.
Quick Navigation
- Levels
- Default Behavior
- Enabling Logging
- Disabling Logging
- Changing the level at runtime
- Vite Applications
- Related Topics
Levels
Each level includes everything above it in this table. silent discards everything.
| Level | Includes |
|---|---|
silent | nothing |
error | logger.error |
warn | + logger.warn |
info | + logger.info, logger.log |
debug | + logger.debug, logger.table |
Default Behavior
The level is resolved once, when Routier is first imported, from the first of these that applies:
| Precedence | Source | Result |
|---|---|---|
| 1 | globalThis.__ROUTIER_LOG_LEVEL__ | that level |
| 2 | globalThis.__ROUTIER_DEBUG__ === true / === false | debug / silent |
| 3 | process.env.ROUTIER_LOG_LEVEL | that level |
| 4 | process.env.DEBUG is routier or * | debug |
| 5 | process.env.NODE_ENV is dev or development | debug |
| 6 | nothing above applies | silent |
An unrecognised level name is ignored rather than throwing, and resolution continues down the table — a typo in configuration will not take an application down.
NODE_ENV=test does not enable logging
It used to. That meant every test suite running against Routier logged whether it wanted to or not, and the cost is not small: test runners capture console output by snapshotting a stack trace per call, so a suite driving a few thousand saves through a plugin that logs per query spent roughly half its wall clock on logging it never asked for. Set ROUTIER_LOG_LEVEL=debug when a test needs the output.
Enabling Logging
Option 1: Global override (all environments)
Set globalThis.__ROUTIER_DEBUG__ = true before any routier imports. This works in Node, browser, and Vite.
// At the very top of your entry file (main.ts, main.tsx, index.ts)
(globalThis as any).__ROUTIER_DEBUG__ = true;
import { DataStore } from "@routier/datastore";
// ... rest of your appOption 2: Node.js / bundlers with process.env
# Pick a level explicitly — silent, error, warn, info, debug
ROUTIER_LOG_LEVEL=debug npm run dev
# Keep only problems, in production
ROUTIER_LOG_LEVEL=warn npm start
# Development implies debug
NODE_ENV=development npm run dev
# Or use the conventional DEBUG variable
DEBUG=routier npm run dev
DEBUG=* npm run devOption 3: Vite
Routier is built with rspack, which replaces import.meta.env with undefined in the bundle, so Routier cannot read your app's import.meta.env — it would see its own build environment. In a Vite app, read import.meta.env yourself and set the global:
// main.tsx – must run before any routier imports.
// Routier cannot read your import.meta.env (it is bundled with rspack, which
// replaces import.meta), so translate it into the global here.
(globalThis as any).__ROUTIER_LOG_LEVEL__ = import.meta.env?.DEV ? "debug" : "silent";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";Disabling Logging
Logging is off by default. It is explicitly off when:
- nothing in the precedence table above applies (so: any environment other than development)
globalThis.__ROUTIER_DEBUG__ === falseglobalThis.__ROUTIER_LOG_LEVEL__orROUTIER_LOG_LEVELissilent
To force logging off even in a development environment, set:
// Forces logging off, overriding NODE_ENV and DEBUG.
(globalThis as any).__ROUTIER_DEBUG__ = false;This takes precedence over NODE_ENV and DEBUG, so it also silences a process started with DEBUG=*.
To keep errors but drop the rest, choose a level instead of switching off entirely:
(globalThis as any).__ROUTIER_LOG_LEVEL__ = "error";Changing the level at runtime
The precedence table is read once, at import, which is what keeps a suppressed log call cheap (about 3ns, against 70ns for one that is emitted). When verbosity is decided after startup — a debug toggle in a settings panel, or a test that wants to assert on output — use the API:
import { getLogLevel, setLogLevel } from "@routier/core/utilities";
setLogLevel("debug");
// ... later
setLogLevel("silent");
getLogLevel(); // "silent"setLogLevel throws on an unknown level, unlike the environment sources, because a bad literal at a call site is a programming error rather than a configuration mistake.
If you change an environment variable after Routier has been imported, call resetLogLevel() to re-read it.
Guarding an expensive payload
A suppressed call still evaluates its own arguments. That does not matter for an ordinary object — it costs a fraction of a nanosecond next to the call itself — but it does when building the payload is real work, such as serializing a large collection:
import { isLogLevelEnabled, logger } from "@routier/core/utilities";
if (isLogLevelEnabled("debug")) {
logger.debug("cache contents", JSON.stringify([...cache.entries()]));
}Vite Applications
For Vite apps, the global override is required because:
- Routier is pre-bundled with rspack, which replaces
import.meta.envwithundefined. - In the browser,
processis typically undefined, soprocess.envchecks do not run.
Add the override at the top of your entry file (before any routier imports):
(globalThis as any).__ROUTIER_LOG_LEVEL__ = import.meta.env?.DEV ? "debug" : "silent";See React Best Practices - Debug Logging in Vite for the same pattern in a React context.
Related Topics
- React Best Practices – Debug logging setup for Vite + React
- Optimistic Replication – Uses debug logs for hydration and sync
- Syncing – Uses debug logs for sync lifecycle