TypeScript 应用程序中的日志记录:实践中的最佳实践
日志记录是任何生产级应用程序的基础部分。它不仅允许跟踪代码执行和查找错误,还能进行安全审计、分析性能以及调试复杂场景。在 TypeScript 中,凭借其强大的类型系统,日志记录方法变得更加灵活和可靠。在本文中,我们将通过具体示例分析日志记录的最佳实践。
1. 选择库并配置日志器
首先要做的是选择合适的库。在流行的解决方案中,winston 和 pino 脱颖而出。两者都原生支持 TypeScript,但各有侧重:winston 注重灵活性和丰富功能,pino 则追求极致性能。
让我们看一个带类型定义的 winston 配置示例:
import winston from 'winston';
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'user-service' }, transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }), new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }), ],});
export default logger;对于 pino,配置会更简洁,但同样高效:
import pino from 'pino';
const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', }, },});
export default logger;建议:始终将日志级别配置提取到环境变量中(LOG_LEVEL)。生产环境通常使用 info 或 warn,而开发环境则使用 debug。
2. 结构化日志记录与上下文
一个主要错误是记录简单的字符串。相反,应使用结构化对象。它们可以轻松地在 ELK、Grafana 或 Datadog 等系统中过滤和聚合日志。
不良日志记录示例:
logger.info('User logged in successfully');带上下文的良好日志记录示例:
logger.info({ message: 'User logged in', userId: user.id, email: user.email, loginMethod: 'oauth', ip: request.ip, duration: Date.now() - startTime,});为了方便,可以创建一个带类型的包装函数:
interface LogContext { requestId?: string; userId?: string; action?: string; [key: string]: unknown;}
function logWithContext(message: string, context: LogContext = {}): void { logger.info({ message, ...context });}
// 使用示例logWithContext('Order processed', { orderId: '12345', userId: 'user_678', total: 99.99 });这种方法确保每条日志都包含用于分析的关键信息。
3. 错误和异常日志记录
错误处理是日志记录中最重要的部分。始终记录完整的错误堆栈以及发生错误的上下文。不要使用 console.error——它无法提供日志轮转和格式化的灵活性。
使用 TypeScript 的正确模式:
import logger from './logger';
async function fetchUserData(userId: string): Promise<UserData> { try { const response = await api.get(`/users/${userId}`); ret