backend-pino

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pino (Structured Logging)

Pino(结构化日志工具)

Overview

概述

Pino is the fastest JSON logger for Node.js. It outputs structured logs that integrate with observability platforms (Datadog, ELK, Splunk, CloudWatch).
Version: v9.x (2024-2025)
Performance: ~5x faster than Winston, ~10x faster than Bunyan
Key Benefit: Structured JSON logs → easy parsing, filtering, and alerting in production.
Pino是Node.js中最快的JSON日志工具。它输出的结构化日志可与可观测性平台(Datadog、ELK、Splunk、CloudWatch)集成。
版本: v9.x(2024-2025)
性能: 比Winston快约5倍,比Bunyan快约10倍
核心优势: 结构化JSON日志 → 便于生产环境中的解析、过滤和告警。

When to Use This Skill

适用场景

Use Pino when:
  • Building production APIs
  • Need structured JSON logs
  • Integrating with observability platforms
  • Require request tracing (correlation IDs)
  • Must redact sensitive data (passwords, tokens)
Skip Pino when:
  • Simple scripts or CLI tools
  • Early prototyping (console.log is fine)
  • Client-side JavaScript

推荐使用Pino的场景:
  • 构建生产级API
  • 需要结构化JSON日志
  • 与可观测性平台集成
  • 要求请求追踪(关联ID)
  • 必须脱敏敏感数据(密码、令牌)
不推荐使用Pino的场景:
  • 简单脚本或CLI工具
  • 早期原型开发(使用console.log即可)
  • 客户端JavaScript

Quick Start

快速开始

Installation

安装

bash
npm install pino pino-pretty
npm install -D @types/pino
bash
npm install pino pino-pretty
npm install -D @types/pino

Basic Configuration

基础配置

typescript
// src/lib/logger.ts
import pino from 'pino';

const isDev = process.env.NODE_ENV === 'development';

export const logger = pino({
  level: process.env.LOG_LEVEL || (isDev ? 'debug' : 'info'),
  
  // Pretty print in development
  transport: isDev ? {
    target: 'pino-pretty',
    options: {
      colorize: true,
      translateTime: 'SYS:standard',
      ignore: 'pid,hostname',
    },
  } : undefined,
  
  // Redact sensitive fields
  redact: {
    paths: [
      'password',
      'token',
      'authorization',
      'cookie',
      '*.password',
      '*.token',
      'req.headers.authorization',
    ],
    censor: '[REDACTED]',
  },
  
  // Add base context to all logs
  base: {
    service: process.env.SERVICE_NAME || 'api',
    env: process.env.NODE_ENV,
    version: process.env.APP_VERSION,
  },
});

export default logger;

typescript
// src/lib/logger.ts
import pino from 'pino';

const isDev = process.env.NODE_ENV === 'development';

export const logger = pino({
  level: process.env.LOG_LEVEL || (isDev ? 'debug' : 'info'),
  
  // 开发环境下启用美观打印
  transport: isDev ? {
    target: 'pino-pretty',
    options: {
      colorize: true,
      translateTime: 'SYS:standard',
      ignore: 'pid,hostname',
    },
  } : undefined,
  
  // 脱敏敏感字段
  redact: {
    paths: [
      'password',
      'token',
      'authorization',
      'cookie',
      '*.password',
      '*.token',
      'req.headers.authorization',
    ],
    censor: '[REDACTED]',
  },
  
  // 为所有日志添加基础上下文
  base: {
    service: process.env.SERVICE_NAME || 'api',
    env: process.env.NODE_ENV,
    version: process.env.APP_VERSION,
  },
});

export default logger;

Log Levels

日志级别

