Migrating from evlog
Map evlog wide events to Logixlysia request context and access logs
Plugin setup
evlog
import { Elysia } from 'elysia'
import { evlog } from 'evlog/elysia'
new Elysia().use(evlog({ drain: myDrain }))
Logixlysia
import { Elysia } from 'elysia'
import logixlysia from 'logixlysia'
new Elysia().use(logixlysia({ preset: 'prod' }))
Context accumulation
log is always on the handler context; outside a handler, store.logger takes the request explicitly, and useLogger() needs useAsyncLocalStorage: true (it is a no-op without it).
| evlog | Logixlysia, in a handler | Logixlysia, with a request in hand |
|---|---|---|
log.set({ userId: 'x' }) |
({ log }) => log.mergeContext({ userId: 'x' }) |
store.logger.mergeContext(request, { userId: 'x' }) |
log.set({ cart: { total: 99 } }) |
({ log }) => log.mergeContext({ cart: { total: 99 } }) |
store.logger.mergeContext(request, { cart: { total: 99 } }) |
useLogger() |
useLogger() from logixlysia |
useLogger() from logixlysia |
Emitting the request log
evlog:
log.emit({ status: 200 })
Logixlysia emits the access log automatically once Elysia has produced the response. Use a custom log only when you need to skip the default line:
store.logger.info(request, 'handled manually')
AI metrics
evlog — automatic via evlog/ai.
Logixlysia
import { mergeAIMetrics } from 'logixlysia/ai'
mergeAIMetrics(store.logger, request, {
model: 'claude-sonnet',
inputTokens: 100,
outputTokens: 50,
totalTokens: 150
})
Tracing
Use injectTraceContext from logixlysia/otel in onRequest (see OpenTelemetry).
Drains vs transports
evlog drains map to Logixlysia adapters, which are transports:
| evlog drain | Logixlysia adapter |
|---|---|
evlog/axiom |
createAxiomTransport() from logixlysia/axiom |
evlog/better-stack |
createBetterStackTransport() from logixlysia/better-stack |
evlog/clickhouse |
createClickHouseTransport() from logixlysia/clickhouse |
evlog/datadog |
createDatadogTransport() from logixlysia/datadog |
evlog/hyperdx |
createHyperDXTransport() from logixlysia/hyperdx |
evlog/loki |
createLokiTransport() from logixlysia/loki |
evlog/otlp |
createOtlpTransport() from logixlysia/otlp |
evlog/posthog |
createPostHogTransport() from logixlysia/posthog |
evlog/sentry |
createSentryTransport() from logixlysia/sentry |
import logixlysia from 'logixlysia'
import { createAxiomTransport } from 'logixlysia/axiom'
logixlysia({
config: {
transports: [createAxiomTransport()],
useTransportsOnly: true
}
})
Anything without a matching adapter falls back to a custom transport:
logixlysia({
config: {
transports: [
{
log: (level, message, meta) => {
myDrain.write({ level, message, ...meta })
}
}
],
useTransportsOnly: true
}
})
Sampling
evlog’s sampling.rates maps to config.sampling.head. evlog’s tail keep maps to config.sampling.tail rules by status, duration, or path. Logixlysia has no predicate form: you write the rule directly.
logixlysia({
config: {
sampling: {
head: { DEBUG: 1, INFO: 10 },
tail: { status: 400, durationMs: 1000, paths: ['/checkout/**'] }
}
}
})
See Sampling.
Enrichers
config.enrichers runs contributors on every request and merges their output into the request context. logixlysia/enrichers ships traceparentEnricher, userAgentEnricher, geoEnricher, and sizeEnricher.
import logixlysia from 'logixlysia'
import { geoEnricher, traceparentEnricher } from 'logixlysia/enrichers'
logixlysia({
config: {
enrichers: [traceparentEnricher(), geoEnricher()]
}
})
See Enrichers.
Structured errors
evlog’s createError({ why, fix }) maps to Logixlysia’s HttpError:
import { HttpError } from 'logixlysia'
throw new HttpError(402, 'Card declined', {
code: 'PAYMENT_DECLINED',
why: 'The issuing bank rejected the charge.',
fix: 'Try a different card, or contact your bank.',
link: 'https://docs.example.com/errors/payment-declined',
internal: { gatewayCode: 'do_not_honor' }
})
code, why, fix, and link appear in both the log and the response. internal appears only in the log.