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

@trebired/logger

v2.0.0

Published

Structured server and browser logger with JSONL storage, shared event semantics, custom weighted levels, redaction, retention, and local query helpers.

Readme

@trebired/logger

Structured logging for server and browser applications that want one event model, readable console output, and durable local logs without running a separate logging stack.

@trebired/logger writes JSONL logs into group-based folders on the server, supports optional partitioned storage, and now ships a framework-neutral browser runtime with an optional React adapter on top.

Install

Runtime support: Bun 1+ and Node.js 18+.

npm install @trebired/logger
import { createLog } from "@trebired/logger";

const log = createLog({
  dir: "/var/log/my-app",
  console: true,
  quiet: true,
});

log.info("app.start", "ready", { port: 3000 });
await log.flush();

Browser entrypoints:

import { createBrowserLog } from "@trebired/logger/browser";
import { LogProvider, useLog } from "@trebired/logger/browser/react";

Browser Runtime

Use @trebired/logger/browser when you want the same levels, metadata conventions, grouping rules, and scoped logger behavior in browser code:

import { createBrowserLog } from "@trebired/logger/browser";

const log = createBrowserLog({
  group: "frontend.app",
  metadata: {
    deploymentId: "deploy-42",
    requestId: "req-abc",
  },
});

log.info("frontend boot");
await log.flush();

The first browser release ships with console delivery built in. It also supports custom browser transports with in-memory batching so you can add fetch, beacon, websocket, or other delivery later without changing the logger API.

There is no built-in SSR bootstrap helper in this release. If you want browser correlation data such as requestId, sessionId, or deploymentId, pass it explicitly through metadata or per-log metadata.

React Adapter

@trebired/logger/browser/react is intentionally thin. It does not create a logger for you. It only helps wire an existing browser logger into React context:

import { createRoot } from "react-dom/client";
import { createBrowserLog } from "@trebired/logger/browser";
import { LogProvider, useLog } from "@trebired/logger/browser/react";

const log = createBrowserLog({
  group: "frontend.app",
  metadata: { deploymentId: "deploy-42" },
});

function SaveButton() {
  const scopedLog = useLog("ui.save_button");
  return <button onClick={() => scopedLog.info("clicked")}>Save</button>;
}

createRoot(document.getElementById("root")!).render(
  <LogProvider log={log}>
    <SaveButton />
  </LogProvider>,
);

LogErrorBoundary is also available from @trebired/logger/browser/react when you want render errors logged with the shared event shape.

Why This Logger

Most loggers either write to stdout and expect an external collector, or provide a very broad transport system. This package is intentionally opinionated around a simpler operational workflow:

  • structured JSONL entries
  • one directory tree per log group
  • optional partition folders above group trees
  • async queued file writes by default
  • custom weighted log levels
  • local querying by group, level, day, and hour
  • built-in redaction for common sensitive fields
  • retention and file-size rolling without a database

The storage layout is meant to stay human-browsable:

/var/log/my-app/
  app/
    start/
      2026-05-03-13-0000-info.jsonl
  billing/
    invoice/
      2026-05-03-13-0000-audit.jsonl

Each line is a JSON object:

{"recorded_at":"2026-05-03T13:00:00.000Z","level":"info","group":"app.start","message":"ready","origin":{"source":"app","instance":null},"metadata":{"port":3000}}

If you want an extra top-level separation layer, set partition and the logger writes one more folder layer. This is useful for deployments, releases, environments, sessions, tenants, workers, import batches, or any other caller-defined bucket:

/var/log/my-app/
  blue-2026-05-16/
    app/
      start/
        2026-05-03-13-0000-info.jsonl

Partition names can now be built from a stable time prefix plus any caller-defined suffix:

import {
  buildPartitionName,
  buildTemporaryPartitionName,
  createLog,
} from "@trebired/logger";

const staged = buildTemporaryPartitionName({
  timeZone: "UTC",
  suffix: "deployment-unknown",
});

const final = buildPartitionName({
  timeZone: "UTC",
  suffix: "deployment-42",
});

const log = createLog({
  dir: "/var/log/my-app",
  partition: staged,
  temporaryPartition: true,
});

log.info("app.boot", "starting before final ownership is known");
await log.flush();

await log.promotePartition(final);

If you already have a full custom partition string, pass it directly with partition, or normalize it first with sanitizePartitionName().

Partition Lifecycle

You can manage partitions either from a live logger or with standalone helpers:

import {
  copyPartition,
  createPartition,
  deleteLogs,
  listPartitions,
  renamePartition,
} from "@trebired/logger";

await createPartition("/var/log/my-app", "2026-05-17-12-0000-staged", {
  temporary: true,
});

console.log(await listPartitions("/var/log/my-app"));

