Adapters Overview
Ship logs to observability platforms with built-in adapters
Send your logs to external observability platforms with built-in adapters. Each adapter is a regular transport — batched, retried, and non-blocking — so you can mix them with console and file logging or run them exclusively.
Available Adapters
Cloud Platforms
| Platform | Import | Best for |
|---|---|---|
| Axiom | logixlysia/axiom |
Schema-free log analytics — every field is queryable |
| Better Stack | logixlysia/better-stack |
Logs, uptime, and alerting in one place |
| Datadog | logixlysia/datadog |
Enterprise observability with facets and pipelines |
| Sentry | logixlysia/sentry |
Structured logs next to your errors and traces |
| PostHog | logixlysia/posthog |
Product analytics — link logs to persons and funnels |
Self-Hosted & Open Standards
| Platform | Import | Best for |
|---|---|---|
| OTLP | logixlysia/otlp |
Any OpenTelemetry backend — collectors, Grafana Cloud, New Relic, Honeycomb, SigNoz |
| HyperDX | logixlysia/hyperdx |
Open-source observability via OTLP |
| Grafana Loki | logixlysia/loki |
Label-indexed logs for the Grafana stack |
| ClickHouse | logixlysia/clickhouse |
Your own SQL log warehouse, no pipeline in between |
Quick Start
Set the platform’s environment variables, create the transport, and pass it to transports:
import { Elysia } from 'elysia'
import logixlysia from 'logixlysia'
import { createAxiomTransport } from 'logixlysia/axiom'
const app = new Elysia()
.use(
logixlysia({
config: {
transports: [createAxiomTransport()]
}
})
)
.get('/', () => 'ok')
.listen(3000)
Trigger a request and the access log appears in your platform’s log explorer.
Shared Behavior
All adapters share the same core:
- Batching — entries buffer and flush either when
maxBatchSizeis reached (default 20) or afterflushIntervalMs(default 2000 ms), whichever comes first. - Retries — network errors,
429, and5xxresponses retry with linear backoff (default 2 retries). Other4xxresponses fail immediately. - Timeout — each request aborts after
timeoutms (default 5000). - Non-blocking — sends run in the background and never delay your HTTP responses. Failures are reported through
onError(sink'transport') or rate-limited to stderr. - Credentials — read from environment variables by default; options passed to the factory always win. Missing credentials throw at startup with an actionable message, not silently at runtime.
- Endpoint safety — endpoint URLs are validated when the transport is created and must use
http:orhttps:, and requests never follow redirects, so a credential header can’t be forwarded to another origin.
Every adapter accepts these options on top of its platform-specific ones:
| Option | Type | Default | Description |
|---|---|---|---|
maxBatchSize |
number |
20 |
Entries buffered before an immediate flush |
flushIntervalMs |
number |
2000 |
Max time an entry waits before the buffer is sent |
timeout |
number |
5000 |
Per-request timeout in milliseconds |
retries |
number |
2 |
Retry attempts on network errors, 429, and 5xx |
maxPendingBatches |
number |
32 |
Batches allowed to be waiting on the backend at once |
onError |
(error: unknown) => void |
— | Called when a batch fails after retries or is dropped |
Batches are sent one at a time, so they arrive in the order they were logged and a slow backend applies backpressure instead of piling up requests. Once maxPendingBatches batches are waiting, new batches are dropped rather than buffered forever. Pass the same function you give config.onError as the adapter’s onError to see every batch failure — including the ones from the interval timer, which no caller is awaiting — in one place.
Multiple Destinations
Adapters compose — fan the same logs out to several platforms:
import { createAxiomTransport } from 'logixlysia/axiom'
import { createSentryTransport } from 'logixlysia/sentry'
app.use(
logixlysia({
config: {
transports: [createAxiomTransport(), createSentryTransport()]
}
})
)
Production-Only External Logging
Use useTransportsOnly to disable console and file output and send logs exclusively to your platform:
app.use(
logixlysia({
config: {
transports: [createAxiomTransport()],
useTransportsOnly: process.env.NODE_ENV === 'production'
}
})
)
Graceful Shutdown
When the app stops, the plugin starts flushing every transport and the file sink. Elysia’s app.stop() does not wait for plugin stop hooks, so a process that exits right after await app.stop() can cut that flush short. Before you exit, await flushLogixlysia with the same options object you passed to the plugin. The stop hook’s own wait is bounded by flushTimeoutMs (5000 ms by default; 0 starts the flush without waiting), and a flush that runs out of time is reported through onError with sink: 'shutdown'.
What that does not cover:
SIGKILL, or a crash — nothing runs.- A process that exits without calling
app.stop(), including a bareSIGINTwith no handler. - A serverless function frozen between invocations.
Signals are yours to handle, because a library that installs its own handlers fights the rest of your process. This handler stops the app, waits for the flush, then exits:
import { Elysia } from 'elysia'
import logixlysia, { flushLogixlysia } from 'logixlysia'
import { createAxiomTransport } from 'logixlysia/axiom'
const options = { config: { transports: [createAxiomTransport()] } }
const app = new Elysia().use(logixlysia(options)).listen(3000)
process.on('SIGTERM', async () => {
await app.stop()
await flushLogixlysia(options)
process.exit(0)
})
For workers, scripts, and anything else that logs outside the Elysia lifecycle, drain the sinks directly:
import { flushLogixlysia } from 'logixlysia'
await flushLogixlysia(options, { close: true })
close: true also releases sockets, timers, and the file handle; omit it to flush and keep logging.
What Gets Sent
Each log carries its level, message, and the full meta object: the request method and URL, the response status, durationMs, and everything merged into the request context — request IDs, trace IDs, user IDs, and your own fields. Platforms that prefer flat attributes (HyperDX, Sentry, PostHog) receive dot-notation keys like request.method and context.requestId; Axiom receives the nested structure as-is.
Redaction runs before transports, so autoRedact and redactKeys apply to everything an adapter ships off-box.