---
changelog:
  category: Release
  version: logixlysia@6.10.0
date: '2026-10-10T13:33:43Z'
seo:
  description: >-
    HTTP adapters no longer cap delivery at one 20-entry batch per round trip.
    Entries that arrive while a request is in flight wait and go out together in
    the…
title: logixlysia@6.10.0
type: changelog
---
## Minor Changes

- 3423143: HTTP adapters no longer cap delivery at one 20-entry batch per round trip. Entries that arrive while a request is in flight wait and go out together in the next request, which starts as soon as the one in flight returns if a full batch (`maxBatchSize`) is waiting and on the `flushIntervalMs` timer otherwise, so light traffic is batched as before and a 50 ms backend now keeps up with thousands of records per second instead of dropping about half of them. Delivery order is unchanged. The new `maxEntriesPerRequest` option (default 500; 100 for Sentry) is the most entries one request carries; it applies to every request, so a `maxBatchSize` above it splits each batch. At most `maxPendingBatches × maxBatchSize` entries wait behind the request in flight, which carries up to `maxEntriesPerRequest` more, so an adapter holds at most 1,140 entries by default (740 for Sentry) where it held about 660; beyond that, entries are dropped, counted and reported once per request. Requests can carry more bytes than before, and vendor byte limits are not enforced. A failure of a request the queue started on its own goes to the adapter's `onError` (or rate-limited stderr), not to `config.onError`, because no `log()` call is awaiting it; a failure of the request started by a `log()` or `flush()` call is still rejected to that caller. `flush()` waits for the entries accepted before the call, not for the queue to go idle, and it resolves rather than rejects when those entries went out in a request the queue started on its own and that request failed, so `flushLogixlysia` no longer passes that failure to `config.onError`. `maxBatchSize` and `maxEntriesPerRequest` must be positive integers, and the adapter throws at construction otherwise; `maxBatchSize: 0` and `Infinity`, which used to mean one request per entry and timer-only batching, now throw too. The built-in HTTP adapters implement `close()`: it stops accepting entries, then flushes, so `flushLogixlysia(options, { close: true })` releases them. `close` is now a required member of the exported `AdapterTransport` type, so an object of your own typed as `AdapterTransport` needs one as well. A transport wrapped in `withRedaction` is not closed through the wrapper yet; it forwards `flush` only.

## Patch Changes

