您现在的位置是:网站首页 > 日志管理文章详情

日志管理

日志管理的重要性

日志是应用程序运行过程中产生的记录,用于追踪系统行为、排查问题和分析性能。良好的日志管理能帮助开发者快速定位问题,提高系统可维护性。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) {
    // ...
  }
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步