🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

Logging

Mochi exposes a single isomorphic logger with five standard methods. The same import works on the server, in SSR, in hydrated Svelte components, and in vanilla web components — and it’s gated by a configurable level set on Mochi.serve().

import { logger } from 'mochi-framework';

logger.error('boom');
logger.warn('careful');
logger.info('starting up');
logger.log('verbose detail');
logger.debug('asset request');

Methods map to console.error / console.warn / console.info / console.log / console.debug. Each line is prefixed with a coloured [mochi]. Calls below the configured level are no-ops with negligible overhead.

Log level

import { Mochi } from 'mochi-framework';

await Mochi.serve({
  port: 3333,
  routes,
  logger: { level: 'warn' },
});

level accepts 'silent' | 'error' | 'warn' | 'info' | 'log' | 'debug'. A method runs when its severity is at or above the active level, so 'warn' lets error and warn through while suppressing info, log, and debug.

LevelWhat you seeWhen to reach for it
'silent'Nothing — no boot line, no requests, no errorsTests; CLI scripts that don’t want any noise
'debug'Everything 'log' shows plus per-asset request lines (CSS, JS, images) and fallbacksInvestigating asset fetches or unmatched routes
'log'Adds chatty client-side hydration traces and other verbose detailDebugging hydration / island lifecycle issues
'info'Boot line, page/api requests, file-change notifications, plus warnings and errorsDefault in development
'warn'Slow requests, 5xx responses, deprecations, recoverable problems, plus errorsDefault in production
'error'Only handler failures — logger.error calls and unhandled exceptionsProduction with a separate alerting pipeline

If level is omitted, the default is picked from the development flag you pass to Mochi.serve():

Mochi.serve({ development })Default level
true'info'
false (or omitted)'warn'

A common pattern is to drive development from an env var so the same index.ts handles both:

await Mochi.serve({
  development: process.env.MODE === 'development',
  routes,
});

The level applies on both sides: the server captures it once at startup, and the same value is shipped to the browser via a tiny inline script so client-side logger calls (e.g. inside hydrated islands) honour it too. Reload the page after changing the config to pick up a new level on the client.

Method semantics

  • logger.error (red) — failures the operator should see.
  • logger.warn (yellow) — slow requests, 5xx responses, deprecations, recoverable problems.
  • logger.info (green) — boot, page/api request lines, file-change notifications.
  • logger.log (dim) — verbose / trace output, e.g. hydration lifecycle. Off unless level: 'log' or 'debug'.
  • logger.debug (dim) — high-volume, low-signal lines like asset requests and fallback routes. Off unless level: 'debug'.

Setting the level at runtime

import { setLogLevel, getLogLevel } from 'mochi-framework';

setLogLevel('error');
getLogLevel(); // 'error'

setLogLevel is exported for niche cases — toggling verbosity from a feature flag, lifting silence inside a debugging route. The serve-time config is the right place for normal use.

Relationship to mochiEvents

The event bus (mochiEvents) is a separate concern: it carries structured payloads to any subscriber you wire up, regardless of console output. The built-in consoleLogger() — the thing that prints request lines like GET /foo 200 12ms — is just one consumer subscribing to those events and calling logger.info / logger.warn per event. Plug Sentry, OpenTelemetry, or your own pipeline directly into mochiEvents; use logger for ad-hoc messages.