Skip to content
Elogs
Esc
navigateopen⌘Jpreview
On this page

Usage

在 Elysia 应用中使用 Elogs

安装

bun add @eastgold15/elogs elysia@next

next 标签对应 Elysia 2 open beta。如果你还在 Elysia 1.4,请见 Elysia 2 支持

基本用法

import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'

const app = new Elysia()
  .use(createElogs())
  .get('/', () => 'Hello World')
  .listen(3000)

createElogs() 返回一个 Elysia 插件。Logger 挂在 Elysia store 上,可以通过 store.logger 访问;请求作用域的 log 也会派生到 handler context 上(不需要再手动传 request)。插件同时注册 .request.afterHandle.error.setup 钩子,你不需要手动接线。

预设

三个 preset 一行设好环境默认。显式 config 字段永远覆盖 preset。

createElogs({ preset: 'dev' })   // 美化 console + 启动 banner
createElogs({ preset: 'prod' })  // JSON console + autoRedact + requestId
createElogs({ preset: 'json' })  // 极简 JSON console,无附加

也可以通过 registerPreset('staging', { ... }) 注册自己的预设。完整默认值见 Presets

配置

最小化

createElogs({
  config: {
    showStartupMessage: true,
    ip: true,
    autoRedact: false,
  },
})

请求作用域 context

在请求过程中累积字段,最终合并到 access log 的 context 下:

.get('/users/:id', ({ request, store, params }) => {
  store.logger.mergeContext(request, { userId: params.id })
  return { ok: true }
})

完整 API 含深调用栈用的 useLogger(),见 Request context

Request ID

开启 X-Request-Id 头透传用 requestId: true。插件入口读这个头,缺失时自动生成,出口回显——包括错误响应。

createElogs({ config: { requestId: true } })

Request ID

自定义日志格式

通过 customLogFormat 自定义 access log:

createElogs({
  config: {
    customLogFormat: '{now} {level} {duration}ms {method} {pathname} {status} {requestId}',
  },
})

可用的占位符:

