@khanprog/logger
v0.5.0
Published
Universal colored console logger for Node.js and the browser (ESM, CJS, TypeScript)
Maintainers
Readme
@khanprog/logger
A lightweight universal console logger for Node.js and the browser, with ESM / CommonJS / TypeScript support.
Works with pnpm, npm, yarn, and bun. Dual Node + browser builds with full TypeScript declarations.
Table of contents
- Why @khanprog/logger?
- Install
- Recommended usage
- Core levels
- Scoping
- Semantic helpers
- Timing & utils
- Config
- Formats & colors
- Compat
- License
Why @khanprog/logger?
👌 Easy to use — createLogger() and go
💅 Colored pretty output with icons, plus machine-readable JSON mode
🌐 Universal — Node.js and the browser (dual builds)
📘 TypeScript-first with full declarations (ESM + CJS)
🏷 Tag / context support via withTag and child
✨ Semantic DX helpers — success, fail, start, ready
🎚️ Level filtering (debug → error) with env override (KP_LOG_LEVEL)
🎨 Themeable icons and colors (named or raw ANSI)
📄 Project config via kp-logger.json / .js / .ts (or in-memory config)
⏱️ Built-in timeIt timing, circular-safe safeStringify, and measureSize helpers
🪶 Zero runtime dependencies
Every log line includes an ISO timestamp (
[2024-06-06T12:34:56.789Z]). Example times below are illustrative. Examples usecolors: falseso output is easy to read.
Install
pnpm add @khanprog/logger
# or: npm / yarn / bunRecommended usage
Prefer createLogger for application code. Use a bound context (or withTag) for module scope, core levels for severity, and semantic helpers for DX milestones.
import { createLogger } from "@khanprog/logger";
const log = createLogger({
context: "App",
level: "info",
colors: false,
});
log.info("Server started");
log.warn("Cache miss", { meta: { key: "user:1" } });
log.error("Request failed", { meta: new Error("timeout") });
log.success("Deploy complete");
log.fail("Migration aborted");
log.start("Building…");
log.ready("Listening on :3000");
const auth = log.withTag("Auth"); // → [App:Auth]
auth.info("token verified", { meta: { userId: 1 } });
const done = log.timeIt("boot", { level: "debug" });
done();Output:
[2024-06-06T12:34:56.789Z] ℹ️ [INFO] [App] Server started
[2024-06-06T12:34:56.790Z] ⚠️ [WARN] [App] Cache miss {"key":"user:1"}
[2024-06-06T12:34:56.791Z] ❌ [ERROR] [App] Request failed {"__type":"Error","name":"Error","message":"timeout","stack":"..."}
[2024-06-06T12:34:56.792Z] ✅ [SUCCESS] [App] Deploy complete
[2024-06-06T12:34:56.793Z] 💥 [FAIL] [App] Migration aborted
[2024-06-06T12:34:56.794Z] 🚀 [START] [App] Building…
[2024-06-06T12:34:56.795Z] ✨ [READY] [App] Listening on :3000
[2024-06-06T12:34:56.796Z] ℹ️ [INFO] [App:Auth] token verified {"userId":1}
[2024-06-06T12:34:56.797Z] 🔍 [DEBUG] [App] 🚀 ~ Execution time for "boot": 0.420msFrontend & backend
| Environment | How to configure |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Browser / Vite / Next / CRA | Pass config or options to createLogger() (no filesystem). Bundlers resolve the browser build automatically. |
| Node.js / Bun | Auto-load kp-logger.ts / .js / .json, or pass config / options the same way. |
import { createLogger } from "@khanprog/logger";
import config from "./kp-logger.json";
const log = createLogger({ config, colors: false });
log.info("hello");Core levels
Rank order: debug < log < info < warn < error.
Methods: .debug, .log, .info, .warn, .error.
Each method: (message?: unknown, fields?: LogFields).
Arguments
| Call | Meaning |
|------|---------|
| log.info(msg) | message only |
| log.info(msg, { meta }) | message + structured data |
| log.info(msg, { context }) | one-off context override |
| log.info(msg, { meta, context }) | both |
| log.withTag(tag).info(msg, { meta }) | preferred scoping |
const log = createLogger({ colors: false, configFile: false });
log.debug("detail");
log.log("plain");
log.info("ok");
log.warn("careful");
log.error("failed");[2024-06-06T12:34:56.789Z] 🔍 [DEBUG] detail
[2024-06-06T12:34:56.790Z] 📝 [LOG] plain
[2024-06-06T12:34:56.791Z] ℹ️ [INFO] ok
[2024-06-06T12:34:56.792Z] ⚠️ [WARN] careful
[2024-06-06T12:34:56.793Z] ❌ [ERROR] failedLevel filtering
const log = createLogger({ level: "warn", colors: false, configFile: false });
log.debug("nope"); // skipped
log.info("nope"); // skipped
log.warn("yes");
log.error("yes");Scoping
Bound context
const log = createLogger({ context: "Auth", colors: false, configFile: false });
log.info("token verified");
// → ℹ️ [INFO] [Auth] token verifiedwithTag (recommended) and child
withTag(tag) is an alias for child({ context: tag }). Contexts join with :.
const app = createLogger({ context: "App", colors: false, configFile: false });
const auth = app.withTag("Auth");
auth.info("ok");
// → ℹ️ [INFO] [App:Auth] ok
// Full options escape hatch:
const login = auth.child({ context: "Login", level: "debug" });Per-call context override
Prefer withTag for ongoing scope. Use fields.context for a one-off override:
const log = createLogger({ context: "Auth", colors: false, configFile: false });
log.info("ok", { context: "Login" });
// → ℹ️ [INFO] [Login] okStructured meta
Pass structured data under fields.meta (never as a bare second object):
log.info("user created", { meta: { userId: 42, role: "admin" } });
// → ℹ️ [INFO] user created {"userId":42,"role":"admin"}
log.info("ok", { meta: { userId: 1 }, context: "Auth" });
// → ℹ️ [INFO] [Auth] ok {"userId":1}Semantic helpers
Semantic DX methods. They keep lean filter levels (LogLevel is unchanged) and use distinct icons/labels.
| Method | Filters as | Label |
| --------- | ---------- | --------- |
| success | info | ✅ SUCCESS |
| fail | error | 💥 FAIL |
| start | info | 🚀 START |
| ready | info | ✨ READY |
In JSON mode, output includes both "level" (filter rank) and "kind" (success | fail | start | ready).
const log = createLogger({ format: "json", configFile: false });
log.success("done");
// → {"time":"...","level":"info","kind":"success","message":"done"}Timing & utils
timeIt
Browser-safe timing via performance.now() (falls back to Date.now()). Options are named: { context?, level? }. Default level: debug.
const log = createLogger({ colors: false, configFile: false });
const done = log.timeIt("fetchData");
// ... work ...
done();
// → 🔍 [DEBUG] 🚀 ~ Execution time for "fetchData": 12.345ms
const done2 = log.timeIt("fetchData", {
context: "UserService",
level: "warn",
});
done2();
// → ⚠️ [WARN] [UserService] 🚀 ~ Execution time for "fetchData": 12.345msNamed export (uses the package default logger):
import { timeIt } from "@khanprog/logger";
const done = timeIt("boot");
done();safeStringify
Circular-safe JSON helper (Error, Map, Set, Date, BigInt, functions, typed arrays).
import { safeStringify } from "@khanprog/logger";
const obj: Record<string, unknown> = { name: "kp" };
obj.self = obj;
console.log(safeStringify(obj, 0));
// → {"name":"kp","self":"[Circular]"}measureSize
Measure the byte size of any value — plain text, binary, or structured data.
| Input | How size is measured |
| --- | --- |
| string | UTF-8 length as-is (no JSON quotes) |
| ArrayBuffer / typed arrays / Buffer | .byteLength |
| primitives | UTF-8 length of String(value) |
| objects / arrays | Compact safeStringify, then UTF-8 length |
import { createLogger, formatBytes, isWithinSizeLimit, measureSize } from "@khanprog/logger";
const body = { foo: "bar" };
const size = measureSize(body);
// → { bytes: 13, kb: "0.01", mb: "0.00" }
measureSize("hello").bytes; // 5 (not JSON-quoted 7)
measureSize(new Uint8Array(4)).bytes; // 4
formatBytes(size.bytes); // "13 B"
isWithinSizeLimit(body, 1024 * 1024); // true if <= 1 MB
const log = createLogger({ colors: false, configFile: false });
log.info("response received", {
meta: { size: formatBytes(size.bytes) },
});Config
createLogger(options?)
| Option | Type | Default | Description |
| ------------- | ----------------------------------- | ------------------ | -------------------------------------------------------------------------------- |
| level | LogLevel | "debug" | Minimum level (debug < log < info < warn < error) |
| context | string | "" | Bound context label on every line |
| colors | "auto" | boolean | "auto" | ANSI colors (NO_COLOR / FORCE_COLOR / TTY aware; off in browsers by default) |
| format | "pretty" | "json" | "pretty" | Human or machine-readable output |
| icons | Partial<Record<LogLevel, string>> | built-ins | Override per-level icons |
| levelColors | Partial<Record<LogLevel, string>> | built-ins | Override per-level colors (named or ANSI) |
| config | KpLoggerFileConfig | — | In-memory config (browser + Node) |
| configFile | string | false | auto kp-logger.* | Filesystem path (Node only), or false to skip |
Merge order: built-in defaults → kp-logger.* file (Node) → config object → createLogger fields.
Config files (kp-logger.json / .js / .ts)
Browser: import the file and pass it via config.
Node: place the file in the project root (or a parent). Discovery order:
kp-logger.ts → .mts → .cts → .js → .mjs → .cjs → .json
TypeScript ([kp-logger.example.ts](kp-logger.example.ts)):
import { defineConfig } from "@khanprog/logger";
export default defineConfig({
level: "info",
icons: { info: "💙", error: "🔥" },
levelColors: { info: "blue", error: "brightRed" },
});JSON ([kp-logger.example.json](kp-logger.example.json)):
{
"level": "info",
"colors": "auto",
"format": "pretty",
"context": "App",
"icons": {
"info": "💙",
"warn": "🧡",
"error": "🔥",
"debug": "🐛",
"log": "📄"
},
"levelColors": {
"info": "blue",
"warn": "yellow",
"error": "brightRed",
"debug": "magenta",
"log": "white"
}
}Named colors: black, red, green, yellow, blue, magenta, cyan, white, gray, plus brightRed, brightCyan, etc. Raw ANSI such as "\\x1b[36m" also works.
On Node, .ts configs need a TypeScript loader (e.g. tsx) or Node type-stripping. Prefer .json / .cjs for plain node.
Formats & colors
Pretty (default)
[2024-06-06T12:34:56.789Z] ℹ️ [INFO] [App] Server startedJSON
const log = createLogger({ format: "json", context: "API", configFile: false });
log.info("handled", { meta: { status: 200 } });{"time":"2024-06-06T12:34:56.789Z","level":"info","message":"handled","context":"API","meta":{"status":200}}Color behavior
| Condition | Result |
| ----------------- | --------- |
| colors: false | Off |
| colors: true | On |
| NO_COLOR set | Off |
| FORCE_COLOR set | On |
| Node TTY | On (auto) |
| Browser (auto) | Off |
JSON format never uses ANSI colors.
Compat
Secondary API for quick scripts. Prefer createLogger for new application code.
import logger, { logger as log } from "@khanprog/logger";
logger("info", "hello", { context: "App" });
logger.info("also works");
log.success("named export");
logger.child({ context: "X" }).warn("hi");Env: set KP_LOG_LEVEL (e.g. warn) to raise the default logger’s minimum level.
KP_LOG_LEVEL=warn node app.jsCommonJS
const { createLogger, logger, measureSize, safeStringify } = require("@khanprog/logger");
const log = createLogger({ colors: false, configFile: false });
log.info("from cjs");Types
import type {
ByteSize,
LogLevel,
LogKind,
LogFields,
TimeItOptions,
Logger,
LoggerOptions,
} from "@khanprog/logger";License
MIT
