Skip to content

📋 Logging & Debug

⚙️ Logging Options

OptionTypeDefaultDescription
logboolean__DEV__Enable non-critical logs. Critical always logged.
loggerCitadelLoggercreateDefaultLogger()Custom logger implementation
debugbooleanfalseEnables logging + debugger breakpoints
debugHandlerDebugHandlercreateDefaultDebugHandler()Custom debug handler for breakpoints

__DEV__ is true when import.meta.env.DEV or NODE_ENV !== 'production'.

⚠️ Critical vs Non-Critical

  • Critical events — always logged via logger, regardless of log setting
  • Non-critical events — only logged when log: true (or debug: true)

This ensures developers always see errors even with log: false.

🔇 Disable Non-Critical Logging

typescript
createNavigationCitadel(router, { log: false });

🔧 Custom Logger

Logger Interface

typescript
interface CitadelLogger {
  info: (...args: unknown[]) => void;
  warn: (...args: unknown[]) => void;
  error: (...args: unknown[]) => void;
  debug: (...args: unknown[]) => void;
}

SSR with Pino

typescript
import pino from 'pino';

const pinoLogger = pino();

createNavigationCitadel(router, {
  logger: {
    info: (...args) => pinoLogger.info({ ctx: 'citadel' }, ...args),
    warn: (...args) => pinoLogger.warn({ ctx: 'citadel' }, ...args),
    error: (...args) => pinoLogger.error({ ctx: 'citadel' }, ...args),
    debug: (...args) => pinoLogger.debug({ ctx: 'citadel' }, ...args),
  },
});

Testing with vi.fn()

typescript
const mockLogger: CitadelLogger = {
  info: vi.fn(),
  warn: vi.fn(),
  error: vi.fn(),
  debug: vi.fn(),
};

const citadel = createNavigationCitadel(router, { logger: mockLogger });

// Assert logging calls
expect(mockLogger.info).toHaveBeenCalledWith('Deploying global outpost: auth');

Custom Format (No Emoji)

typescript
const plainLogger: CitadelLogger = {
  info: (...args) => console.log('[Citadel]', ...args),
  warn: (...args) => console.warn('[Citadel]', ...args),
  error: (...args) => console.error('[Citadel]', ...args),
  debug: (...args) => console.debug('[Citadel DEBUG]', ...args),
};

📊 Log Events Reference

EventMethodCritical
Hook start (only if outposts present)logger.infoNo
Processing outpostlogger.infoNo
Deploying outpostlogger.infoNo
Abandoning outpostlogger.infoNo
Assigned outposts to routelogger.infoNo
DevTools initializedlogger.infoNo
Destroying citadellogger.infoNo
Patrol stoppedlogger.warnNo
Duplicate outpostslogger.warnYes
Outpost not foundlogger.warnYes
Route not foundlogger.warnYes
Outpost timeoutlogger.warnYes
Outpost errorlogger.errorYes
afterEach patrol errorlogger.errorYes

Critical events are always logged via logger. Non-critical only when log: true.

Hook start is only logged when there are outposts to process. Hooks with no outposts return ALLOW silently without any logging.

🐛 Debug Reference

Named debug points with console output [DEBUG] <name> (all require debug: true):

NameLocation
navigation-startStart of patrol (only when outposts present for the hook)
outpost-enterBefore each outpost handler processing
outpost-blockWhen outpost returns BLOCK or redirect
outpost-timeoutWhen outpost handler times out
error-catchWhen outpost throws an error
devtools-initDevTools initialized (via install hook or existing app)
devtools-inspectDevTools inspector registered

navigation-start breakpoint only triggers when there are outposts to process for the current hook.

🔨 Custom Debug Handler

By default, debug points trigger debugger statements. However, bundlers like Vite/esbuild may strip debugger from dependencies during optimization. To ensure reliable breakpoints, provide a custom debugHandler:

typescript
const citadel = createNavigationCitadel(router, {
  debug: true,
  debugHandler: (name) => {
    console.trace(`Debug point: ${name}`);
    debugger; // You control this — add any debug logic here
  },
});

Alternative — use alert() for debug:

typescript
const citadel = createNavigationCitadel(router, {
  debug: true,
  debugHandler: (name) => {
    alert(name); // Blocks execution, works when debugger is stripped
  },
});

Use cases for custom debugHandler:

  • Reliable breakpointsdebugger in your code isn't stripped by bundlers
  • Conditional breakpoints — only break on specific debug points
  • Loggingconsole.trace() for stack traces without stopping
  • Testing — mock handler to verify debug points are triggered

Released under the MIT License.