Logging

Fire Tools uses a single structured logger across the frontend and the planned backend. Logs stay on the user's device. They are only shared when the user explicitly exports them through Settings.

Format

Every log entry is rendered as a single line:

[YYYY-MM-DD HH:MM:SS] [section] [actor] [event]: message

Example:

[2025-01-15 10:42:03] [dca] [system] [price-fetch-failed]: failed to fetch latest price

PII gating (the core privacy rule)

The logger has an off-by-default flag that controls whether personally identifiable / financial information is written to the log line.

When the flag is off (default), any data passed in opts.pii is dropped before the entry is buffered or written. The base log line — timestamp, section, actor, event, generic message — is always recorded, so support and debugging still work without seeing the user's portfolio.

When the flag is on, opts.pii is serialized and appended. The Settings page shows a red warning while the flag is on so the user knows their next log export will contain financial details.

API

Import the logger:

// Frontend
import { logger } from '../utils/logger';

// Backend
import { logger } from './logger.js';

Use one of the convenience methods:

logger.userAction('settings', 'export-clicked', 'user exported settings');
logger.systemEvent('fire-calculator', 'recalculated', 'recomputed projection');
logger.warn('exchange-rate', 'rate-stale', 'cached rate older than 24h');
logger.error('dca', 'price-fetch-failed', 'failed to fetch latest price', {
  pii: { ticker, error: (err as Error)?.message },
});
logger.debug('cli-migrate', 'sql-applied', 'ran migration', { pii: { sql } });

The shape of opts:

interface LogOptions {
  pii?: unknown; // Dropped from output when the PII flag is off.
}

Migration pattern

Replace console.* calls during migration:

// Before
console.error('Failed to fetch price for', ticker, err);

// After
logger.error('dca', 'price-fetch-failed', 'failed to fetch price', {
  pii: { ticker, error: (err as Error)?.message },
});

Rules of thumb:

Export for bug reports

Settings → Privacy & Region → Export diagnostic logs writes the in-memory log buffer to a fire-tools-logs-<timestamp>.log file using the same line format. The buffer holds up to 1000 entries.

The export reflects the current PII flag state:

There is no telemetry or network upload. The file is generated and downloaded entirely client-side.

Enforcement

tests/shared/logger.test.ts covers the logger itself.

tests/shared/no-console.test.ts is a guardrail test: it scans src/ and server/src/ and fails the build if it finds direct console.* calls outside of the logger sources and test files. New code must go through the logger.

See also