日志中间件微服务改造:Express日志中间件独立化方案咨询
独立日志微服务改造方案(基于Node.js+Express+Nginx)
一、核心改造思路
把原嵌入各微服务的日志中间件抽离为独立微服务,通过Nginx实现统一流量拦截:所有客户端请求先转发到日志微服务,日志服务完成请求内容记录后,再将请求转发至目标业务微服务;收到业务服务的响应后,记录响应内容,最后把响应返回给客户端。全程无需修改任何业务微服务代码。
二、独立日志微服务实现
基于Express搭建日志服务,核心实现请求转发与双向日志记录,代码如下:
const express = require('express'); const http = require('http'); const { URL } = require('url'); const connection = require('./database/db'); // 复用原数据库连接逻辑 const app = express(); // 接收原始请求体,避免解析丢失内容 app.use(express.raw({ type: '*/*' })); // 日志存储核心函数 async function saveLog(req, resBody, statusCode) { const now = new Date(); const reqBody = req.body ? req.body.toString('utf8') : '{}'; try { await connection.query("CALL HospitalGatifu.insertLog(?,?,?,?,?)", [ `[${req.method}] - ${req.originalUrl}`, reqBody, resBody, statusCode === 400 ? 1 : 0, now, ]); } catch (err) { console.error('日志存储失败:', err); // 日志存储失败不阻塞业务流程 } } // 处理所有请求的转发与日志记录 app.all('*', async (req, res) => { // 从Nginx传递的请求头获取目标业务服务地址 const targetServer = req.headers['x-target-server']; if (!targetServer) { res.status(500).send('Missing target server'); return; } try { const targetUrl = new URL(req.originalUrl, targetServer); // 构造转发请求配置 const proxyOptions = { hostname: targetUrl.hostname, port: targetUrl.port, path: targetUrl.pathname + targetUrl.search, method: req.method, headers: { ...req.headers, 'host': targetUrl.host, 'x-target-server': undefined // 移除自定义头,不传递给业务服务 } }; // 转发请求到业务服务 const proxyReq = http.request(proxyOptions, (proxyRes) => { let resChunks = []; proxyRes.on('data', (chunk) => resChunks.push(chunk)); proxyRes.on('end', async () => { const resBody = Buffer.concat(resChunks).toString('utf8'); // 异步存储日志 await saveLog(req, resBody, proxyRes.statusCode); // 将业务服务响应返回给客户端 res.writeHead(proxyRes.statusCode, proxyRes.headers); res.end(resBody); }); }); // 传递原始请求体到业务服务 if (req.body) proxyReq.write(req.body); proxyReq.end(); proxyReq.on('error', (err) => { console.error('请求转发失败:', err); res.status(502).send('Bad Gateway'); }); } catch (err) { console.error('请求处理失败:', err); res.status(500).send('Internal Server Error'); } }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`日志微服务运行在端口 ${PORT}`); });
三、Nginx反向代理配置
修改Nginx配置,将各业务微服务的请求先转发到日志微服务,并通过自定义请求头传递目标服务地址。示例配置如下:
http { # 定义业务微服务上游节点 upstream service_user { server user-service:3001; # 用户微服务地址 } upstream service_order { server order-service:3002; # 订单微服务地址 } server { listen 80; server_name your-domain.com; # 用户服务请求转发规则 location /user/ { proxy_pass http://log-service:3000; # 日志微服务地址 proxy_set_header X-Target-Server http://service_user; # 传递目标服务地址 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 确保请求体与请求头完整传递 proxy_pass_request_body on; proxy_pass_request_headers on; } # 订单服务请求转发规则 location /order/ { proxy_pass http://log-service:3000; proxy_set_header X-Target-Server http://service_order; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass_request_body on; proxy_pass_request_headers on; } # 其他业务服务可参照上述规则配置 } }
四、关键注意事项
- 请求体处理:使用
express.raw()接收原始请求体,避免Express自动解析JSON/表单导致日志记录的请求内容与实际发送给业务服务的不一致。 - 异步日志存储:日志存储操作异步执行,不阻塞响应返回,避免影响业务接口的响应速度。
- 错误隔离:日志服务的异常(如数据库连接失败)不会中断业务请求转发,保证业务可用性。
- 目标服务寻址:除了请求头传递目标地址,也可通过请求路径前缀、Host等方式匹配目标服务,可根据实际架构调整。
- 性能优化:高流量场景下,可引入消息队列(如Redis、RabbitMQ)将日志存储操作完全异步化,进一步降低日志服务的性能损耗。
内容的提问来源于stack exchange,提问作者Erick Villatoro
相关产品推荐
相关产品推荐

