LogTape 2.2.0: Lint rules, testing utilities, and request context #179
dahlia
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
LogTape is a logging library for JavaScript and TypeScript that works across Deno, Node.js, Bun, and browsers. It's built around structured logging, has zero dependencies, and is designed to work as well in library code as in application code.
Version 2.2.0 ships two new packages, a significant performance overhaul in the core, and first-class request correlation for HTTP integrations. Here's what changed.
New website
The LogTape website at logtape.org has been redesigned from the ground up. The new site is cleaner and navigates better across the growing documentation: a dedicated manual, per-sink reference pages, lint rule guides, a library comparison page, and a runtime comparison page.
New package: @logtape/lint
A common failure mode for new LogTape users is reaching for familiar patterns that bypass the library's structured logging design. Template literals (
`User ${id} joined`) embed values directly into the message string and lose structure. Object properties computed eagerly ({ data: fetchData() }) run the fetch even when the log level is disabled. Async lazy callbacks withoutawaitsilently drop the returnedPromise<void>.@logtape/lint is a new package that catches these mistakes at lint time. It ships four rules that work with ESLint v8 and v9 (flat config), Oxlint, and Deno Lint.
The
no-message-interpolationrule flags template literal expressions in log message arguments:The
prefer-lazy-evaluationrule flags a properties object whose values contain function calls, and auto-fixes it by wrapping the object in an arrow function so the calls only run when the log level is enabled:The
no-unawaited-logrule flags async lazy callbacks passed to log methods withoutawait, with an auto-fix forasyncfunctions. Therequire-meta-sinkrule warns whenconfigure()orconfigureSync()has no logger entry covering the meta category ("logtape",["logtape"], or["logtape", "meta"]).To add the rules in ESLint:
See the lint rules documentation for Oxlint and Deno Lint setup. (#170, #171)
New package: @logtape/testing
Every project using LogTape-instrumented library code ends up writing the same test boilerplate: create an array, wire
array.push.bind(array)as a sink, callconfigure(), remember to callreset()in teardown, write your own matchers from scratch. @logtape/testing replaces all of that.createLogRecorder()returns a recorder with asinkyou wire directly intoconfigure():The recorder exposes
records(all captured entries in order),clear(),take()(returns and clears),find(),filter(),assertLogged(), andassertNotLogged(). Matchers can target category, category prefix, level, rendered message string or regex, raw message template, structured properties for partial matching, and arbitrary predicate functions. Date property values match by timestamp; regex matchers match string values.When
assertLogged()fails, the error shows the expected matcher alongside up to three nearby candidate records with category, level, rendered message, and a properties summary, so the mismatch is clear at a glance.The recorder is a synchronous sink and works in any test runner across Deno, Node.js, and Bun. (#173, #175)
See the testing documentation for the full API.
Request context for HTTP integrations
Correlating application logs with request logs has always required assembling the pieces manually: configure
AsyncLocalStorage, callwithContext()in middleware, generate a request ID, propagate it through response headers. Version 2.2.0 handles all of it with a single option.The new
contextoption is now available onelysiaLogger(),expressLogger(),honoLogger(), andkoaLogger(). The simplest form iscontext: true:With that option enabled, the middleware reads the incoming
x-request-idheader, generates a UUID withcrypto.randomUUID()when the header is absent, writes the resolved ID back to thex-request-idresponse header, addsrequestIdto the request completion log record, and propagates the same value throughwithContext()to every log record emitted while handling the request, whencontextLocalStorageis configured.For full control, pass a
RequestContextOptionsobject:The
includeoption selects which request fields enter the implicit context. Theenrichcallback adds any additional fields, including values from the framework's own context. The Express integration also exposes thehttpVersionfield.Existing request logging behavior is unchanged when
contextis not set. (#172, #174)See the request context documentation for complete configuration details.
Clearer HTTP format names
The
combinedandcommonformat names in the HTTP integrations have always produced structured log objects, not verbatim Apache access log text. That gap between the name and what it actually outputs caused real confusion, as @shilrobot described in detail in #178.Version 2.2.0 adds unambiguous names. Structured output now has
structured-combinedandstructured-common; Apache-format text that matches Morgan's output hasmorgan-combinedandmorgan-common. The originalcombinedandcommonnames remain as deprecated aliases for the structured presets, so nothing breaks for existing users.Core performance improvements
The core logging path went through a focused optimization pass in 2.2.0. Seven independent changes reduce allocation and redundant work on the hot path for enabled logging:
Applications that log frequently at enabled levels will see measurably lower overhead.
@logtape/config: Implicit context in file-based configuration
configureFromObject()in @logtape/config now accepts acontextLocalStorageoption. This enables implicit context support when loading configuration from JSON, YAML, TOML, or other external files, bringing file-based configuration to parity withconfigure()andconfigureSync(), which have supported this option since 2.0.0.@logtape/drizzle-orm: SQLite support
The Drizzle ORM integration previously substituted query parameters only for the PostgreSQL placeholder style (
$1,$2, …). Applications using SQLite-based databases such as Turso would see bare?placeholders in log output instead of the actual parameter values.Version 2.2.0 adds a
dialectoption that handles parameter substitution correctly for each Drizzle dialect:The default remains
"pg"for backward compatibility. Big thanks to @Van-sh for the issue report and for submitting the pull request. (#168, #169)@logtape/redaction: Traversal limits
Field-based and pattern-based redaction now accept
maxDepthandmaxPropertiesoptions. Without limits, a sufficiently large or deeply nested log record can cause the redaction traversal to run without bound. When a limit is exceeded, redaction emits a warning through the meta logger and truncates or omits the unprocessed portion. The newRedactionTraversalOptionsinterface covers both options and is shared between the field-based and pattern-based redaction configuration interfaces.@logtape/sentry: Sentry namespace option
In monorepos that use multiple Sentry framework SDKs (
@sentry/nextjs,@sentry/react-native, and others), each SDK pins its own exact@sentry/coreversion. When @logtape/sentry resolves to a different@sentry/coreinstance than the one the application initialized,getClient()returnsundefinedand the sink silently no-ops, with no error or warning emitted.The new
sentryoption onSentrySinkOptionsaccepts the Sentry namespace the application already imported:This ensures the sink uses the same module instance for captures, active spans, isolation scopes, and structured logs. Omitting the
sentryoption falls back to the static import, so existing usage is unchanged. Thanks to @conordmcgovern for the detailed issue report and proposed fix. (#167)@logtape/adaptor-pino: Pino 10.x support
The
pinopeer dependency range for @logtape/adaptor-pino is expanded from^9.7.0to^9.7.0 || ^10.0.0. Users on Pino 10 no longer see a peer dependency warning at install time, and no API changes are needed on their end. Thanks to @stexandev for the request. (#176)@logtape/file: Throughput improvements and a rotation fix
getStreamFileSink()now writes formatted records directly to the underlying file stream instead of routing through an extra Node.js stream layer. Built-in file sinks that format records immediately also skip the pre-sink snapshot step. Both changes measurably improve throughput in high-volume logging scenarios.A bug in
getRotatingFileSink()caused the current log file to be renamed to path.1 during rollover even whenmaxFileswas0or negative, leaving stale backup files behind despite no backups being configured. This is now fixed. Custom rotating file drivers that implementRotatingFileSinkDriverorAsyncRotatingFileSinkDrivercan optionally provideunlinkSync()to support the corrected behavior.Upgrading
There are no breaking changes in 2.2.0. See the full changelog for complete details.
Beta Was this translation helpful? Give feedback.
All reactions