integration
Elysia Framework Logging Integration
Add structured logging to Elysia applications running on Bun with lifecycle hooks, scoped context, and trace propagation.
- Bun-optimized
- Lifecycle hook integration
- Globally scoped plugin
- Trace propagation
LogTide’s Elysia SDK provides a Bun-optimized plugin for automatic request logging, scoped context, lifecycle hooks, and W3C trace propagation. This page shows how it maps onto Elysia’s request lifecycle, how to add your own hooks next to it, and how to shut down cleanly.
Why use LogTide with Elysia?
- Bun-optimized: Takes advantage of Bun’s performance
- Plugin architecture: Registered globally, so it covers the whole app
- Lifecycle hooks: onRequest, onAfterHandle, onError
- Per-request scope: Each request gets its own scope and trace
Prerequisites
- Bun 1.x
- Elysia 1.x
- LogTide instance with a DSN
Installation
bun add @logtide/elysia
Quick Start
import { Elysia } from 'elysia';
import { hub } from '@logtide/core';
import { logtide } from '@logtide/elysia';
const app = new Elysia()
.use(logtide({
dsn: process.env.LOGTIDE_DSN,
service: 'elysia-api',
environment: process.env.NODE_ENV,
}))
.get('/users/:id', ({ params }) => {
hub.captureLog('info', 'Fetching user', { userId: params.id });
return { id: params.id };
})
.listen(3000);
The plugin is already registered with .as('global') internally, so it applies to all routes.
Plugin Options
The plugin accepts the ClientOptions of @logtide/core (see the JavaScript SDK configuration):
app.use(logtide({
dsn: process.env.LOGTIDE_DSN,
service: 'elysia-api',
environment: process.env.NODE_ENV,
release: '1.0.0',
tracesSampleRate: 1.0,
}));
How the plugin maps onto the Elysia lifecycle
Elysia runs each request through a fixed sequence of events: onRequest, onParse, onTransform (and derive), validation, onBeforeHandle (and resolve), the handler, onAfterHandle, mapResponse, and finally onAfterResponse. If something throws at any stage, onError runs. LogTide uses three of these:
| Hook | Behavior |
|---|---|
onRequest | Creates scope, extracts traceparent, starts span |
onAfterHandle | Finishes the span with status and duration, sets traceparent response header |
onError | Captures errors with request context |
Everything else in the sequence is yours. Elysia applies hooks only to routes registered after them, so register logtide() first and your routes after it. Elysia’s plugin scope is local by default, and the LogTide plugin applies .as('global'), so its hooks reach every instance that uses it, including parents and descendants.
Adding your own hooks
Your own hooks sit next to the plugin’s. Two useful ones are derive, to add a request ID to every request context, and onAfterResponse, which runs after the response has been sent and is a good place for an access log that does not slow the response down.
import { Elysia } from 'elysia';
import { hub } from '@logtide/core';
import { logtide } from '@logtide/elysia';
const app = new Elysia()
.use(logtide({ dsn: process.env.LOGTIDE_DSN, service: 'elysia-api' }))
.derive(({ request }) => ({
requestId: request.headers.get('x-request-id') ?? crypto.randomUUID(),
}))
.onBeforeHandle(({ requestId, request }) => {
hub.addBreadcrumb({
type: 'custom',
category: 'http',
message: `${request.method} ${new URL(request.url).pathname}`,
timestamp: Date.now(),
data: { requestId },
});
})
.onAfterResponse(({ request, set, requestId }) => {
hub.captureLog('info', 'request completed', {
requestId,
method: request.method,
path: new URL(request.url).pathname,
status: set.status,
});
})
.get('/orders', () => ({ orders: [] }));
In onAfterResponse the value you get is the handler’s return value, not a Response, so read the status and headers from set.
Scoped hooks with guard
guard lets you attach hooks to a group of routes, for example to log only the admin API at a higher detail level:
app.guard(
{
beforeHandle: ({ request }) => {
hub.captureLog('info', 'admin access', { path: new URL(request.url).pathname });
},
},
(admin) => admin.get('/admin/users', () => []),
);
Error Handling
The plugin captures the error first, so your own onError only has to shape the response. Register it before the routes it should cover. code is one of Elysia’s error codes such as NOT_FOUND, VALIDATION, PARSE, INTERNAL_SERVER_ERROR or UNKNOWN:
app
.use(logtide({
dsn: process.env.LOGTIDE_DSN,
service: 'elysia-api',
}))
.onError(({ code, error }) => {
// Already captured by the plugin
const status = code === 'NOT_FOUND' ? 404 : code === 'VALIDATION' ? 422 : 500;
return new Response(JSON.stringify({ error: String(error) }), {
status,
headers: { 'Content-Type': 'application/json' },
});
});
If your handler returns nothing, Elysia sends the error by its own rules.
Graceful Shutdown
The plugin does not register a shutdown hook, so flush explicitly when the process gets a termination signal:
const app = new Elysia()
.use(logtide({ dsn: process.env.LOGTIDE_DSN, service: 'elysia-api' }))
.listen(3000);
async function shutdown() {
await app.stop(); // stop accepting requests
await hub.close(); // flush buffered events and disable further capture
process.exit(0);
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
In containers, SIGTERM is what the orchestrator sends first, so this is the path that matters for Docker deployments.
Next Steps
- JavaScript SDK - Core SDK reference
- Elysia SDK Reference - Full API documentation
- Docker Integration - Container deployments
Frequently Asked Questions
How do I add LogTide logging to an Elysia application?
Install @logtide/elysia with bun add @logtide/elysia, then call app.use(logtide({ dsn, service, environment })) in your Elysia setup. The plugin is already registered as global internally, so it covers all routes in the app.
Does the LogTide Elysia plugin automatically log every request?
Yes. The plugin hooks into Elysia's onRequest lifecycle to start a span and extract any incoming traceparent header, and into onAfterHandle to finish the span with the response status and duration, with no per-route code required.
How does LogTide handle Elysia errors and exceptions?
The plugin registers an onError lifecycle hook that captures errors with full request context automatically. You can still add your own onError handler to return a custom response; the error is already recorded by LogTide before your handler runs.
Does LogTide flush pending logs when the Elysia server stops?
Buffered events are flushed automatically on the batching interval. For graceful shutdown, call hub.flush() (or hub.close()) before the process exits to ensure no logs are lost - the plugin does not register a shutdown hook itself.