Skip to content
Logixlysia
Esc
navigateopen⌘Jpreview
On this page

Request context

Accumulate request-scoped fields into a single access log line

Logixlysia can accumulate context during a request and merge it into the automatic access log—similar to evlog wide events, without replacing your existing logger.info() API.

Basic usage

import { Elysia } from 'elysia'
import logixlysia from 'logixlysia'

const app = new Elysia()
  .use(logixlysia())
  .get('/checkout', ({ request, store }) => {
    store.logger.mergeContext(request, { userId: 'usr_123' })
    store.logger.mergeContext(request, { cartTotal: 9999 })
    return { ok: true }
  })

The final INFO access log includes a context object with userId and cartTotal (and renders as a context tree when showContextTree is enabled).

Precedence

When you call logger.info(request, message, { ...explicit }), keys in the explicit context override accumulated values. Non-colliding keys from both sources are kept.

Reading context

const ctx = store.logger.getContext(request)

Request-Scoped Logger & AsyncLocalStorage

Instead of manually passing the request object to store.logger methods (e.g. store.logger.info(request, message)), you can enable AsyncLocalStorage propagation:

logixlysia({
  config: {
    useAsyncLocalStorage: true
  }
})

When enabled, a request-scoped logger log is derived automatically on the Elysia handler context. You can also retrieve the active logger globally in nested service layers or helper functions using the useLogger() hook.

1. Handler Context (log)

Destructure log directly in your route handlers:

app.get('/user/:id', ({ log, params }) => {
  log.mergeContext({ userId: params.id })
  log.info('Fetched user profile')
  return { success: true }
})

2. Global Hook (useLogger())

Import useLogger() to log or merge context from deeply nested operations without prop-drilling the request context:

import { useLogger } from 'logixlysia'

async function findUser(id: string) {
  const log = useLogger()
  log.mergeContext({ action: 'db_query' })
  log.info('Executing database lookup...')
  
  // Database lookup logic...
  return { id, name: 'John Doe' }
}

Typed Fields

userId on one route and user_id on the next is the kind of drift that makes a dashboard query quietly incomplete. Pass a field type to the plugin and TypeScript catches it at compile time:

import { Elysia } from 'elysia'
import logixlysia from 'logixlysia'

interface CheckoutFields {
  cartId: string
  itemCount: number
  userId: string
}

const app = new Elysia()
  .use(logixlysia<CheckoutFields>())
  .post('/pay', ({ log }) => {
    log.mergeContext({ userId: 'usr_123' }) // ✓
    log.info('charged', { itemCount: 3 }) // ✓

    log.mergeContext({ user_id: 'usr_123' }) // ✗ not in CheckoutFields
    log.mergeContext({ itemCount: 'three' }) // ✗ wrong type
    return { ok: true }
  })

Every field stays optional — you declare the vocabulary, not a requirement to fill it in on every route.

useLogger() takes the same parameter, so context stays typed outside the handler too:

import { useLogger } from 'logixlysia'

const chargeCard = async (amount: number) => {
  const log = useLogger<CheckoutFields>()
  log.mergeContext({ cartId: 'cart_9' })
}

This is type-only: there is no runtime cost, and leaving the parameter off keeps every key allowed, exactly as before.

Last updated on August 23, 2026

Was this page helpful?