await renamePartition("/var/log/my-app", {
  from: "2026-05-17-12-0000-staged",
  to: "2026-05-17-12-0000-final",
});

await copyPartition({
  fromDir: "/var/log/my-app",
  from: "2026-05-17-12-0000-final",
  toDir: "/var/log/archive",
  to: "2026-05-17-12-0000-final-copy",
});

await deleteLogs("/var/log/my-app", {
  partition: "2026-05-17-12-0000-final",
  groupKey: "jobs.queue",
  level: "warn",
  olderThanDays: 7,
});

Core API

const log = createLog({
  dir: "/var/log/my-app",
  save: true,
  console: true,
  timeZone: "America/New_York",
  source: "api",
});

log.debug("app.boot", "config loaded");
log.info("app.boot", "ready");
log.success("job.import", "finished", { rows: 1200 });
log.warn("http.request", "slow request", { took_ms: 842 });
log.fail("job.import", "failed validation");
log.error("app.runtime", "uncaught error");

save defaults to true when dir is provided. If no dir is provided, the logger can still emit console output and live stream events.

If you log without passing a group, the logger always uses "default".

@trebired/logger runs on both Bun and Node.js. It may print one-time package notices for runtime-specific guidance or important future package messages. For example, when it detects Node.js, it recommends Bun for best startup and file I/O performance. Pass quiet: true to suppress package notices:

const log = createLog({
  quiet: true,
});

When quiet is not true, the package also prints a one-time startup greeting using the same console logger style as normal entries.

timeZone is a top-level logger option because it controls the actual local moment used for saved file names and console timestamps. It defaults to the host timezone, then falls back to America/New_York.

Console output is configurable. level and message are always shown; timestamp, group, and metadata can be hidden. console.locale only controls display formatting. When locale is omitted or invalid, the logger passes undefined to Intl.DateTimeFormat, so JavaScript uses the runtime or system default locale:

const log = createLog({
  timeZone: "America/New_York",
  console: {
    colors: true,
    timestamp: true,
    group: false,
    metadata: false,
    locale: "en-US",
  },
});

Use timeZone for the local hour and console.locale for the display style.

European dot-date locales such as cs-CZ and de-DE are formatted consistently as 03.05.2026, 15:59:23.

Full API Example

import { createLog } from "@trebired/logger";

const log = createLog({
  dir: "/var/log/my-app",
  partition: "blue-2026-05-16",
  temporaryPartition: false,
  save: true,
  console: {
    enabled: true,
    colors: true,
    timestamp: true,
    group: true,
    metadata: true,
    locale: "en-US",
  },
  quiet: true,
  timeZone: "America/New_York",
  source: "api",
  levels: {
    debug: { weight: 10, label: "DEBUG", color: "#94a3b8" },
    info: { weight: 20, label: "INFO", color: "#38bdf8" },
    success: { weight: 25, label: "SUCCESS", color: "#22c55e", bold: true },
    warn: { weight: 30, label: "WARN", color: "#f59e0b", stream: "stderr" },
    fail: { weight: 40, label: "FAIL", color: "#fb7185", stream: "stderr" },
    error: { weight: 50, label: "ERROR", color: "#ef4444", stream: "stderr", showStack: true, bold: true },
    audit: { weight: 35, label: "AUDIT", color: "#8b5cf6" },
    panic: { weight: 100, label: "PANIC", color: "#dc2626", stream: "stderr", showStack: true, bold: true },
  },
  minLevel: "debug",
  write: {
    mode: "async",
    maxQueue: 10000,
    overflow: "drop-newest",
  },
  retention: {
    enabled: true,
    maxFileSize: "20mb",
    compressOldFiles: false,
    cleanupIntervalMs: 60_000,
    // deletion is opt-in:
    maxAgeDays: 30,
    maxPartitions: 5,
  },
  redact: {
    includeDefaultSensitiveKeys: true,
    paths: ["user.password", /^headers\.authorization$/i],
    replacement: "[REDACTED]",
  },
  serializers: {
    userId: (value) => `user:${String(value)}`,
    error: (value) =>
      value instanceof Error
        ? { name: value.name, message: value.message, stack: value.stack }
        : value,
  },
  sample: (entry) => entry.level !== "debug" || Math.random() < 0.1,
  request: {
    group: "http.request",
    idHeader: "x-request-id",
    attach: true,
  },
});

log.info("app.start", "ready", { port: 3000 });
log.audit("billing.invoice", "created", { invoiceId: "inv_123" });
log.error("app.start", "failed", { reason: "missing config" });

await log.flush();
await log.close();

Custom Levels

Levels are weighted. minLevel filters out entries with lower weight.

const log = createLog({
  levels: {
    audit: { weight: 35, label: "AUDIT", color: "#8b5cf6" },
    panic: { weight: 100, label: "PANIC", stream: "stderr", bold: true },
  },
  minLevel: "audit",
});

