Pino
用 Pino 做高性能结构化日志
Elogs 集成了 Pino 作为底层日志引擎,挂载在 Elysia store 的 pino 字段上 —— 所有 pino.LoggerOptions 都通过 config.pino 透传,不需要单独 import。
为什么选 Pino
- 高性能 —— 业界最快的 Node.js logger 之一
- 结构化日志 —— JSON 优先
- 生产验证 —— 大规模生产环境使用
- 灵活 —— 丰富的选项和 transport 支持
基本用法
在 Elysia route 里
在 route handler 里通过 store 拿 Pino:
import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'
const app = new Elysia().use(createElogs())
app.get('/users/:id', ({ store, params }) => {
const { pino } = store
pino.info({
userId: params.id,
action: 'view_profile',
}, 'User profile accessed')
return { user: 'data' }
})
或者通过请求作用域 log(useAsyncLocalStorage: true 时,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' }
})
在 Elysia route 之外(standalone)
在服务、后台任务、或任何 HTTP 请求上下文外的地方用 Pino:
// logger.ts
import { createElogs } from '@eastgold15/elogs'
export const elogsIns = createElogs({
config: {
logFilePath: './logs/app.log',
pino: {
level: 'info',
base: {
service: 'my-api',
version: '1.0.0',
},
},
},
})
// 导出 Pino 实例给 standalone 用
export const logger = elogsIns.store.pino
// services/order.service.ts
import { logger } from '../logger'
export class OrderService {
async processOrder(orderId: string) {
logger.info({ orderId }, 'Processing order')
try {
// ... 业务逻辑
logger.info({ orderId }, 'Order processed successfully')
} catch (error) {
logger.error({ orderId, error }, 'Order processing failed')
throw error
}
}
}
// index.ts
import { Elysia } from 'elysia'
import { elogsIns } from './logger'
import { OrderService } from './services/order.service'
const orderService = new OrderService()
new Elysia()
.use(elogsIns)
.post('/orders/:id/process', async ({ params }) => {
await orderService.processOrder(params.id)
return { success: true }
})
.listen(3000)
注意: 在 route 外用 Pino 没有 HTTP context(method、pathname、IP 等)。HTTP 请求日志请在 route handler 里用 store.logger 或 store.pino。
配置
config.pino 字段是 PinoConfig 类型,实际定义是:
interface PinoConfig {
/** 显式禁用 pino(测试中常用) */
enabled?: boolean
/** Pretty 打印(pino-pretty) */
prettyPrint?: boolean
/** 其他 pino options 透传(走 [key: string]: unknown) */
[key: string]: unknown
}
[key: string]: unknown 索引签名让你能透传任何 pino.LoggerOptions(level、redact、transport、base、timestamp、formatters、serializers 等)。下面是常用配置:
日志级别
import { createElogs } from '@eastgold15/elogs'
createElogs({
config: {
pino: {
level: 'debug', // 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent'
},
},
})
美化输出
import { createElogs } from '@eastgold15/elogs'
createElogs({
config: {
pino: {
prettyPrint: true,
},
},
})
或者自定义:
import { createElogs } from '@eastgold15/elogs'
createElogs({
config: {
pino: {
prettyPrint: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
},
})
prettyPrint 透传给 pino-pretty。要本地开发用漂亮输出,装一下 pino-pretty(peer dep,可选):
bun add pino-pretty
脱敏
Pino 自己的 redact 跟 Elogs 的 autoRedact 独立,可以同时跑:
import { createElogs } from '@eastgold15/elogs'
createElogs({
config: {
autoRedact: true, // Elogs: 改写 meta + headers
pino: { redact: ['password'] }, // Pino: 擦掉 JSON 输出
},
})
用 paths + remove 删掉整个 key:
createElogs({
config: {
pino: {
redact: {
paths: ['user.password', 'req.headers.authorization'],
remove: true,
},
},
},
})
base 字段
import { createElogs } from '@eastgold15/elogs'
createElogs({
config: {
pino: {
base: {
service: 'my-api',
version: '1.0.0',
environment: process.env.NODE_ENV,
},
},
},
})
Pino transport
Pino 有自己的 transport 体系,处理高级日志目标。用 transport 选项配置:
import { createElogs } from '@eastgold15/elogs'
createElogs({
config: {
pino: {
transport: {
target: 'pino/file',
options: { destination: './logs/pino-example.log' },
},
},
},
})
重要: Pino transport 和 Elogs transport 是独立的:
- Pino transport(
config.pino.transport)—— 影响store.pino.info()、store.pino.error()等写出的日志 - Elogs transport(
config.transports)—— 影响 HTTP access log 管道
import { createElogs } from '@eastgold15/elogs'
const customTransport = {
log(level, message, meta) {
// 你的实现
},
}
createElogs({
config: {
// Pino transport —— 只对 store.pino.* 日志生效
pino: {
transport: { target: 'pino/file', options: { destination: './logs/pino.log' } },
},
// Elogs transport —— 只对 HTTP access log 生效
transports: [customTransport],
},
})
app.get('/example', ({ store }) => {
store.pino.info({ feature: 'pino' }, 'pino log example')
// → 走 pino/file transport
// HTTP access log 走 Elogs transport
})
进阶用法
结构化日志
app.get('/users/:id', ({ store, params }) => {
const { pino } = store
pino.info({
userId: params.id,
action: 'view_profile',
timestamp: Date.now(),
}, 'User profile viewed')
return user
})
Child logger
派生绑定 context 的 child logger:
app.get('/api/orders/:id', ({ store, params }) => {
const { pino } = store
const orderLogger = pino.child({
orderId: params.id,
module: 'order-service',
})
orderLogger.info('Fetching order details')
orderLogger.debug({ query: 'SELECT * FROM orders WHERE id = ?' })
return order
})
错误日志
app.get('/api/risky', ({ store }) => {
const { pino } = store
try {
performRiskyOperation()
} catch (error) {
pino.error({ err: error, operation: 'risky_operation' }, 'Operation failed')
throw error
}
})
按环境配置
import { createElogs } from '@eastgold15/elogs'
const isDev = process.env.NODE_ENV === 'development'
createElogs({
config: {
pino: {
level: isDev ? 'debug' : 'info',
prettyPrint: isDev,
redact: isDev ? [] : ['password', 'token', 'apiKey'],
base: {
service: 'my-api',
environment: process.env.NODE_ENV,
},
},
},
})
dev preset 已经预设了 pino: { prettyPrint: true },开发环境通常直接用:
import { createElogs } from '@eastgold15/elogs'
createElogs({ preset: 'dev' })
最佳实践
-
用结构化日志 —— 传对象,不要字符串拼接
// ✅ pino.info({ userId: 123, action: 'login' }, 'User logged in') // ❌ pino.info(`User ${userId} logged in`) -
派生 child logger 来绑 context
-
记性能指标 —— 关键操作
pino.info({ duration: 150, operation: 'db_query' }, 'Query completed') -
脱敏 ——
autoRedact(Elogs)和pino.redact(Pino)都开 -
正确选级别:
fatal—— 应用即将崩溃error—— 需要关注的错误warn—— 警告,应用继续info—— 一般信息(默认)debug—— 详细调试trace—— 极详细调试silent—— 完全关掉
-
导出 Pino 给 standalone 用 —— 在服务、job、工具里
export const logger = elogsIns.store.pino