typescript
// In order of severity (lowest to highest)
logger.trace('Detailed debugging');     // level 10
logger.debug('Debug information');      // level 20
logger.info('Normal operation');        // level 30
logger.warn('Warning condition');       // level 40
logger.error('Error occurred');         // level 50
logger.fatal('App is crashing');        // level 60
typescript
// 按严重程度从低到高排序
logger.trace('详细调试信息');     // 级别10
logger.debug('调试信息');      // 级别20
logger.info('正常运行记录');        // 级别30
logger.warn('警告情况');       // 级别40
logger.error('发生错误');         // 级别50
logger.fatal('应用崩溃');        // 级别60

Level Configuration

级别配置

typescript
// Set via environment
LOG_LEVEL=debug npm start

// Or in code
const logger = pino({ level: 'debug' });

typescript
// 通过环境变量设置
LOG_LEVEL=debug npm start

// 或在代码中设置
const logger = pino({ level: 'debug' });

Structured Logging

结构化日志

Log Objects, Not Strings

记录对象而非字符串

typescript
// ❌ Avoid string interpolation
logger.info(`User ${userId} logged in from ${ip}`);

// ✅ Use structured objects
logger.info({ userId, ip, action: 'login' }, 'User logged in');
typescript
// ❌ 避免字符串插值
logger.info(`User ${userId} logged in from ${ip}`);

// ✅ 使用结构化对象
logger.info({ userId, ip, action: 'login' }, '用户登录');

Output (JSON)

输出(JSON格式)

json
{
  "level": 30,
  "time": 1702300800000,
  "service": "api",
  "userId": "user_123",
  "ip": "192.168.1.1",
  "action": "login",
  "msg": "User logged in"
}

json
{
  "level": 30,
  "time": 1702300800000,
  "service": "api",
  "userId": "user_123",
  "ip": "192.168.1.1",
  "action": "login",
  "msg": "用户登录"
}

Child Loggers (Context)

子日志器(上下文传递)

Request Context

请求上下文

typescript
// Create child logger with request context
const requestLogger = logger.child({
  requestId: 'req_abc123',
  userId: 'user_456',
});

// All logs include context automatically
requestLogger.info('Processing request');
requestLogger.info({ orderId: 'order_789' }, 'Order created');
typescript
// 创建包含请求上下文的子日志器
const requestLogger = logger.child({
  requestId: 'req_abc123',
  userId: 'user_456',
});

// 所有日志将自动包含上下文
requestLogger.info('处理请求中');
requestLogger.info({ orderId: 'order_789' }, '订单已创建');

Output

输出

json
{
  "requestId": "req_abc123",
  "userId": "user_456",
  "msg": "Processing request"
}
{
  "requestId": "req_abc123",
  "userId": "user_456",
  "orderId": "order_789",
  "msg": "Order created"
}

json
{
  "requestId": "req_abc123",
  "userId": "user_456",
  "msg": "处理请求中"
}
{
  "requestId": "req_abc123",
  "userId": "user_456",
  "orderId": "order_789",
  "msg": "订单已创建"
}

Express Request Logging

Express请求日志中间件

Middleware

中间件实现

typescript
// src/middleware/request-logger.ts
import { randomUUID } from 'crypto';
import { Request, Response, NextFunction } from 'express';
import { logger } from '../lib/logger';

// Extend Express Request type
declare global {
  namespace Express {
    interface Request {
      log: typeof logger;
      requestId: string;
    }
  }
}

export function requestLogger(req: Request, res: Response, next: NextFunction) {
  const requestId = (req.headers['x-request-id'] as string) || randomUUID();
  const start = Date.now();

  // Create child logger with request context
  const childLogger = logger.child({
    requestId,
    method: req.method,
    path: req.path,
    userAgent: req.headers['user-agent'],
  });

  // Attach to request for use in handlers
  req.log = childLogger;
  req.requestId = requestId;

  // Set response header for tracing
  res.setHeader('x-request-id', requestId);

  // Log request start
  childLogger.info('Request started');

  // Log request completion
  res.on('finish', () => {
    childLogger.info({
      statusCode: res.statusCode,
      duration: Date.now() - start,
    }, 'Request completed');
  });

  next();
}
typescript
// src/middleware/request-logger.ts
import { randomUUID } from 'crypto';
import { Request, Response, NextFunction } from 'express';
import { logger } from '../lib/logger';