log.audit("billing.invoice", "created", { invoiceId: "inv_123" });
log.panic("runtime", "unrecoverable");

Built-in levels are debug, info, success, warn, fail, and error. A custom level with the same name overrides the built-in config.

Custom level methods are available at runtime. In TypeScript, the logger exposes dynamic level methods through an index signature, so custom names work but are not exhaustively autocompleted from the levels object yet.

Async Writes, Flush, and Close

File writes are async queued by default.

const log = createLog({
  dir: "/var/log/my-app",
  write: {
    mode: "async",
    maxQueue: 10000,
    overflow: "drop-newest",
  },
});

log.info("queue.example", "queued");

await log.flush();
await log.close();

getStats() exposes write health:

const stats = log.getStats();
// { mode, queued, written, dropped, failed, queueLength, closed }

For simple scripts or tests, sync writing is available:

const log = createLog({
  dir: "./logs",
  write: { mode: "sync" },
});

Retention and Rolling

Defaults:

  • logs are kept forever unless you set a retention number
  • maxFileSize: "20mb"
  • compressOldFiles: false
const log = createLog({
  dir: "/var/log/my-app",
  retention: {
    // only delete when you set one or both of these:
    maxAgeDays: 30,
    maxPartitions: 5,
    maxFileSize: "100mb",
    compressOldFiles: true,
    cleanupIntervalMs: 60 * 60 * 1000,
  },
});

maxAgeDays deletes old files by age. maxPartitions keeps only the newest partition folders by last write time. A partition can represent deployments, sessions, environments, release versions, or any other caller-defined grouping. If you omit both, the logger stores logs indefinitely.

When a file exceeds the configured size, the logger rolls to the next sequence inside the same group and hour:

2026-05-03-13-0000-info.jsonl
2026-05-03-13-0001-info.jsonl
2026-05-03-13-0002-info.jsonl

Redaction and Serializers

Common sensitive keys are redacted by default: password, token, secret, authorization, cookie, api_key, and related variants.

const log = createLog({
  redact: {
    paths: ["user.ssn", /^payment\./],
    replacement: "[hidden]",
  },
  serializers: {
    userId: (value) => `user:${value}`,
  },
});

log.info("account.update", "saved", {
  userId: 42,
  password: "secret",
  user: { ssn: "123-45-6789" },
});

Scoped Loggers

const jobs = log.group("jobs.queue");
jobs.info("started", { jobId: "job_1" });

const worker = log.withScope("worker", "jobs.queue", 2);
worker.error("failed", { jobId: "job_1" });

Express-Style Middleware

requestLogger() is compatible with Express-style middleware and attaches req.log by default.

app.use(log.requestLogger({
  group: "http.request",
  idHeader: "x-request-id",
}));

app.get("/", (req, res) => {
  req.log.info("handled");
  res.end("ok");
});

Query Saved Logs

Old query names are not supported anymore. Use getLogsForDir(), getAllLogs(), and getAllLogsAcrossPartitions(). Do not use getEntriesForDir() or getAll().

import { getLogsForDir } from "@trebired/logger";

const result = await getLogsForDir("/var/log/my-app", {
  level: "error",
  groupKey: "app.runtime",
  limit: 100,
});

console.log(result.logs);
console.log(result.levels.error.color);
console.log(result.metadata.count);
console.log(result.metadata.total);

The main instance query method is getAllLogs():

const recent = await log.getAllLogs({ groupKey: "billing.invoice", limit: 50 });
console.log(recent.logs);
console.log(recent.levels);
console.log(recent.metadata.total);

Pass partition: null when you want only unpartitioned logs from a mixed directory tree.

If you use partition folders and want a merged read across every partition:

const merged = await log.getAllLogsAcrossPartitions({
  groupKey: "billing.invoice",
  limit: 100,
});

console.log(merged.logs);
console.log(merged.metadata.partitions.items);

Sampling

const log = createLog({
  sample: 0.1,
});

const selective = createLog({
  sample: (entry) => entry.level === "error" || entry.group.startsWith("audit."),
});

Live Stream

import { logStream } from "@trebired/logger";

logStream.on("log", (entry, context) => {
  // context.dir is the active log directory, if one is configured
});

Development

bun install
bun run demo
bun test
bun run typecheck
bun run build

bun run demo starts a small dummy system that keeps logging until interrupted. It exercises grouped and scoped loggers, custom levels, redaction, request middleware, live stream events, local querying, and write stats. It writes throwaway logs under the OS temp directory, such as /tmp/@trebired-logger/dummy on Linux and macOS. Microslop Windows is not supported.

The npm package exports compiled files from dist. Publishing runs typecheck, tests, and build through prepublishOnly.