npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

loggily

v0.4.0

Published

TypeScript logger with debug-style namespaces, structured JSON output, and lightweight spans. Disabled logs skip argument evaluation via optional chaining.

Downloads

160

Readme

Loggily

Clarity without the clutter.

One library. One namespace tree. One output pipeline. For logs (structured JSON or console), debug(), and tracing spans. Near-zero overhead from disabled log levels. Pure TypeScript. ~3KB. Zero dependencies.

Tests TypeScript MIT License

Early release (0.x) -- API may evolve before 1.0.

Install

npm install loggily

| Requirement | Version | | ------------- | ------------------------------------------------- | | Node.js | 18+ | | Bun | 1.0+ | | TypeScript | 5.2+ (for using; .end() works on any version) | | Module format | ESM-only | | Browser | Supported via conditional export |

Quick Start

import { createLogger } from "loggily"

const log = createLogger("myapp")

// ?. skips the entire call — including argument evaluation — when the level is disabled
log.info?.("server started", { port: 3000 })
log.debug?.("cache hit", { key: "user:42" })
log.error?.(new Error("connection lost"))

Output in development (colorized with timestamps and clickable source lines):

14:32:15 INFO myapp server started {port: 3000}
14:32:15 DEBUG myapp cache hit {key: "user:42"}
14:32:15 ERROR myapp connection lost

Set NODE_ENV=production or LOG_FORMAT=json and the same code emits structured JSON:

{ "time": "2024-01-15T14:32:15.123Z", "level": "info", "name": "myapp", "msg": "server started", "port": 3000 }

Spans

Time operations with lightweight spans. Uses TC39 Explicit Resource Management (using requires TypeScript 5.2+ and runtime support). For other environments, call .end() manually:

// With `using` (TS 5.2+, Bun 1.0+, Node 22+)
{
  using span = log.span("db:query", { table: "users" })
  const users = await db.query("SELECT * FROM users")
  span.spanData.count = users.length
}
// Output: SPAN myapp:db:query (45ms) {count: 100, table: "users"}

// Without `using` — works on any runtime
const span = log.span("db:query", { table: "users" })
try {
  const users = await db.query("SELECT * FROM users")
  span.spanData.count = users.length
} finally {
  span.end()
}

Why Loggily?

One API for debug-style namespace logging, structured JSON output, and lightweight spans. Many projects end up with separate tools for these -- debug for conditional output, pino/winston for production logs, a tracing SDK for timings -- with separate configs, formats, and APIs. Loggily integrates all three into one namespace tree, one output pipeline, one ?. pattern.

Near-zero cost for disabled logs

Most loggers waste work when logging is disabled. Even with a noop function, arguments are still evaluated:

// Traditional — args are ALWAYS evaluated, even when debug is off
log.debug(`state: ${JSON.stringify(computeExpensiveState())}`)

Loggily uses optional chaining to skip the entire call — including argument evaluation:

// Loggily — args are NOT evaluated when disabled
log.debug?.(`state: ${JSON.stringify(computeExpensiveState())}`)

For trivial arguments the difference is negligible. But for real-world logging — string interpolation, JSON serialization, state snapshots — optional chaining is typically 10x+ faster because it skips the work entirely. The more expensive your arguments, the bigger the win.

Note: The big performance advantage is specifically for disabled logging with expensive arguments, not universal logger throughput. Pino is optimized for high-throughput enabled JSON logging; Loggily's biggest advantage is skipping work when logs are disabled. See benchmarks for detailed numbers per scenario.

Features

  • Namespace hierarchy — organize logs with : separators. log.logger("db") creates myapp:db. Children inherit parent context.
  • Lightweight spans and trace IDs — time any operation with using span = log.span("name"). Automatic duration, parent-child tracking, and trace IDs. For full OpenTelemetry interoperability with exporters and propagation, use OpenTelemetry.
  • Lazy messageslog.debug?.(() => expensiveString()) skips the function entirely when disabled.
  • Child contextlog.child({ requestId }) adds structured fields to every message in the chain.
  • Dev & production — colorized console with timestamps, level colors, and clickable source lines in development. Structured JSON in production. Switches automatically via NODE_ENV — same code, zero config.
  • File writeraddWriter() + createFileWriter() for buffered file output with auto-flush.
  • Worker threads — forward logs from workers to the main thread with full type safety (loggily/worker).
  • debug-compatible namespace filtering — reads DEBUG=myapp:* just like the debug package. Easy migration from debug — see the migration guide.

When Not to Use Loggily

  • Max-throughput transport pipelines — use Pino for worker-thread transports, custom serializers, and log rotation.
  • Vendor/exporter interop — use OpenTelemetry for distributed tracing with propagation, semantic conventions, and backend integrations.
  • Tiny dev-only namespace logs — use debug if all you need is conditional dev output with zero ceremony.

Documentation

Environment Variables

| Variable | Values | Effect | | -------------- | --------------------------------------- | --------------------------------------- | | LOG_LEVEL | trace, debug, info, warn, error, silent | Minimum output level | | LOG_FORMAT | console, json | Output format | | DEBUG | *, namespace prefixes, -prefix | Namespace filter (like debug package) | | TRACE | 1, true, or namespace prefixes | Enable span output | | TRACE_FORMAT | json | Force JSON for spans | | NODE_ENV | production | Auto-enable JSON format |

API

| Function | Description | | ---------------------------------------------------------------------- | ------------------------------------------------------------- | | createLogger(name, props?) | Create a logger (disabled levels return undefined for ?.) | | .trace?.() / .debug?.() / .info?.() / .warn?.() / .error?.() | Log at level (message + optional data) | | .logger(namespace) | Create child logger with extended namespace | | .span(namespace, props?) | Create timed span (implements Disposable) | | .child(context) | Create child with structured context fields | | addWriter(fn) / createFileWriter(path) | Custom output writers | | setLogLevel() / setLogFormat() / enableSpans() | Runtime configuration | | createWorkerLogger() / createWorkerLogHandler() | Worker thread support (loggily/worker) |

See the full API reference for all functions and options.

License

MIT