// 扩展Express Request类型
declare global {
  namespace Express {
    interface Request {
      log: typeof logger;
      requestId: string;
    }
  }
}

export function requestLogger(req: Request, res: Response, next: NextFunction) {
  const requestId = (req.headers['x-request-id'] as string) || randomUUID();
  const start = Date.now();

  // 创建包含请求上下文的子日志器
  const childLogger = logger.child({
    requestId,
    method: req.method,
    path: req.path,
    userAgent: req.headers['user-agent'],
  });

  // 将日志器附加到请求对象,供后续处理器使用
  req.log = childLogger;
  req.requestId = requestId;

  // 设置响应头用于追踪
  res.setHeader('x-request-id', requestId);

  // 记录请求开始
  childLogger.info('请求已启动');

  // 记录请求完成
  res.on('finish', () => {
    childLogger.info({
      statusCode: res.statusCode,
      duration: Date.now() - start,
    }, '请求已完成');
  });

  next();
}

Usage in Express

在Express中使用

typescript
// src/app.ts
import express from 'express';
import { requestLogger } from './middleware/request-logger';

const app = express();
app.use(requestLogger);

app.get('/users/:id', async (req, res) => {
  req.log.info({ userId: req.params.id }, 'Fetching user');
  
  try {
    const user = await getUser(req.params.id);
    req.log.debug({ user }, 'User found');
    res.json(user);
  } catch (error) {
    req.log.error({ error }, 'Failed to fetch user');
    res.status(500).json({ error: 'Internal error' });
  }
});

typescript
// src/app.ts
import express from 'express';
import { requestLogger } from './middleware/request-logger';

const app = express();
app.use(requestLogger);

app.get('/users/:id', async (req, res) => {
  req.log.info({ userId: req.params.id }, '获取用户信息');
  
  try {
    const user = await getUser(req.params.id);
    req.log.debug({ user }, '用户信息已找到');
    res.json(user);
  } catch (error) {
    req.log.error({ error }, '获取用户信息失败');
    res.status(500).json({ error: '内部错误' });
  }
});

tRPC Logging Middleware

tRPC日志中间件

typescript
// src/server/middleware/logging.ts
import { middleware } from '../trpc';
import { logger } from '@/lib/logger';

export const loggerMiddleware = middleware(async ({ path, type, next, ctx }) => {
  const start = Date.now();
  const log = ctx.log || logger;
  
  log.debug({ path, type }, 'tRPC procedure started');

  try {
    const result = await next();
    
    log.info({
      path,
      type,
      duration: Date.now() - start,
    }, 'tRPC procedure completed');
    
    return result;
  } catch (error) {
    log.error({
      path,
      type,
      duration: Date.now() - start,
      error,
    }, 'tRPC procedure failed');
    
    throw error;
  }
});

// Apply to all procedures
export const loggedProcedure = publicProcedure.use(loggerMiddleware);

typescript
// src/server/middleware/logging.ts
import { middleware } from '../trpc';
import { logger } from '@/lib/logger';

export const loggerMiddleware = middleware(async ({ path, type, next, ctx }) => {
  const start = Date.now();
  const log = ctx.log || logger;
  
  log.debug({ path, type }, 'tRPC流程已启动');

  try {
    const result = await next();
    
    log.info({
      path,
      type,
      duration: Date.now() - start,
    }, 'tRPC流程已完成');
    
    return result;
  } catch (error) {
    log.error({
      path,
      type,
      duration: Date.now() - start,
      error,
    }, 'tRPC流程执行失败');
    
    throw error;
  }
});

// 应用到所有流程
export const loggedProcedure = publicProcedure.use(loggerMiddleware);

Error Logging

错误日志

With Stack Traces

包含堆栈跟踪

