@kleb/logging (0.5.0)

Published 2026-07-14 11:00:16 +02:00 by kleb

Installation

@kleb:registry=
npm install @kleb/logging@0.5.0
"@kleb/logging": "0.5.0"

About this package

@kleb/logging

Structured logging for TypeScript projects.

Install from the private Forgejo npm registry after configuring auth for https://git.kleb.sh/api/packages/kleb/npm/:

bun add @kleb/logging
import { createLogger } from "@kleb/logging";

const logger = createLogger();
logger.info("app.started", { port: 3000 });

Async sink writes are tracked until an awaited flush observes their outcome. flush() is a barrier for writes accepted before the call; writes made later belong to a later barrier. Concurrent flushes are serialized safely, all sinks are attempted, and multiple failures reject with AggregateError.

shutdown() stops accepting new logger writes, flushes accepted writes, and attempts every sink shutdown. It is idempotent. A caller can bound how long it waits without cancelling the graceful shutdown already in progress:

await logger.shutdown({ deadline: Date.now() + 5_000 });

A missed deadline rejects with ShutdownDeadlineError; a later shutdown() call can still observe the underlying operation's eventual result.

Use the config integration when @kleb/config is installed and logging should be driven by app configuration:

import { kConfig } from "@kleb/config/server";
import { createLoggerFromConfig, loggingConfig } from "@kleb/logging/config";

const appConfig = kConfig("server.json").section("logging", loggingConfig());
const loaded = appConfig.load({ logger });
const logger = createLoggerFromConfig(loaded.value.logging);

Use the server entrypoint for rotating file logs:

import { createLogger } from "@kleb/logging";
import { createRotatingFileSink } from "@kleb/logging/server";

const logger = createLogger({
  sinks: [
    createRotatingFileSink({
      directory: "logs",
      archiveDirectory: "archive",
      formats: ["json", "text"],
      rotation: { every: "day", atHour: 0 },
      maxArchived: 31,
    }),
  ],
});

Archives are written below the active log directory by default:

logs/
  current.log
  current.json.log
  error.log
  errors.json.log
  archive/
    text/
    json/

Use a custom archive folder when active logs and archived logs should be separated:

createRotatingFileSink({
  directory: "logs/active",
  archiveDirectory: "../archive",
  formats: ["json", "text"],
  rotation: { every: "hour" },
});

For short-lived validation or interval-based rotation, use rotation.every.milliseconds:

createRotatingFileSink({
  directory: "logs",
  formats: ["json", "text"],
  rotation: { every: { milliseconds: 60_000 } },
});

Log entries are queued in memory and written to disk by a background drain loop. Await flush() or shutdown() to confirm that entries accepted before that call reached every configured destination. A rejection reports accepted entries that could not be formatted or written, while writes to unaffected formats and files still complete. These methods do not call fsync; resolution confirms completion of the filesystem writes, not storage-device durability across a crash or power loss.

The queue uses bounded, drop-new behavior. maxQueueEntries counts queued and in-flight accepted entries; a new entry is dropped when that bound is reached. Drops and accepted-entry failures are observable without relying on the file sink itself:

const sink = createRotatingFileSink({
  maxQueueEntries: 10_000,
  onDiagnostic(diagnostic) {
    monitoring.record(diagnostic.type, diagnostic.diagnostics);
  },
});

const snapshot = sink.getDiagnostics();
// { acceptedEntries, droppedEntries, failedEntries, pendingEntries }

Diagnostic counters are monotonic for the sink lifetime. Exceptions thrown by onDiagnostic are isolated from logging. Writes attempted after shutdown are also counted and reported as drops with reason "shutdown".

Disable automatic rotation while keeping manual rotation available:

const sink = createRotatingFileSink({
  directory: "logs",
  rotation: false,
});

await sink.rotate();

The legacy rotationHour, rotationIntervalMs, and scheduleRotation options remain available for one compatibility release. New code should use rotation; the legacy fields are deprecated in the public types.

Numeric file-sink options are validated rather than silently normalized: maxQueueEntries must be a positive safe integer, millisecond intervals must be positive and finite, maxArchived must be a non-negative safe integer, and rotation hours must be integers from 0 through 23.

Use the context entrypoint to attach request-scoped fields across async calls:

import { createLogger } from "@kleb/logging";
import { logContextProvider, withLogContext } from "@kleb/logging/context";

const logger = createLogger({
  contextProvider: logContextProvider,
});

await withLogContext({ requestId: "req-123", userId: "user-1" }, async () => {
  await doWork();
  logger.info("work.completed");
});

Fields passed directly to a log call override active context fields:

logger.info("work.completed", { requestId: "req-explicit" });

Redaction is explicit:

const logger = createLogger({
  redaction: {
    keys: ["authorization", /password/i],
    patterns: [/token-[a-z0-9]+/gi],
  },
});

HTTP helpers require already-safe route patterns:

logger.info(
  "http.request.completed",
  createHttpLogFields({
    requestId: "req-123",
    method: "GET",
    routePattern: "/api/users/:id",
    status: 200,
    durationMs: 12.5,
  }),
);

Dependencies

Peer dependencies

ID Version
@kleb/config ^0.6.0
Details
npm
2026-07-14 11:00:16 +02:00
10
UNLICENSED
18 KiB
Assets (1)
Versions (7) View all
0.6.0 2026-07-20
0.5.0 2026-07-14
0.4.0 2026-07-07
0.3.1 2026-07-04
0.3.0 2026-07-03