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.
| Level | What you see | When to reach for it |
|---|---|---|
'silent' | Nothing — no boot line, no requests, no errors | Tests; CLI scripts that don’t want any noise |
'debug' | Everything 'log' shows plus per-asset request lines (CSS, JS, images) and fallbacks | Investigating asset fetches or unmatched routes |
'log' | Adds chatty client-side hydration traces and other verbose detail | Debugging hydration / island lifecycle issues |
'info' | Boot line, page/api requests, file-change notifications, plus warnings and errors | Default in development |
'warn' | Slow requests, 5xx responses, deprecations, recoverable problems, plus errors | Default in production |
'error' | Only handler failures — logger.error calls and unhandled exceptions | Production 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 unlesslevel: 'log'or'debug'.logger.debug(dim) — high-volume, low-signal lines like asset requests and fallback routes. Off unlesslevel: '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.