您现在的位置是:网站首页 > 日志管理文章详情
日志管理
陈川
【
Node.js
】
59061人已围观
6454字
日志管理的重要性
日志是应用程序运行过程中产生的记录,用于追踪系统行为、排查问题和分析性能。良好的日志管理能帮助开发者快速定位问题,提高系统可维护性。Node.js作为异步事件驱动平台,日志管理需要考虑其特有的并发特性。
日志级别分类
常见的日志级别包括:
- DEBUG:详细的调试信息
- INFO:常规运行信息
- WARN:潜在问题警告
- ERROR:错误事件但不影响系统继续运行
- FATAL:严重错误导致应用崩溃
const levels = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
FATAL: 4
};
Node.js日志模块选择
Winston
Winston是Node.js最流行的日志库之一,支持多传输、自定义格式和日志级别:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// 开发环境添加控制台输出
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
Bunyan
Bunyan提供结构化日志和流式处理:
const bunyan = require('bunyan');
const log = bunyan.createLogger({
name: 'myapp',
streams: [
{
level: 'info',
path: '/var/tmp/myapp-info.log'
},
{
level: 'error',
path: '/var/tmp/myapp-error.log'
}
]
});
log.info('Server started on port %d', 3000);
日志格式最佳实践
结构化日志
JSON格式便于后续处理和分析:
{
"timestamp": "2023-05-15T14:32:45.123Z",
"level": "ERROR",
"message": "Database connection failed",
"service": "user-service",
"requestId": "abc123",
"stack": "..."
}
上下文信息
记录请求相关上下文:
app.use((req, res, next) => {
req.logger = logger.child({
requestId: uuid.v4(),
method: req.method,
url: req.url
});
next();
});
// 在路由中使用
app.get('/api/users', (req, res) => {
req.logger.info('Fetching user list');
// ...
});
日志存储策略
文件轮转
防止日志文件过大:
const { createLogger, transports, format } = require('winston');
const { combine, timestamp, json } = format;
const DailyRotateFile = require('winston-daily-rotate-file');
const logger = createLogger({
format: combine(
timestamp(),
json()
),
transports: [
new DailyRotateFile({
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
})
]
});
云服务集成
将日志发送到ELK、Splunk等平台:
const { ElasticsearchTransport } = require('winston-elasticsearch');
const esTransport = new ElasticsearchTransport({
level: 'info',
clientOpts: { node: 'http://localhost:9200' }
});
logger.add(esTransport);
性能考虑
异步日志记录
避免阻塞事件循环:
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
function logWithContext(message) {
const store = asyncLocalStorage.getStore();
logger.info({
message,
...store
});
}
asyncLocalStorage.run({ requestId: '123' }, () => {
logWithContext('Processing request');
});
批量写入
减少I/O操作:
const batchTransport = {
log: (info, callback) => {
batchQueue.push(info);
if (batchQueue.length >= 10) {
flushBatch();
}
callback();
}
};
function flushBatch() {
// 批量写入逻辑
}
错误处理与日志
未捕获异常
全局错误处理:
process.on('uncaughtException', (err) => {
logger.fatal('Uncaught exception', { error: err.stack });
// 优雅关闭
server.close(() => process.exit(1));
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled rejection', { reason, promise });
});
Express错误中间件
app.use((err, req, res, next) => {
req.logger.error('Route error', {
error: err.message,
stack: err.stack
});
res.status(500).json({ error: 'Internal server error' });
});
日志分析与监控
关键指标提取
const logParser = /\[(?<timestamp>.+)\] (?<level>\w+): (?<message>.+)/;
fs.createReadStream('app.log')
.pipe(split())
.on('data', line => {
const match = logParser.exec(line);
if (match) {
const { level } = match.groups;
metrics[level] = (metrics[level] || 0) + 1;
}
});
实时监控
使用WebSocket实时推送日志:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
logger.add(new transports.Stream({
stream: {
write: (message) => {
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
}
}));
安全注意事项
敏感信息过滤
const sensitiveFields = ['password', 'token', 'creditCard'];
function sanitize(obj) {
return Object.entries(obj).reduce((acc, [key, value]) => {
acc[key] = sensitiveFields.includes(key) ? '***' : value;
return acc;
}, {});
}
logger.info('User login', sanitize({
username: 'admin',
password: 'secret'
}));
日志访问控制
const fs = require('fs');
const { createHash } = require('crypto');
function ensureLogPermissions(file) {
fs.chmodSync(file, 0o640);
const hash = createHash('sha256').update(file).digest('hex');
fs.writeFileSync(`${file}.hash`, hash);
}
测试环境差异
开发环境配置
const logger = createLogger({
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
})
]
});
模拟日志
测试中使用模拟日志:
const mockLogger = {
logs: [],
info: (message) => mockLogger.logs.push({ level: 'info', message }),
error: (message) => mockLogger.logs.push({ level: 'error', message })
};
// 在测试中
assert(mockLogger.logs.some(log => log.message.includes('expected')));
高级日志模式
请求追踪
const cls = require('cls-hooked');
const namespace = cls.createNamespace('app');
app.use((req, res, next) => {
namespace.run(() => {
namespace.set('traceId', uuid.v4());
next();
});
});
// 任何地方获取traceId
function getLogger() {
return logger.child({
traceId: namespace.get('traceId')
});
}
性能日志
记录函数执行时间:
function timed(label) {
return function(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = async function(...args) {
const start = Date.now();
try {
return await original.apply(this, args);
} finally {
logger.debug(`${label} took ${Date.now() - start}ms`);
}
};
};
}
class UserService {
@timed('getUser')
async getUser(id) {
// ...
}
}