typescript
try {
  await riskyOperation();
} catch (error) {
  // Pino serializes Error objects automatically
  logger.error({ err: error }, 'Operation failed');
}
typescript
try {
  await riskyOperation();
} catch (error) {
  // Pino会自动序列化Error对象
  logger.error({ err: error }, '操作执行失败');
}

Custom Error Serializer

自定义错误序列化器

typescript
const logger = pino({
  serializers: {
    err: pino.stdSerializers.err,  // Default error serializer
    error: (error) => ({
      type: error.constructor.name,
      message: error.message,
      stack: error.stack,
      code: error.code,
      // Add custom fields
      ...(error.details && { details: error.details }),
    }),
  },
});

typescript
const logger = pino({
  serializers: {
    err: pino.stdSerializers.err,  // 默认错误序列化器
    error: (error) => ({
      type: error.constructor.name,
      message: error.message,
      stack: error.stack,
      code: error.code,
      // 添加自定义字段
      ...(error.details && { details: error.details }),
    }),
  },
});

Sensitive Data Redaction

敏感数据脱敏

Configuration

配置

typescript
const logger = pino({
  redact: {
    paths: [
      'password',
      'secret',
      'token',
      'apiKey',
      'authorization',
      'cookie',
      'creditCard',
      '*.password',           // Nested fields
      '*.secret',
      'req.headers.cookie',
      'req.headers.authorization',
      'user.email',           // PII
    ],
    censor: '[REDACTED]',
    remove: false,            // Keep key, redact value
  },
});
typescript
const logger = pino({
  redact: {
    paths: [
      'password',
      'secret',
      'token',
      'apiKey',
      'authorization',
      'cookie',
      'creditCard',
      '*.password',           // 嵌套字段
      '*.secret',
      'req.headers.cookie',
      'req.headers.authorization',
      'user.email',           // 个人可识别信息
    ],
    censor: '[REDACTED]',
    remove: false,            // 保留键名,仅脱敏值
  },
});

Output

输出

json
{
  "user": {
    "id": "123",
    "email": "[REDACTED]",
    "password": "[REDACTED]"
  }
}

json
{
  "user": {
    "id": "123",
    "email": "[REDACTED]",
    "password": "[REDACTED]"
  }
}

Production Configuration

生产环境配置

typescript
// src/lib/logger.ts
import pino from 'pino';

const isProduction = process.env.NODE_ENV === 'production';

export const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  
  // No transport in production (JSON to stdout)
  transport: isProduction ? undefined : {
    target: 'pino-pretty',
  },
  
  // Faster serialization in production
  formatters: {
    level: (label) => ({ level: label }),
  },
  
  // ISO timestamp
  timestamp: pino.stdTimeFunctions.isoTime,
  
  // Redaction
  redact: ['password', 'token', '*.password'],
  
  // Base context
  base: {
    service: process.env.SERVICE_NAME,
    version: process.env.APP_VERSION,
    env: process.env.NODE_ENV,
  },
});

typescript
// src/lib/logger.ts
import pino from 'pino';

const isProduction = process.env.NODE_ENV === 'production';

export const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  
  // 生产环境不使用传输器(直接输出JSON到标准输出)
  transport: isProduction ? undefined : {
    target: 'pino-pretty',
  },
  
  // 生产环境下更快的序列化
  formatters: {
    level: (label) => ({ level: label }),
  },
  
  // ISO格式时间戳
  timestamp: pino.stdTimeFunctions.isoTime,
  
  // 脱敏配置
  redact: ['password', 'token', '*.password'],
  
  // 基础上下文
  base: {
    service: process.env.SERVICE_NAME,
    version: process.env.APP_VERSION,
    env: process.env.NODE_ENV,
  },
});

Rules

最佳实践

Do ✅

