console logger · zero dependencies
Loud in dev.
Silent in prod.
Styled, scoped output while you build. Nothing once you ship, unless you tell a server to speak. No flags, no build step.
Flip the switch. Silence is the default — servers can ask for an exception.
levels
Five levels, five colors
Each level maps to its matching console method, so devtools filtering and stack traces keep working. Color is the only thing hushlog adds.
log()
General output
log.info()
Informational messages
log.warn()
Warnings, deprecations
log.error()
Errors, failures
log.success()
Confirmations, completed actions
import { log } from "hushlog"; log("Fetching user:", id); log.warn("Token expiring soon"); log.success("User saved");
scopes
Name the source once
logScope returns a logger with all five levels attached
and a prefix baked in, so you never hand-write the module name into a
message again.
import { logScope } from "hushlog"; const log = logScope("auth"); log("User signed in:", user); log.error("Invalid credentials");
filters
Narrow the noise at runtime
setLogFilter decides which prefixes speak, without
touching a line of code. Unprefixed logs from the default
log always come through — until you silence everything.
groups
Keep a sequence together
logGroup wraps related output in a collapsible block. It
is async-aware: the group stays open until your callback resolves, and
closes even if the callback throws.
await logGroup("Load config", async () => { const config = await fetchConfig(); log.success("Config loaded:", config); }, { collapsed: false });
server logs
Production output, terminal only
Server-rendered apps often want production logs where a hosted
collector can read them, while the browser stays silent. Mark a scope
target: "server" and it survives into production on the
server — and only there.
const dbLog = logScope("db", { target: "server" }); const uiLog = logScope("ui"); // dev only, the default dbLog.error("Query failed:", err); // dev, and production stdout uiLog("Modal opened"); // dev only
This one is pinned to production, so the switch at the top of the page
does not reach it. [ui] is still in the code either way
— it just never gets here.
Two switches
The deploy sets HUSHLOG_SERVER=1; the code sets
target. Both must agree, so ops can silence
production without a code change.
The browser never sees it
HUSHLOG_SERVER is read from
process.env only, so bundlers inline it as
undefined in client builds. Workers count as browser.
Change it without touching code
setServerLogScopes("db", "auth") overrides every
target at once — process-wide, so set it at boot rather than
per request. setLogFilter still applies on top.
Before you turn it on
Production output leaves the box for your log store, and nothing is tree-shaken — keep secrets out either way.
environment
It works out what dev means
hushlog checks for development in the order your tooling is likely to report it, then falls back to the host it is running on.
Bundlers
Vite reads import.meta.env.DEV. Next.js, webpack, Bun
and Node read process.env.NODE_ENV.
Deno
Reads NODE_ENV when env access is granted, and stays
quiet rather than throwing when it is not.
Plain browsers & workers
With no bundler in the picture, a
localhost hostname counts as development.
Terminals
Devtools get %c styling; Node TTYs get ANSI color and
honour NO_COLOR.
reference
The whole API
log(message?, ...args)
Default logger. .info, .warn,
.error and .success share the signature.
logScope(prefix, options?)
Returns a logger that tags every message with
[prefix]. target is
"dev" or "server", default
"dev".
setLogFilter(...prefixes)
Pass prefixes to allow, "*" for everything, or
null to silence output entirely.
setServerLogScopes(...prefixes)
Overrides every logger's target, choosing which
prefixes reach production server output. No arguments clears it.
logGroup(label, callback, options?)
Groups the callback's output. Options are
collapsed, prefix and
target. Returns the callback's result.
// Named and default imports both work import log, { logScope, logGroup, setLogFilter } from "hushlog";
Ships as ESM and CommonJS with hand-written TypeScript types. No dependencies.