Skip to content

LoggilyDebugs, logs, and spans — one API

Clarity without the clutter. One namespace tree. One output pipeline. One ?. pattern for near-zero overhead. Pure TypeScript. ~3KB. Zero dependencies.

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

Quick Start

bash
npm install loggily
bash
bun add loggily
bash
pnpm add loggily
bash
yarn add loggily
typescript
import { createLogger } from "loggily"
import { toOtel } from "loggily/otel"
import { withMetrics, createMetricsCollector } from "loggily/metrics"
import * as otelApi from "@opentelemetry/api"

// One pipeline: console + OTEL + a Pino transport
const collector = createMetricsCollector()
const log = withMetrics(collector)(
  createLogger("myapp", [
    { level: "debug" },
    toOtel({ api: otelApi }),
    { write: (event) => pinoTransport.write(event), objectMode: true },
    console,
  ]),
)

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

// Spans — automatic timing, parent-child tracking, trace IDs
{
  using span = log.span("db:query", { table: "users" })
  const users = await db.query("SELECT * FROM users")
  span.spanData.count = users.length
}
// → SPAN myapp:db:query (45ms) {count: 100, table: "users"}
// → also forwarded to OTLP backend and Pino transport

// Metrics — check p50/p95/p99 from explicit collector
for (const [name, s] of collector.all()) {
  if (s.p95 > 100) console.warn(`${name} is slow: p95=${s.p95}ms`)
}