推荐做法 ✅

  • Use structured objects, not string interpolation
  • Create child loggers for request context
  • Redact sensitive data (passwords, tokens, PII)
  • Include correlation IDs for tracing
  • Use appropriate log levels
  • Log errors with
    { err: error }
  • 使用结构化对象,而非字符串插值
  • 为请求上下文创建子日志器
  • 脱敏敏感数据(密码、令牌、个人可识别信息)
  • 包含关联ID用于追踪
  • 使用合适的日志级别
  • 使用
    { err: error }
    记录错误

Avoid ❌

避免做法 ❌

  • console.log()
    in production code
  • Logging sensitive data (passwords, tokens)
  • String interpolation for structured data
  • Excessive debug logging in production
  • Blocking I/O in log transports

  • 在生产代码中使用
    console.log()
  • 记录敏感数据(密码、令牌)
  • 对结构化数据使用字符串插值
  • 生产环境中过度打印调试日志
  • 在日志传输中使用阻塞式I/O

Log Level Guidelines

日志级别指南

LevelUse CaseProduction
trace
Very detailed debuggingOff
debug
Development debuggingOff
info
Normal operationsOn
warn
Potential issuesOn
error
Errors (handled)On
fatal
App crashingOn

级别使用场景生产环境是否启用
trace
极详细的调试信息关闭
debug
开发调试使用关闭
info
正常运行记录开启
warn
潜在问题预警开启
error
已处理的错误开启
fatal
应用崩溃级错误开启

Troubleshooting

故障排查

yaml
"Logs not appearing":
  → Check LOG_LEVEL environment variable
  → Verify level: logger.level returns current level
  → Debug level is often disabled in production

"pino-pretty not working":
  → Only use in development
  → Check transport configuration
  → npm install pino-pretty

"Sensitive data in logs":
  → Add paths to redact array
  → Use wildcards: '*.password'
  → Verify with test logs

"Performance issues":
  → Remove pino-pretty in production
  → Reduce log level
  → Check for sync logging (avoid)

yaml
"日志不显示":
  → 检查LOG_LEVEL环境变量
  → 验证当前级别:logger.level返回的当前级别
  → 生产环境通常禁用debug级别

"pino-pretty不生效":
  → 仅在开发环境使用
  → 检查传输器配置
  → 确认已安装pino-pretty

"日志中出现敏感数据":
  → 将对应路径添加到redact数组
  → 使用通配符:'*.password'
  → 通过测试日志验证

"性能问题":
  → 生产环境移除pino-pretty
  → 降低日志级别
  → 检查是否存在同步日志操作(避免使用)

Integration with Observability

与可观测性平台集成

Datadog

Datadog

typescript
// Datadog expects JSON logs to stdout
const logger = pino({
  formatters: {
    level: (label) => ({ level: label }),
  },
  // Datadog trace correlation
  mixin: () => ({
    dd: {
      trace_id: getCurrentTraceId(),
      span_id: getCurrentSpanId(),
    },
  }),
});
typescript
// Datadog期望JSON日志输出到标准输出
const logger = pino({
  formatters: {
    level: (label) => ({ level: label }),
  },
  // Datadog追踪关联
  mixin: () => ({
    dd: {
      trace_id: getCurrentTraceId(),
      span_id: getCurrentSpanId(),
    },
  }),
});

ELK Stack

ELK栈

typescript
// Elasticsearch-friendly format
const logger = pino({
  timestamp: pino.stdTimeFunctions.isoTime,
  formatters: {
    level: (label) => ({ level: label }),
  },
});

typescript
// 适配Elasticsearch的格式
const logger = pino({
  timestamp: pino.stdTimeFunctions.isoTime,
  formatters: {
    level: (label) => ({ level: label }),
  },
});

File Structure

文件结构

src/
├── lib/
│   └── logger.ts           # Logger configuration
├── middleware/
│   └── request-logger.ts   # Express middleware
└── server/
    └── middleware/
        └── logging.ts      # tRPC middleware
src/
├── lib/
│   └── logger.ts           # 日志工具配置
├── middleware/
│   └── request-logger.ts   # Express中间件
└── server/
    └── middleware/
        └── logging.ts      # tRPC中间件

References

参考链接