占位符 描述 示例
{now} 当前时间戳 2026-08-15 10:00:00
{epoch} Unix 时间戳 1734729600
{level} 日志级别(DEBUGINFOWARNINGERROR INFO
{duration} 请求耗时(已格式化) 12ms1.5s
{method} HTTP 方法 GET
{pathname} 请求路径(别名:{path});logQueryParams: true 时带 query /users
{query} 原始 query 字符串 ?id=123
{status} 响应状态码 200
{statusText} HTTP 状态文本 Not Found
{message} 自定义消息 User profile accessed
{icon} Elogs 狐狸 🦊(颜色 + TTY 时为级别色块) 🦊
{speed} 慢请求徽章(耗时 ≥ verySlowThreshold ⚡ slow
{service} config.service 服务名前缀 [my-api]
{ip} 客户端 IP 地址 127.0.0.1
{context} context 的 JSON 表示(树关掉或空时显示在主行) {"id":1}
{requestId} X-Request-Id 值(未开启时为空) 8c2f…

日志过滤

logFilter.level 接受单个级别或数组:

createElogs({
  config: {
    logFilter: { level: ['ERROR', 'WARNING'] },
  },
})

根级有一个等价的 logLevel 简写:

createElogs({ logLevel: ['ERROR', 'WARNING'] })

Log Levels

Pino 集成

Elogs 由 Pino 驱动。Pino 实例可以通过 store.pino 访问,也可以从请求作用域 log 拿到:

app.get('/users/:id', async ({ log, params, store }) => {
  log.info({ userId: params.id, action: 'view_profile' }, 'profile accessed')
  // 或者想用原始 Pino:store.pino.info({ ... }, '...')
  return { user: 'data' }
})

配置 Pino 选项:

createElogs({
  config: {
    pino: {
      level: 'debug',
      prettyPrint: true,
      base: { service: 'my-api' },
    },
  },
})

Pino 在首次访问时按需构造 —— 错误的 Pino 选项只在真正 log 时才报错。如果想启动时立即校验,把 config.pino 设为真值,proxy 就会立刻初始化。

Pino

文件日志

createElogs({
  config: {
    logFilePath: './logs/app.log',
    logRotation: {
      maxSize: '10m',
      interval: '1d',
      maxFiles: '7d',
      compress: true,
    },
  },
})

文件 sink 通过 queueMicrotask 批写、缓存文件句柄,跨文件 rotation 用 keyed mutex 串行,不同文件之间不会互相阻塞。文件创建时默认 0o600(文件)和 0o700(目录);可以用 logFileMode / logDirMode 覆盖。

File LoggingLog Rotation

Transports

const consoleTransport = {
  log: (level, message, meta) => {
    console.log(`[${level}] ${message}`, meta)
  },
}

createElogs({
  config: {
    transports: [consoleTransport],
  },
})

transport 抛出的同步异常和异步 reject 会被捕获并打印到 console.error,同一 transport 5s 窗口内只输出一次,坏的 target 不会拖慢请求路径。

Transports

OpenTelemetry

bun add @opentelemetry/api
import { injectTraceContext } from '@eastgold15/elogs/otel'

app.request(({ request, store }) => {
  injectTraceContext(store.logger, request)
})

当存在 active span 时,trace_idspan_id 会合并进请求 context 包,出现在 access log 上。Elogs 不启动 OpenTelemetry SDK —— 你需要自己装 tracer provider 和 instrumentation。

OpenTelemetry

AI SDK 指标

import { mergeAIMetrics } from '@eastgold15/elogs/ai'

app.post('/chat', async ({ request, store }) => {
  // 在 AI SDK 调用之后:
  mergeAIMetrics(store.logger, request, {
    model: 'claude-sonnet',
    provider: 'anthropic',
    inputTokens: 1200,
    outputTokens: 400,
    totalTokens: 1600,
    msToFinish: 2300,
  })

  return { ok: true }
})

access log 的 context.ai 字段沿用 evlog wide event 形态,方便两边仪表盘直接对比迁移。

AI SDK

数据库错误翻译(Drizzle)

createElogs 上开 autoTranslate: { db: 'drizzle' },单点 onError 钩子就会把 Drizzle 抛出的 driver 错误(唯一约束、外键违反、连接失败等)翻译成合适的 HTTP 状态码来决定日志级别。翻译只影响日志输出,不劫持错误响应格式——原 error 继续传播,你的 .error("DrizzleError", ...) / Elysia 默认 application/problem+json 响应都按原逻辑走。

createElogs({
  autoTranslate: { db: 'drizzle' },
  config: { /* ... */ },
})

也可以手动在 try/catch 里调 translateDrizzleError(error)(来自 @eastgold15/elogs/translator)。详见 Database errors

AsyncLocalStorage

深调用栈(服务层、helper、请求里的子任务)想拿到请求作用域 logger,不用逐层传 request,开启 ALS 即可:

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

// 在任意位置:
import { useLogger } from '@eastgold15/elogs'

const log = useLogger()
log.mergeContext({ stage: 'db' })
log.info('query started')

Request context

输出控制

createElogs({
  config: {
    disableInternalLogger: false, // console 输出
    disableFileLogging: false,     // logFilePath 输出
    useTransportsOnly: false,      // 同时禁用上面两个,只走 transport
    transports: [/* ... */],
  },
})

路由拆分模式

跟 Elysia 2 推荐写法一致,把每个子路由写成 <App extends CreateElogs>(app: App) => app 的高阶函数,TypeScript 就能精确推断 store.logger / log 的类型:

import type { CreateElogs } from '@eastgold15/elogs'
import { createElogs } from '@eastgold15/elogs'
import { Elysia } from 'elysia'

const chatRouter = <App extends CreateElogs>(app: App) =>
  app.post('/chat', ({ request, store }) => {
    store.logger.info(request, 'chat received')
    return { ok: true }
  })

const app = new Elysia()
  .use(createElogs())
  .use(chatRouter)
  .listen(3000)

参考 apps/elysia/src/routers/ 下的真实用法。

示例

生产配置

import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'

const app = new Elysia().use(
  createElogs({
    preset: 'prod',
    autoTranslate: { db: 'drizzle' },
    config: {
      service: 'my-api',
      logFilePath: './logs/production.log',
      logRotation: { maxSize: '100m', interval: '1d', maxFiles: '30d', compress: true },
      logFilter: { level: ['ERROR', 'WARNING'] },
      pino: { level: 'info', redact: ['password', 'token', 'apiKey'] },
    },
  })
)

开发配置

import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'

const app = new Elysia().use(
  createElogs({
    preset: 'dev',
    config: {
      service: 'my-api',
      pino: { level: 'debug', prettyPrint: true },
    },
  })
)

Last updated on August 15, 2026

Was this page helpful?