@kleb/logging (0.5.0)
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 |