- 9ac4a0a: Compressed rotation archives are created with `logFileMode` (default `0o600`) instead of the process umask, so they are no more readable than the live log. An adapter's "invalid endpoint URL" error no longer echoes the credentials embedded in the URL, in its message or in its `cause`. File write and rotation failures reported on stderr (when `config.onError` is not set) are rate limited to one message every 5 seconds, like transport and formatting failures.
- ca5fe30: `autoRedact` no longer throws, and therefore no longer drops the record, when an error's `name` or `message` is an inherited accessor (a `DOMException`, an app error class with `get name()`); redacted errors keep `message`, `name` and `stack` non-enumerable, as native errors do. A value with a `toJSON` method (Bun's `CookieMap`, app classes) is redacted as what it serializes to; errors keep their class even when they define `toJSON`, so an `HttpError` stays an `HttpError`. The password of a URL, `Bearer` and `Basic` credentials in any string, sensitive query keys inside an absolute URL in a `referer`, `location` or `content-location` header, and a field named `cookies` are now masked. Redaction and log serialization stop at a depth of 64 with `"[Depth]"`, so a deeply nested client payload no longer costs quadratic time or overflows the stack. Rotation sizes, intervals and retention given with units are rejected when they are zero, as the numeric zero already was.
- 7b030bc: `LogFilter`, `LogixlysiaConfig`, `LogRotationConfig`, `SinkErrorContext` and `WebSocketLike` are exported from the package root as types, so a config object, a rotation config, an `onError` handler or a `wrapWs` hook can be typed without reaching into the package's internals. No runtime export changes.
- 665b71b: The access line is now written once per request, after Elysia has produced the final response, so it carries the status the client received for every exit: a `mapResponse` rewrite, an error mapped by an `onError` hook in either registration order, `set.status` followed by a thrown `Error`, a response returned from an `onRequest` hook, and an auth short-circuit. A generator (streaming) handler is logged when its body finishes, so `{duration}` covers the whole stream, and a throw inside the body produces an error line. A plain object with `code` and `response` keys is no longer mistaken for Elysia's `status()` result. A thrown 2xx or 3xx `status()` now logs at INFO, as an access line without its message, where it was an ERROR line carrying the message. With `useAsyncLocalStorage`, `useLogger()` works inside hooks registered after the plugin, inside a generator body, inside a handler that awaits `app.handle()` for another route, and in work that outlives the response; outside a request it is still a no-op. The `X-Request-Id` response header is set as soon as the request starts, so short-circuited responses carry it too. The request boundary is app-wide: once the plugin is mounted anywhere, every request of the application gets a line, including routes defined on parent apps (previously those were silently unlogged); mount the plugin once at the root. Apps running with `aot: false`, or mounting the plugin inside `group()` or `guard()`, keep the previous hook-based behaviour, including a second error line for an error thrown after the handler returned. Consequences for existing expectations: an error thrown after the handler returned, by an `afterHandle` hook or while Elysia builds the response, now produces one error line instead of a success line followed by an error line; when an app-wide `onError` registered before the plugin answers, the plugin never sees the thrown value, so the line is an access line at the level of the status sent, with an empty message and no error payload, where it was an error line carrying the thrown value; an error that a later `onError` maps to a 2xx or 3xx (a redirect to a login page, say) is logged as an error line carrying that status and the thrown message, where it was logged as ERROR 500 before; a custom log made from a hook registered after the plugin replaces the access line, as a custom log from the handler always did, where it was a second line before, and a custom log made from a later `onError` now comes before the error line instead of after it; and with two plugin instances in sibling modules, access lines now go through the first instance's sinks, while a custom log made through a later instance's `log` still reaches that instance's transports, without a request ID, and replaces the access line for that request (create the plugin once and share it).
- 0fb85de: `store.pino` is defined again. Since 6.7.0 the lazy pino Proxy was dropped by Elysia's `state()` because its target had no keys, so every handler that read `store.pino` threw. Assignments such as `store.pino.level = 'debug'` now reach pino. The plugin rejects at construction a `logFilter.level` that is not an array of `DEBUG`, `INFO`, `WARNING` or `ERROR`, `useTransportsOnly: true` without a transport, and a `flushTimeoutMs` that is not a finite number of milliseconds at or above 0; each of these used to turn logging off or misreport shutdown silently. `useTransportsOnly: true` with an empty `transports` list was previously accepted as an effectively disabled logger; use `disableInternalLogger` and `disableFileLogging` for that. `geoEnricher` ignores non-string fields in a Netlify `x-nf-geo` header instead of throwing. A `requestId.generator` that throws or returns an invalid id no longer produces an error line on every request; the plugin falls back to a generated UUID and reports the failure through `onError`, or on stderr at most once per interval when no hook is set.
- c7667f2: The HTTP adapters now take a JSON-safe copy of each record's `meta` at the moment it is logged. A later change to a logged object is no longer shipped, which also closes a redaction bypass under `autoRedact` (an adapter wrapped by `withRedaction` takes its copy when the wrapper hands the record over, after its own asynchronous redaction, so that window is unchanged). A value that cannot be serialized (a throwing `toJSON` or getter) is replaced by `"[Unserializable]"` for that field only, where it previously turned a whole Axiom, Better Stack or Datadog batch into that string or rejected a ClickHouse, HyperDX, OTLP, PostHog or Sentry batch. A BigInt anywhere in `meta` is shipped as its decimal string by every adapter, so a BigInt user id now resolves to PostHog's `distinct_id`, and `flattenMeta` no longer renders a nested BigInt or cycle as `[object Object]`.
- 3373fc1: `wrapWs` no longer discards what a hook returns. A returned string, object, `Buffer`, promise or generator reaches Elysia and is sent to the client exactly as without the wrapper; a hook that returned a value by accident (for example `(ws, m) => ws.send(m)`, whose result is Bun's numeric send status) now has that value sent too, as Elysia does for an unwrapped hook. An `async` hook that rejects is handled by Elysia's error path instead of becoming an unhandled rejection, and its lifecycle line is written after the hook settles.
