日志系统的集成与配置
日志系统是后端开发中不可或缺的组件,尤其在Express框架中,合理的日志配置能帮助开发者快速定位问题、分析请求行为或监控系统状态。从基础的控制台输出到文件存储,再到第三方服务的集成,日志系统的灵活性和扩展性直接影响开发效率。
日志中间件的选择与基础配置
Express生态中有多种日志中间件可供选择,最常用的是morgan
和winston
的组合。morgan
专注于HTTP请求日志,而winston
提供了更通用的日志功能。安装基础依赖:
npm install morgan winston
基础配置示例:
const express = require('express');
const morgan = require('morgan');
const winston = require('winston');
const app = express();
// Morgan配置
app.use(morgan('combined')); // 使用预定义格式
// Winston基础配置
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' })
]
});
app.get('/', (req, res) => {
logger.info('Homepage accessed');
res.send('Hello World');
});
日志格式自定义
实际项目中往往需要定制日志格式。morgan
支持自定义token和格式字符串:
// 自定义morgan token
morgan.token('request-id', req => req.headers['x-request-id'] || 'none');
app.use(morgan(':request-id :method :url :status :response-time ms'));
对于winston
,可以通过组合多种format实现复杂格式:
const { combine, timestamp, label, printf } = winston.format;
const myFormat = printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
logger.add(new winston.transports.Console({
format: combine(
label({ label: 'API' }),
timestamp(),
myFormat
)
}));
多环境日志策略
开发环境和生产环境通常需要不同的日志策略。通过环境变量实现配置切换:
const isProduction = process.env.NODE_ENV === 'production';
if (isProduction) {
logger.add(new winston.transports.File({
filename: 'combined.log',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
}));
} else {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
错误日志的特殊处理
Express中的错误需要特殊捕获和记录。创建错误处理中间件:
app.use((err, req, res, next) => {
logger.error({
message: err.message,
stack: err.stack,
path: req.path,
method: req.method
});
res.status(500).json({ error: 'Internal Server Error' });
});
对于未捕获的异常和Promise rejection,需要全局处理:
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
日志文件轮转与压缩
生产环境中日志文件需要自动轮转。使用winston-daily-rotate-file
:
const DailyRotateFile = require('winston-daily-rotate-file');
logger.add(new DailyRotateFile({
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
}));
结构化日志与云服务集成
现代日志系统通常需要结构化数据以便分析。配置JSON格式并集成ELK等系统:
const { ElasticsearchTransport } = require('winston-elasticsearch');
const esTransport = new ElasticsearchTransport({
level: 'info',
clientOpts: { node: 'http://localhost:9200' }
});
logger.add(esTransport);
// 结构化日志示例
logger.info('User login', {
userId: 12345,
ip: '192.168.1.1',
userAgent: req.headers['user-agent']
});
性能优化与日志采样
高流量场景下需要控制日志量,可采用采样策略:
app.use(morgan('combined', {
skip: (req, res) => res.statusCode < 400, // 只记录错误请求
stream: {
write: (message) => {
if (Math.random() < 0.1) { // 10%采样率
logger.info(message.trim());
}
}
}
}));
敏感信息过滤
日志中需要过滤密码等敏感信息。创建morgan的skip函数:
const sensitiveFields = ['password', 'creditCard'];
app.use(morgan((tokens, req, res) => {
let logData = {
method: tokens.method(req, res),
url: maskSensitiveData(tokens.url(req, res)),
status: tokens.status(req, res)
};
return JSON.stringify(logData);
}));
function maskSensitiveData(url) {
const urlObj = new URL(url, 'http://dummy.com');
sensitiveFields.forEach(field => {
if (urlObj.searchParams.has(field)) {
urlObj.searchParams.set(field, '******');
}
});
return urlObj.pathname + urlObj.search;
}
请求/响应日志的详细记录
有时需要记录完整的请求和响应体,需注意内存消耗:
const expressWinston = require('express-winston');
app.use(expressWinston.logger({
winstonInstance: logger,
requestWhitelist: ['url', 'method', 'headers', 'body'],
responseWhitelist: ['body', 'statusCode'],
bodyBlacklist: ['password']
}));
日志与监控系统联动
将日志与APM系统如New Relic集成:
const newrelic = require('newrelic');
logger.add(new winston.transports.Console({
format: winston.format.printf((info) => {
newrelic.recordCustomEvent('NodeLog', {
level: info.level,
message: info.message
});
return info.message;
})
}));
微服务场景下的分布式追踪
在微服务架构中,需要关联多个服务的日志。集成OpenTelemetry:
const { DiagConsoleLogger, DiagLogLevel, diag } = require('@opentelemetry/api');
const { WinstonInstrumentation } = require('@opentelemetry/instrumentation-winston');
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
const winstonInstrumentation = new WinstonInstrumentation({
enabled: true,
logHook: (span, record) => {
record['traceId'] = span.spanContext().traceId;
}
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn