Beautiful logging experience for TypeScript
Powerful, fast and expressive logging for TypeScript and JavaScript
Note
This is tslog v5. It is a deliberate, breaking redesign โ ESM-only, grouped settings, a new fields-first JSON shape, and middleware instead of overwrite.*. If you are on the 4.x line, 4.11.0 is a safe place to stay โ most of the v5 performance wins were back-ported there with zero breaking changes. Upgrade when you want the new capabilities. See Upgrading from v4?.
Highlights
- ๐ Universal โ one logger for Node.js, browser, Deno, Bun, workers and React Native
- ๐งฑ Structured, fields-first JSON โ flat, observability-ready output that drops straight into log pipelines
- ๐งญ Pretty by default, JSON optional โ colored in your terminal, uncolored when piped/CI; structured JSON is one opt-in away
- ๐ค First-class support for agents & LLMs โ fields-first calls, agent/session correlation, an
llms.txt, and OTel-GenAI presets; used by OpenClaw for agent logging - ๐ณ Tree-shakeable subpaths โ transports, presets and helpers ship as opt-in modules,
sideEffects: false - ๐ชถ Zero runtime dependencies โ nothing pulled into your bundle but
tslogitself - ๐ฎ Fully typed โ written in TypeScript 7, native ESM, accurate source-mapped line numbers
- ๐ Secret masking โ keys, JSONPath-lite paths, regex, and a hashing censor for correlation
- ๐จโ๐งโ๐ฆ Sub-loggers with inheritance โ
child()/getSubLogger()with merged settings and accumulated names - ๐ Pluggable transports & middleware โ per-transport level/format, a
use()pipeline,flush()and disposal - ๐ฅ Interactive browser objects โ
pretty.passObjectsNatively(on by default in browsers) keeps logged objects collapsible in DevTools - ๐ค Pretty errors & stack traces โ structured, fully serializable, captured only when needed (
"auto"for pretty,"off"for JSON)
Example
import { Logger } from "tslog"; // `new Logger()` is pretty everywhere: colorized in an // interactive terminal, uncolored when piped/redirected/CI. // Omit `type` for pretty; set "pretty" | "json" | "hidden". const log = new Logger({ minLevel: "INFO" }); // Fields-first OR string-first โ both work: log.info({ port: 3000 }, "server started"); log.info("server started"); // A child logger per request or agent โ name, settings and // fields are inherited. `child(...)` aliases `getSubLogger(...)`: const requestLog = log.getSubLogger({ name: "agent:planner" }); requestLog.info({ tool: "search", tokens: 318 }, "tool call done"); // JSON โ {"message":"tool call done","level":"INFO","levelId":3, // "time":"โฆ","tool":"search","tokens":318, // "_logMeta":{"v":5,"name":"agent:planner",โฆ}} // Keep secrets, PII and prompts out of your logs (grouped under `mask`): const safeLog = new Logger({ type: "json", mask: { keys: ["password", "apiKey", "token", "prompt"], paths: ["user.password", "*.token"], }, }); safeLog.info({ user: { name: "Ada", password: "hunter2" } }); // โ {"user":{"name":"Ada","password":"[***]"},"level":"INFO", โฆ} (a lone object spreads its fields โ no message key)
Become a Sponsor
Donations help me allocate more time for my open source work.
Install
tslog is published to npm as a single ESM package. How you pull it in depends on the runtime โ npm for Node.js, bun add for Bun, an npm: specifier (or import map) for Deno, and a CDN URL for the browser:
| Runtime | Install / import |
|---|---|
| Node.js | npm install tslog โ import { Logger } from "tslog"; |
| Bun | bun add tslog โ import { Logger } from "tslog"; |
| Deno | no install step โ import { Logger } from "npm:tslog"; |
| Browser | no install step โ import { Logger } from "https://esm.sh/tslog"; |
The per-runtime details are below.
Important
tslog v5 is ESM-only and requires Node.js โฅ 20. There is no CommonJS build and no require("tslog"). If you cannot move to ESM or off Node 16/18 yet, stay on tslog@4.11.0 โ it keeps CJS, Node 16+ and the v4 JSON shape. See Upgrading from v4?.
Node.js
npm install tslog
Set "type": "module" in your package.json and run Node with source maps for accurate line numbers:
{ "name": "NAME", "version": "1.0.0", "type": "module", "scripts": { "build": "tsc -p .", "start": "node --enable-source-maps dist/index.js" }, "dependencies": { "tslog": "^5" } }
After building (npm run build), start your app with npm start.
To run TypeScript directly, use a current ESM-aware runner, e.g. node --enable-source-maps --import tsx src/index.ts.
Deno
There is no install step in Deno โ the npm: specifier pulls tslog from npm and caches it on first run (or add it to an import map / deno add npm:tslog if you prefer a bare "tslog" import):
// main.ts import { Logger } from "npm:tslog"; const logger = new Logger(); logger.info("Hello from Deno");
deno run main.ts
# grant optional metadata access: deno run --allow-env main.tsBun
Add the package with Bun's own installer, then import it by name:
bun add tslog
// main.ts import { Logger } from "tslog"; const logger = new Logger(); logger.info("Hello from Bun");
bun run main.ts
Browser
<script type="module"> import { Logger } from "https://esm.sh/tslog"; const logger = new Logger(); logger.silly("I am a silly log."); </script>
A prebuilt IIFE bundle is also published for <script src="tslog.js"> usage, exposing the global window.tslog. In the browser, the default output renders pretty logs with CSS styling.
Bundle-size sensitive? import { Logger } from "tslog/slim" ships the same structured-JSON pipeline at less than half the size (~9.8KB gzip vs ~20.7KB) by leaving out masking, pretty output, and stack capture โ mask settings and type: "pretty" throw there instead of silently degrading. Both sizes are enforced by a CI budget (npm run check-bundle-size).
Enable TypeScript source-map support so tslog can point at the correct line in your source:
