最近由于不像前段时间那么忙了,所以抽了个空看了下线上系统的运行日志,还是看到了很多的报错信息,并且还看到生产环境部分的业务配置的还是测试环境的地址,也就是生产上的业务白跑了,这些平时都关注不到的点,就这样被偶然发现了。相信像我这样情况的人不在少数,咱们生产系统,特别是微服务架构,如果公司人员少,很多信息就会被漏掉,当回过头来发现有问题的时候,这种有问题的程序已经在线上跑了很长一段时间了,可能出现数据断层或者数据错误,导致部分业务受损。所以基于此这两天研究下使用n8n工作流来实现对线上系统的自动巡检功能,当发现故障第一时间用邮件的形式推送给研发部,这样子便于提前发现,提早解决。
我们目前线上整体服务才能的是微服务架构,后端有好几个springboot服务,通过网关一起支撑起线上的产品,这些springboot服务的日志都是直接使用plumeLog进行采集的,想要看日志,直接通过plumeLog的dashboard进行查看即可,如下图:
所以对于我们做自动巡检这块只需要对接这里plumeLog存储的数据即可,目前我们部署的plumeLog数据是存储在elasticsearch中的,所以这里使用n8n做自动巡检运维功能的话,只需要从elasticsearch中查询数据即可,然后对数据进行处理,暂时用不着对接plumeLog。
这里我们实现使用n8n自动巡检的主要思路有:
1、制定一个定时任务 2、从elasticsearch中查询数据 3、使用ai agent进行错误分析,并且让他提供一些建议和意见。 4、通过邮件的形式发送给我们指定的邮箱。
这里由于主要是配合我们这篇文章进行演示,所以我把演示用的工作流给贴出来大家看看:
根据前面的思路我们整体实现的工作流如上图所示,下面我们针对上面的工作流进行挨个节点做下介绍:
1)定时任务
首先我们巡检的话一般来说可以是每天或者每个小时,比如我们的业务是每天执行一遍,那么我们就需要在第一步制定一个定时器触发节点,时间可以根据自己的选择来进行,这里为了演示,我直接使用的是人工点击触发事件来进行演示。
2)code节点
第二步的code节点主要是为了方便使用函数获取具体的日期,在plumlog里面,数据是每一天存储在一个index里面的,如下图:
所以这里为了演示,我直接使用javascript节点获取当天的日期即可,对应的javascriot代码是:
// 创建一个Date对象,表示当前时刻
const now = new Date();
// 获取日期的各个部分(年、月、日)
const year = now.getFullYear();
// 月份需要加1,因为getMonth()返回的月份从0开始(0代表一月)
const month = String(now.getMonth() + 1).padStart(2, '0'); // 确保月份总是两位数
const day = String(now.getDate()).padStart(2, '0'); // 确保日期总是两位数
// 组合成 yyyyMMdd 格式的字符串
const formattedDate = `${year}${month}${day}`;
// 准备返回给n8n中下一个节点的数据
// 返回一个数组,其中包含一个json对象,对象中有一个formattedDate属性
return [{ json: { index_name: "plume_log_run_"+formattedDate } }];把这个代码贴到第二步的code节点就可以看到输出了对应的indexname,如下图:
今天是2025年12月3日,所以获取的是12月3日的日期,最后拼接成字符串,正好能对应上我们elasticsearch上的index_name
3)elasticsearch查询节点
前一步我们已经使用代码生成了index_name,那么接下来我们就需要使用elasticsearch节点从elasticsearch中查询对应的数据了,所以这里我们的配置是:
我们在index_name的地方,把上一个节点生成的indexname给拖过来,下面的dsl里面,我们需要写查询的dsl,由于我们这里是做巡检,所以这里我们不是把所有的数据都给查询出来,而是只查询content字段里面有exception字段的数据,所以这里我们的dsl是:
{
"query": {
"bool": {
"must": [
{
"match_phrase": {
"content": "Exception"
}
}
]
}
}
}备注:
1、这里大家可以根据自己的实际情况进行查询,比如查询日志级别是error的数据
2、同时还可以根据其他的条件进行数据的筛选,比如我们每个小时都要巡检一次,那我们可能还需要在dsl里面添加dtTime的时间区间查询。
最后反正这里的dsl查询情况根据自己的实际需要进行填写即可,最后我们测试看看,是可以查询到数据的:
备注:
1、这里还有个需要注意的地方,即我们一般是需要查询所有数据的,我们上图为了演示可以看到limit是写死的10,实际中需要把return all这个开关给打开,而不是使用limit进行查询,避免数据不完整
4)code节点
接下来我们又增加了一个code节点,这个code节点的作用主要是聚合,比如我们前面使用elasticsearch查询数据,那查询的结果是一条条的,我们不可能把这一条条的数据挨个拿给ai进行分析,这样既浪费token,同时最后每一条错误信息都发送一封邮件,这不是我们想要的。所以这里我们使用code节点,把所有的错误数据给综合一下,弄成一条发给ai进行分析即可,所以这里code节点我们的javascript代码是:
// 获取上一个节点传来的所有数据项(数组)
const allInputItems = $input.all();
// 处理所有项,将它们收集到一个数组中
const allErrors = allInputItems.map((inputItem) => {
const itemData = inputItem.json;
return {
appName: itemData.appName || "未知应用",
className: itemData.className || "未知类",
content: itemData.content ? itemData.content.substring(0, 200) + "..." : "无内容",
dtTime: itemData.dtTime ? new Date(itemData.dtTime).toISOString() : "无时间戳"
};
});
// 只返回一条数据,包含所有错误信息的数组
return [{
json: {
all_errors: allErrors, // 包含所有错误对象的数组
total_count: allErrors.length, // 错误总数
summary: `系统检测到 ${allErrors.length} 条错误日志需要分析`,
timestamp: new Date().toISOString()
}
}];最后我们运行一下,看下最后的结果:
可以看到上一个节点数据里面一条条的数据都被我们的code节点统一综合输出到了all_errors节点里面了。
5)AI agent节点
这个节点主要是让ai帮我们分析错误,所以添加一个ai agent节点,模型的话我们还是使用硅基流动的qwen3-8b(最主要是免费,香啊)。然后这里我们需要在ai agent节点里面配置独赢的system.message和prompt,对应的信息是:
system.message信息:
你是一名经验丰富的Java后端架构师和SRE工程师。你的任务是分析系统错误日志,提供专业的故障分析和解决方案建议。
prompt信息:
请分析以下系统错误日志数据,这些是从pd-payments应用获取的10条错误记录:
应用名称:{{ $json.all_errors[0].appName }}
错误总数:{{ $json.all_errors.length }} 条
错误详情:
{{ JSON.stringify($json.all_errors, null, 2) }}
请用以下表格格式输出分析结果:
| 分析维度 | 分析结果 |
|---------|---------|
| 主要异常类型 | |
| 异常简要解读 | |
| 发生位置/链路 | |
| 最可能的根本原因 | |
| 立即补救措施 | |
| 长期预防建议 | |
请基于实际的错误内容进行分析,不要使用预设示例。这是根据我的实际情况设置的系统词和提示词,大家可以根据自己的情况进行改写,最后我们运行一遍看看效果:
可以看到这里右侧的输出使用了markdown的表格形式进行了输出。
6)code节点
这里为什么我们又添加了一个code节点呢?主要是因为后面我们要发送邮件的话,邮件内容不支持markdown格式的显示,看起来就非常凌乱,比如:
这种是不是看起来非常的凌乱,也不方便我们进行分析,所以这里我们转换一下思路,既然邮件内容不支持markdown格式显示,那肯定是支持html格式显示的啊,所以我们把markdown格式的表格内容转换成html格式的内容,所以这里我们code节点的代码是:
// 从AI Agent节点获取Markdown表格
const markdownTable = items[0].json.output;
// 将Markdown表格转换为HTML表格的函数
function markdownTableToHTML(markdown) {
const lines = markdown.split('\n').filter(line => line.trim());
let html = '<table border="1" style="border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;">';
html += '<thead><tr style="background-color: #f2f2f2;">';
// 处理表头
const headers = lines[0].split('|').filter(cell => cell.trim() && cell.trim() !== '---');
headers.forEach(header => {
html += `<th style="padding: 12px; text-align: left;">${header.trim()}</th>`;
});
html += '</tr></thead><tbody>';
// 处理数据行(跳过分隔线)
for (let i = 2; i < lines.length; i++) {
if (lines[i].includes('---')) continue;
const cells = lines[i].split('|').filter(cell => cell.trim());
html += '<tr>';
cells.forEach((cell, index) => {
const style = index === 0 ? 'font-weight: bold;' : '';
html += `<td style="padding: 10px; border: 1px solid #ddd; ${style}">${cell.trim()}</td>`;
});
html += '</tr>';
}
html += '</tbody></table>';
return html;
}
// 转换并返回
const htmlTable = markdownTableToHTML(markdownTable);
// 创建完整的HTML邮件内容
const emailHTML = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
.summary { background: #e8f4fd; padding: 15px; border-radius: 5px; margin: 15px 0; }
</style>
</head>
<body>
<div class="header">
<h2>🚨 系统错误分析报告</h2>
<p>生成时间: ${new Date().toLocaleString()}</p>
</div>
<div class="summary">
<h3>📊 分析概览</h3>
<p>基于系统日志的深度分析,识别关键问题并提供解决方案</p>
</div>
<h3>📋 详细分析结果</h3>
${htmlTable}
<div style="margin-top: 20px; padding: 10px; background: #f8f9fa; border-radius: 5px;">
<small>此邮件由n8n工作流自动生成</small>
</div>
</body>
</html>
`;
return [{
json: {
html_content: emailHTML,
original_markdown: markdownTable
}
}];最后我们运行下结果看看效果:
这里确实已经转换成了html内容了。
7)send email节点
接下来就是最后的发送邮件的节点了,这里很简单,配置下对应的smtp账号及发件人,收件人,主题即可。
然后我们点击测试,去邮箱里面看看
是不是就非常直观了,不仅分析了错误,同时还把解决办法及预防等措施都展示出来了。
以上就是我们内部实现运维场景的自动巡检,自动报警的案例。最后按照惯例,附上本案例的json,登录后即可下载。















还没有评论,来说两句吧...