Skip to main content

logtape

Use this skill when writing any code that uses LogTape for logging in JavaScript or TypeScript. Covers getting loggers, the structured message syntax, configuration, library author rules, context, lazy evaluation, testing, and common mistakes to avoid. Trigger whenever the user is adding logging to a project, debugging log output, or integrating LogTape with a framework.

Jump to install

Source facts

Repository
dahlia/logtape
Last source activity
July 2, 2026 at 06:35
Detected SKILL.md language
English
Stars
1,997
Forks
55

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
logtape
description
Use this skill when writing any code that uses LogTape for logging in JavaScript or TypeScript. Covers getting loggers, the structured message syntax, configuration, library author rules, context, lazy evaluation, testing, and common mistakes to avoid. Trigger whenever the user is adding logging to a project, debugging log output, or integrating LogTape with a framework.
license
MIT
LogTape skill for AI coding assistants ====================================== LogTape is a zero-dependency, library-first logging framework for JavaScript and TypeScript that works across Deno, Node.js, Bun, browsers, and edge functions. Full documentation: <https://logtape.org/> Getting a logger ---------------- Always use `getLogger()` with an **array** category to enable hierarchical filtering. The hierarchy works like a path: a parent category's configuration applies to all children. ~~~~ typescript import { getLogger } from "@logtape/logtape"; // Good: array form enables hierarchical filtering const logger = getLogger(["my-app", "users", "auth"]); // Acceptable shorthand for a single-segment category const rootLogger = getLogger("my-app"); ~~~~ Choose category segments that reflect your module structure so that operators can selectively enable/disable logging per subsystem. Use `logger.getChild("sub")` to derive a child logger without repeating the full category: ~~~~ typescript const dbLogger = logger.getChild("database"); // category = ["my-app", "users", "auth", "database"] ~~~~ See <https://logtape.org/manual/categories.md> for details. Structured messages ------------------- Use **named placeholders** with a properties object. This keeps messages parseable and properties searchable: ~~~~ typescript // Correct: structured message with named placeholders logger.info("User {userId} logged in from {ip}", { userId, ip }); // Correct: structured data without a message logger.info({ userId, ip, action: "login" }); // Nested property access (since 1.2.0) logger.info("Name: {user.name}", { user: { name: "Alice" } }); ~~~~ Template literal syntax is available for quick debug logging but does **not** produce structured data: ~~~~ typescript // Template literal: convenient but not structured logger.debug`User ${userId} logged in`; ~~~~ See <https://logtape.org/manual/struct.md> for full structured logging details. Severity levels --------------- LogTape provides six levels, from most to least verbose: | Level | Use for | | --------- | ----------------------------------------------------- | | `trace` | Very fine-grained diagnostic output | | `debug` | Developer-facing diagnostic messages | | `info` | Normal operational events (startup, shutdown, etc.) | | `warning` | Unexpected but recoverable situations | | `error` | Errors that affect a single operation | | `fatal` | Unrecoverable errors that require process termination | Use the lowest appropriate level. Reserve `error`/`fatal` for actual failures; avoid using them for expected conditions like validation errors. See <https://logtape.org/manual/levels.md> for details. Configuration ------------- ### Async configuration (most common) `configure()` is **application-only**. It must be `await`ed and called **exactly once** at startup (e.g., in your entry point): ~~~~ typescript import { configure, getConsoleSink } from "@logtape/logtape"; await configure({ sinks: { console: getConsoleSink(), }, loggers: [ { category: "my-app", lowestLevel: "debug", sinks: ["console"], }, ], }); ~~~~ ### Synchronous configuration Use `configureSync()` when you cannot use `await` (e.g., top-level in CommonJS, or in a synchronous startup path): ~~~~ typescript import { configureSync, getConsoleSink } from "@logtape/logtape"; configureSync({ sinks: { console: getConsoleSink(), }, loggers: [ { category: "my-app", lowestLevel: "debug", sinks: ["console"], }, ], }); ~~~~ > **Limitation:** `configureSync()` cannot use `AsyncDisposable` sinks such as > `getStreamSink()` or sinks created with `fromAsyncSink()`. ### Key rules - `configure()` returns a `Promise`; always `await` it. - `configureSync()` returns `void`; do not `await` it. - Call either one **once**. Calling again without resetting first throws `ConfigError`. - Do **not** mix async and sync: if you used `configure()`, reset with `await reset()`; if you used `configureSync()`, reset with `resetSync()`. - For tests, call `await reset()` (or `resetSync()`) in teardown. ### Framework-specific patterns - **React**: configure **before** `createRoot()`. - **Vue**: configure **before** `app.mount()`. - **Next.js**: use *instrumentation.js* (server) or *instrumentation-client.js* (client). - **SvelteKit**: configure in *hooks.server.ts*. See <https://logtape.org/manual/config.md> for all options. Library author rule ------------------- **Never call `configure()` or `configureSync()` in library code.** Libraries should only call `getLogger()` and log messages. The application that depends on your library decides how (or whether) to configure sinks and levels. ~~~~ typescript // my-lib/src/client.ts — library code import { getLogger } from "@logtape/logtape"; // Good: just get a logger, don't configure const logger = getLogger(["my-lib", "client"]); export function fetchData(url: string) { logger.debug("Fetching {url}", { url }); // ... } ~~~~ If your library wraps other LogTape-using libraries, use `withCategoryPrefix()` to nest their logs under your category: ~~~~ typescript import { withCategoryPrefix } from "@logtape/logtape"; export function myOperation() { return withCategoryPrefix(["my-lib"], () => { // Logs from inner libraries appear as ["my-lib", ...their-category] innerLib.doWork(); }); } ~~~~ > **Note:** `withCategoryPrefix()` requires `contextLocalStorage` to be > configured by the application. See <https://logtape.org/manual/library.md> for the full guide. Context with `with()` and lazy evaluation ----------------------------------------- ### Adding explicit context Use `logger.with()` to create a child logger that attaches properties to every subsequent log call: ~~~~ typescript const reqLogger = logger.with({ requestId, userId }); reqLogger.info("Processing order {orderId}", { orderId }); // Log record will contain requestId, userId, AND orderId ~~~~ ### Implicit context (request tracing) Use `withContext()` to propagate context across an entire call stack without threading loggers manually. Requires `contextLocalStorage` in configuration: ~~~~ typescript import { configure, getConsoleSink, withContext } from "@logtape/logtape"; import { AsyncLocalStorage } from "node:async_hooks"; await configure({ sinks: { console: getConsoleSink() }, loggers: [{ category: "app", sinks: ["console"] }], contextLocalStorage: new AsyncLocalStorage(), }); function handleRequest(req: Request) { withContext({ requestId: crypto.randomUUID() }, () => { // All logs inside this callback automatically include requestId processRequest(req); }); } ~~~~ > **Note:** Implicit contexts are not available in browsers yet. ### Lazy evaluation Wrap expensive computations with `lazy()` so they only run when the level is enabled: ~~~~ typescript import { getLogger, lazy } from "@logtape/logtape"; const logger = getLogger(["my-app"]); logger.debug("System state: {state}", { state: lazy(() => JSON.stringify(getExpensiveState())), }); ~~~~ For structured data, pass a callback as the second argument: ~~~~ typescript logger.debug("Diagnostics", () => ({ heap: process.memoryUsage().heapUsed, uptime: process.uptime(), })); ~~~~ For **async** lazy evaluation, pass an async callback and `await` the result: ~~~~ typescript await logger.info("User details", async () => ({ user: await fetchUserDetails(), })); ~~~~ For multiple expensive log calls, use `isEnabledFor()`: ~~~~ typescript if (logger.isEnabledFor("debug")) { const snapshot = await captureExpensiveSnapshot(); logger.debug("Snapshot: {data}", { data: snapshot }); } ~~~~ See <https://logtape.org/manual/lazy.md> and <https://logtape.org/manual/contexts.md> for details. Logging errors -------------- Pass `Error` objects directly to `error()` or `fatal()`. You can attach extra properties as a second argument: ~~~~ typescript try { await riskyOperation(); } catch (error) { logger.error(error, { operation: "riskyOperation", userId }); } ~~~~ Sinks and formatters -------------------- ### Built-in sinks ~~~~ typescript import { configure, getConsoleSink, getStreamSink, } from "@logtape/logtape"; await configure({ sinks: { console: getConsoleSink(), stderr: getStreamSink(Writable.toWeb(process.stderr)), }, loggers: [{ category: "app", sinks: ["console"] }], }); ~~~~ ### Sink filters Use `withFilter()` to route different levels to different sinks: ~~~~ typescript import { configure, getConsoleSink, withFilter } from "@logtape/logtape"; await configure({ sinks: { errorsOnly: withFilter(getConsoleSink(), "error"), allLevels: getConsoleSink(), }, loggers: [ { category: "app", sinks: ["allLevels", "errorsOnly"] }, ], }); ~~~~ ### Formatters ~~~~ typescript import { configure, getAnsiColorFormatter, getConsoleSink, getJsonLinesFormatter, getLogfmtFormatter, } from "@logtape/logtape"; // Pretty ANSI-colored output for development getConsoleSink({ formatter: getAnsiColorFormatter() }); // JSON Lines for production / log aggregation getConsoleSink({ formatter: getJsonLinesFormatter() }); // logfmt for readable structured logs getConsoleSink({ formatter: getLogfmtFormatter() }); ~~~~ For even nicer development output, use `@logtape/pretty`: ~~~~ typescript import { getPrettyFormatter } from "@logtape/pretty"; getConsoleSink({ formatter: getPrettyFormatter() }); ~~~~ ### Disposal Non-blocking sinks and stream sinks hold resources. In **edge functions** or short-lived processes, explicitly dispose before exit: ~~~~ typescript
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub