当前位置: 首页 > news >正文

AI智能日志分析系统

文章目录

    • 1.combinations-intelligent-analysis-starter
        • 1.目录结构
        • 2.pom.xml
        • 3.自动配置
          • 1.IntelligentAnalysisAutoConfiguration.java
          • 2.spring.factories
    • 2.combinations-intelligent-analysis-starter-demo
        • 1.目录结构
        • 2.pom.xml
        • 3.application.yml
        • 4.IntelligentAnalysisApplication.java 启动类
        • 5.工具类
          • 1.MailUtil.java 发送邮件
          • 2.MethodCallChainUtil.java 根据堆栈信息从Gitee获取源码并提取每个方法的调用链
          • 3.StringUtils.java 合并然后截取指定长度字符串
        • 6.ELKEntity.java ELK映射实体类
        • 7.RabbitMQConfig.java
        • 8.ElkListener.java 监听从Logstash中发送过来的日志消息
        • 9.DlxQueueListener.java 监听死信队列,确保消费者可靠性
        • 10.结果展示
          • 1.combinations-elk-starter-demo 直接抛出异常
          • 2.combinations-intelligent-analysis-starter-demo 开始监听,一旦发生异常,就进行ai分析
          • 3.AI分析的邮件
    • 3.Logstash的配置以及系统执行流程
        • 1.这个配置可以将消息发送到RabbitMQ
        • 2.AI智能日志分析系统执行流程
          • 1.Logstash采集日志,当日志为ERROR的时候发送到RabbitMQ
          • 2.RabbitMQ监听到日志进行处理
            • 1.通过javaparser根据异常堆栈来解析出所有自己项目的groupId下的类路径和方法名
            • 2.通过仓库名字+日志中的moudle名+类路径就可以从Gitee中获取这个类的代码
            • 3.再使用javaparser去获取到这个方法的调用链,就是当前方法以及调用了当前方法的内容
            • 4.将方法调用链和异常堆栈进行截取后交给AI智能分析日志,给出解决方案
            • 5.为了解决OpenAI的接口调用速率限制,采用消费者指数退避重试机制加上死信队列的方式确保消息正常消费
            • 6.考虑成本问题,只有当方法调用链不为空的时候才进行AI的日志分析,其余情况(方法调用链为空和死信队列)就会直接将错误日志的消息以邮件的形式发送

1.combinations-intelligent-analysis-starter

1.目录结构

CleanShot 2025-01-02 at 21.30.55@2x

2.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.sunxiansheng</groupId><artifactId>sunrays-combinations</artifactId><version>1.0.5</version></parent><version>1.0.5</version><artifactId>combinations-intelligent-analysis-starter</artifactId><dependencies><!-- common-rabbitmq-starter --><dependency><groupId>com.sunxiansheng</groupId><artifactId>common-rabbitmq-starter</artifactId><version>1.0.5</version></dependency><!-- common-openai-starter --><dependency><groupId>com.sunxiansheng</groupId><artifactId>common-openai-starter</artifactId><version>1.0.5</version></dependency><!-- javaparser-core --><dependency><groupId>com.github.javaparser</groupId><artifactId>javaparser-core</artifactId><version>3.25.4</version></dependency><!-- jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><!-- common-mail-starter --><dependency><groupId>com.sunxiansheng</groupId><artifactId>common-mail-starter</artifactId><version>1.0.5</version></dependency><dependency><groupId>com.vladsch.flexmark</groupId><artifactId>flexmark-all</artifactId><version>0.62.2</version></dependency></dependencies></project>
3.自动配置
1.IntelligentAnalysisAutoConfiguration.java
package com.sunxiansheng.intelligent.analysis.config;import org.springframework.context.annotation.Configuration;/*** Description: 智能分析自动配置类** @Author sun* @Create 2025/1/1 19:27* @Version 1.0*/
@Configuration
public class IntelligentAnalysisAutoConfiguration {}
2.spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sunxiansheng.intelligent.analysis.config.IntelligentAnalysisAutoConfiguration

2.combinations-intelligent-analysis-starter-demo

1.目录结构

CleanShot 2025-01-02 at 21.33.01@2x

2.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.sunxiansheng</groupId><artifactId>sunrays-demo</artifactId><version>1.0.5</version></parent><version>1.0.5</version><artifactId>combinations-intelligent-analysis-starter-demo</artifactId><dependencies><!-- combinations-intelligent-analysis-starter --><dependency><groupId>com.sunxiansheng</groupId><artifactId>combinations-intelligent-analysis-starter</artifactId><version>1.0.5</version></dependency><!-- common-log4j2-starter --><dependency><groupId>com.sunxiansheng</groupId><artifactId>common-log4j2-starter</artifactId><version>1.0.5</version></dependency></dependencies>
</project>
3.application.yml
spring:# 邮件配置mail:host: smtp.126.com  # 邮箱服务商的SMTP服务器地址username: guest@126.com  # 邮箱账户password: guest  # 邮箱授权码或密码# RabbitMQ 配置rabbitmq:# 服务器地址host: guest# 用户名username: guest# 密码password: guest# 虚拟主机virtual-host: /# 端口port: 6783# 消费者配置listener:simple:acknowledge-mode: auto # 自动确认模式(消费者确认机制)retry:enabled: true # 开启重试机制max-attempts: 3 # 最大尝试次数initial-interval: 5000ms # 重试间隔时间(5s)multiplier: 2.0 # 重试时间间隔倍数stateless: true # false:有状态,true:无状态,如果是有状态的,每次重试都会发送到同一个队列
openai:api-key: guest
4.IntelligentAnalysisApplication.java 启动类
package com.sunxiansheng.intelligent.analysis;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Description: 智能分析启动类** @Author sun* @Create 2025/1/1 19:29* @Version 1.0*/
@SpringBootApplication
public class IntelligentAnalysisApplication {public static void main(String[] args) {SpringApplication.run(IntelligentAnalysisApplication.class, args);}
}
5.工具类
1.MailUtil.java 发送邮件
package com.sunxiansheng.intelligent.analysis.utils;import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.UnsupportedEncodingException;/*** Description: 邮件工具类** @Author sun* @Create 2025/1/2 18:36* @Version 1.0*/
@Component
public class MailUtil {@Resourceprivate JavaMailSender mailSender;@Value("${spring.mail.username}")private String from;/*** 发送html邮件,没报错就是发送成功了** @param to          收件人* @param name        发件人名称* @param subject     邮件主题* @param htmlContent 邮件内容*/public void sendHtmlMessage(String to, String name, String subject, String htmlContent) throws UnsupportedEncodingException, MessagingException {MimeMessage message = mailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(message, true);// 创建邮件发送者地址helper.setFrom(new InternetAddress(MimeUtility.encodeText(name) + "<" + from + ">"));// 创建邮件发送者地址helper.setTo(new InternetAddress(MimeUtility.encodeText("接收方") + "<" + to + ">"));// 标题helper.setSubject(subject);// 第二个参数指定发送的是HTML格式helper.setText(htmlContent, true);mailSender.send(message);}
}
2.MethodCallChainUtil.java 根据堆栈信息从Gitee获取源码并提取每个方法的调用链
package com.sunxiansheng.intelligent.analysis.utils;import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.MethodCallExpr;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class MethodCallChainUtil {/*** 从堆栈信息中提取每个方法的调用链** @param stackTrace  堆栈信息* @param giteeRepo   Gitee 仓库地址* @param moduleName  模块名* @param branchName  分支名称* @param classPrefix 类路径前缀过滤器* @return 每个方法的调用链列表* @throws Exception 异常信息*/public static List<String> extractMethodCallChainsFromStackTrace(String stackTrace,String giteeRepo,String moduleName,String branchName,String classPrefix) throws Exception {List<String> callChains = new ArrayList<>();// 正则匹配堆栈中的类路径和方法名Pattern pattern = Pattern.compile("at ([\\w\\.]+)\\.([\\w]+)\\((\\w+\\.java):(\\d+)\\)");Matcher matcher = pattern.matcher(stackTrace);while (matcher.find()) {String classPath = matcher.group(1); // 类路径String methodName = matcher.group(2); // 方法名// 过滤掉不符合指定前缀的类if (!classPath.startsWith(classPrefix)) {continue;}// 从 Gitee 仓库获取类文件内容try {String classContent = readClassFileFromGitee(classPath, giteeRepo, moduleName, branchName);// 获取方法的调用链String methodCallChain = extractMethodCallChain(classContent, methodName);callChains.add("类: " + classPath + "\n" + methodCallChain);} catch (Exception e) {System.err.println("无法解析方法 " + methodName + " 于类: " + classPath);}}return callChains;}/*** 从 Gitee 仓库中读取类文件内容** @param classPath  类路径* @param giteeRepo  Gitee 仓库地址* @param moduleName 模块名* @param branchName 分支名称* @return 类文件内容字符串* @throws Exception 如果类文件不存在或读取失败*/private static String readClassFileFromGitee(String classPath,String giteeRepo,String moduleName,String branchName) throws Exception {String filePath = "src/main/java/" + classPath.replace(".", "/") + ".java";String url = String.format("%s/raw/%s/%s/%s", giteeRepo, branchName, moduleName, filePath);HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();connection.setRequestMethod("GET");if (connection.getResponseCode() != 200) {throw new IllegalArgumentException("无法从 Gitee 获取类文件: " + url);}try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {return reader.lines().reduce((a, b) -> a + "\n" + b).orElse("");}}/*** 提取方法的调用链** @param classContent 类文件内容* @param methodName   方法名* @return 方法调用链* @throws Exception 如果解析失败或方法未找到*/private static String extractMethodCallChain(String classContent, String methodName) throws Exception {JavaParser javaParser = new JavaParser();ParseResult<CompilationUnit> parseResult = javaParser.parse(classContent);if (!parseResult.isSuccessful() || !parseResult.getResult().isPresent()) {throw new IllegalArgumentException("无法解析类文件内容");}CompilationUnit compilationUnit = parseResult.getResult().get();Optional<ClassOrInterfaceDeclaration> classDeclarationOpt = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class);if (!classDeclarationOpt.isPresent()) {throw new IllegalArgumentException("未找到类定义");}ClassOrInterfaceDeclaration classDeclaration = classDeclarationOpt.get();// 使用队列递归查找调用链Queue<String> methodQueue = new LinkedList<>();Set<String> processedMethods = new HashSet<>();methodQueue.add(methodName);StringBuilder callChain = new StringBuilder();callChain.append("调用链:\n");// 开始递归提取方法内容及调用链while (!methodQueue.isEmpty()) {String currentMethodName = methodQueue.poll();if (processedMethods.contains(currentMethodName)) {continue; // 防止重复处理方法}processedMethods.add(currentMethodName);Optional<MethodDeclaration> methodOpt = classDeclaration.findAll(MethodDeclaration.class).stream().filter(method -> method.getNameAsString().equals(currentMethodName)).findFirst();if (!methodOpt.isPresent()) {callChain.append("未找到方法: ").append(currentMethodName).append("\n");continue;}MethodDeclaration method = methodOpt.get();callChain.append("方法: ").append(currentMethodName).append("\n").append(method).append("\n\n");// 查找调用此方法的其他方法for (MethodDeclaration callerMethod : classDeclaration.findAll(MethodDeclaration.class)) {if (!processedMethods.contains(callerMethod.getNameAsString())) {boolean callsTarget = callerMethod.findAll(MethodCallExpr.class).stream().anyMatch(call -> call.getNameAsString().equals(currentMethodName));if (callsTarget) {methodQueue.add(callerMethod.getNameAsString());callChain.append("方法 '").append(callerMethod.getNameAsString()).append("' 调用了方法 '").append(currentMethodName).append("':\n");callChain.append(callerMethod).append("\n\n");}}}}return callChain.toString();}
}
3.StringUtils.java 合并然后截取指定长度字符串
package com.sunxiansheng.intelligent.analysis.utils;import java.nio.charset.StandardCharsets;
import java.util.List;/*** Description: 字符串工具类** @Author sun* @Create 2025/1/2 15:26* @Version 1.0*/
public class StringUtils {/*** 截取字符串的前n个字符。* 如果字符串长度小于n,则返回原字符串。** @param input 原字符串* @param n     截取的字符数* @return 截取后的字符串*/public static String truncate(String input, int n) {if (input == null || n <= 0) {return "";}return input.length() > n ? input.substring(0, n) : input;}/*** 按字节数截取字符串(支持多字节字符)。* 如果字符串总字节数小于限制,直接返回原字符串。** @param input     原字符串* @param byteLimit 最大字节数* @return 截取后的字符串*/public static String truncateByBytes(String input, int byteLimit) {if (input == null || byteLimit <= 0) {return "";}byte[] bytes = input.getBytes(StandardCharsets.UTF_8);if (bytes.length <= byteLimit) {return input;}// 按字节截取字符串int endIndex = 0;int currentBytes = 0;for (int i = 0; i < input.length(); i++) {char c = input.charAt(i);// UTF-8编码:ASCII占1字节,其他占2或3字节currentBytes += (c <= 0x7F) ? 1 : (c <= 0x7FF ? 2 : 3);if (currentBytes > byteLimit) {break;}endIndex = i + 1;}return input.substring(0, endIndex);}/*** 截取字符串的前n个字符并在超长时添加省略号(...)。** @param input 原字符串* @param n     截取的字符数* @return 截取后的字符串(可能包含省略号)*/public static String truncateWithEllipsis(String input, int n) {if (input == null || n <= 0) {return "";}if (input.length() <= n) {return input;}return input.substring(0, n) + "...";}/*** 按字节截取字符串并添加省略号(...)。** @param input     原字符串* @param byteLimit 最大字节数* @return 截取后的字符串(可能包含省略号)*/public static String truncateByBytesWithEllipsis(String input, int byteLimit) {if (input == null || byteLimit <= 0) {return "";}String truncated = truncateByBytes(input, byteLimit - 3);return truncated.length() < input.length() ? truncated + "..." : truncated;}/*** 合并字符串列表,截取指定长度,并在超长时添加省略号。** @param stringList 字符串列表* @param maxLength  最大字符数* @return 截取后的字符串(可能包含省略号)*/public static String mergeAndTruncateWithEllipsis(List<String> stringList, int maxLength) {if (stringList == null || stringList.isEmpty() || maxLength <= 0) {return "";}// 合并字符串StringBuilder merged = new StringBuilder();for (String str : stringList) {if (str != null) {merged.append(str);}}// 截取字符串String result = merged.toString();if (result.length() > maxLength) {return result.substring(0, maxLength) + "...";}return result;}
}
6.ELKEntity.java ELK映射实体类
package com.sunxiansheng.intelligent.analysis.entity;import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;import java.io.Serializable;/*** Description: ELK实体类** @Author sun* @Create 2025/1/1 18:08* @Version 1.0*/
@Data
public class ELKEntity implements Serializable {private static final long serialVersionUID = 1L;private String traceId;private String thread;private String logger;private String throwable;private String module;private String level;private String timestamp;private String host;@JsonProperty("log_message")private String logMessage;
}
7.RabbitMQConfig.java
package com.sunxiansheng.intelligent.analysis.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** Description:  RabbitMQ配置类** @Author sun* @Create 2025/1/1 17:09* @Version 1.0*/
@Configuration
public class RabbitMQConfig {/*** 死信交换机** @return*/@Beanpublic DirectExchange dlxExchange() {return new DirectExchange("dlxExchange");}/*** 死信队列** @return*/@Beanpublic Queue dlxQueue() {return QueueBuilder.durable("dlxQueue").build();}/*** 死信队列绑定死信交换机** @return*/@Beanpublic Binding dlxBinding() {return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.elk");}/*** 创建一个fanout类型的elk交换机** @return*/@Beanpublic FanoutExchange elkExchange() {return new FanoutExchange("elk.exchange");}/*** 创建一个elk队列,并设置死信交换机和死信路由键** @return*/@Beanpublic Queue elkQueue() {return QueueBuilder.durable("elkQueue").withArgument("x-dead-letter-exchange", "dlxExchange").withArgument("x-dead-letter-routing-key", "dlx.elk").lazy().build();}/*** 交换机和队列绑定*/@Beanpublic Binding binding() {return BindingBuilder.bind(elkQueue()).to(elkExchange());}
}
8.ElkListener.java 监听从Logstash中发送过来的日志消息
package com.sunxiansheng.intelligent.analysis.consumer;import com.sunxiansheng.intelligent.analysis.entity.ELKEntity;
import com.sunxiansheng.intelligent.analysis.utils.MailUtil;
import com.sunxiansheng.intelligent.analysis.utils.MethodCallChainUtil;
import com.sunxiansheng.intelligent.analysis.utils.StringUtils;
import com.sunxiansheng.openai.client.OpenAiClient;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.List;/*** Description: elk消息监听** @Author sun* @Create 2025/1/1 17:17* @Version 1.0*/
@Component
@Slf4j
public class ElkListener {@Resourceprivate OpenAiClient openAiClient;@Resourceprivate MailUtil mailUtil;private static final String to = "sunxiansehng@gmail.com";@RabbitListener(queues = "elkQueue")public void receive(ELKEntity message) throws Exception {String timestamp = message.getTimestamp();String logger = message.getLogger();String module = message.getModule();String throwable = message.getThrowable();String host = message.getHost();String logMessage = message.getLogMessage();analyze(throwable, module, timestamp, logger, host, logMessage);}public void analyze(String throwable, String module, String timestamp, String logger,String host, String logMessage) throws Exception {List<String> methodCallChains = MethodCallChainUtil.extractMethodCallChainsFromStackTrace(throwable,"https://gitee.com/qyxinhua_0/sunrays-framework",module,"master","com.sunxiansheng");// 如果方法调用链为空,则直接返回if (methodCallChains.isEmpty()) {sendMailWithDetails("线上报错(无AI分析)", "无", timestamp, logger, module, host, logMessage, throwable);log.info("方法调用链为空,无法分析问题");return;}String pattern = "        问题: 在这里总结一个问题标题\n" +"        ----------------------------------------\n" +"        1. 问题产生原因:\n" +"           在这里写原因\n" +"        ----------------------------------------\n" +"        2. 问题解决方式:\n" +"           在这里写解决方式\n" +"        ----------------------------------------\n";String info = String.format("方法调用链:%s 异常信息:%s",StringUtils.mergeAndTruncateWithEllipsis(methodCallChains, 500),StringUtils.truncateWithEllipsis(throwable, 500));String question = String.format("我会给你我的方法调用链以及异常信息:\n%s\n" +"请帮我按照下面的格式去分析一下问题产生的原因和解决方式:\n%s",info,pattern);log.info("问题:{}", question);String aiAns = openAiClient.askAI("gpt-4o", question, false);log.info("AI回答:{}", aiAns);// 发送AI分析邮件sendMailWithDetails("线上报错(有AI分析)", aiAns, timestamp, logger, module, host, logMessage, throwable);}private void sendMailWithDetails(String subject, String analysisResult, String timestamp, String logger,String module, String host, String logMessage, String throwable) throws Exception {// 构建邮件的HTML格式内容String htmlContent = buildHtmlContent(subject, analysisResult, timestamp, logger, module, host, logMessage, throwable);// 发送邮件mailUtil.sendHtmlMessage(to, "SunRays-Framework", subject, htmlContent);}public String buildHtmlContent(String subject, String analysisResult, String timestamp, String logger,String module, String host, String logMessage, String throwable) {// 使用 Flexmark 将 analysisResult 转换为 HTMLParser parser = Parser.builder().build();HtmlRenderer renderer = HtmlRenderer.builder().build();String analysisHtml = renderer.render(parser.parse(analysisResult));// 构建 HTML 内容return "<html><body>" +"<h2>" + subject + "</h2>" +"<table border='1' cellpadding='10' cellspacing='0'>" +"<tr><td><strong>时间戳</strong></td><td>" + timestamp + "</td></tr>" +"<tr><td><strong>日志器</strong></td><td>" + logger + "</td></tr>" +"<tr><td><strong>模块</strong></td><td>" + module + "</td></tr>" +"<tr><td><strong>主机</strong></td><td>" + host + "</td></tr>" +"<tr><td><strong>日志信息</strong></td><td>" + logMessage + "</td></tr>" +"<tr><td><strong>异常信息</strong></td><td><pre>" + throwable + "</pre></td></tr>" +"</table>" +"<h3>AI分析结果:</h3>" +"<div>" + analysisHtml + "</div>" +  // 渲染后的分析结果"</body></html>";}
}
9.DlxQueueListener.java 监听死信队列,确保消费者可靠性
package com.sunxiansheng.intelligent.analysis.consumer;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sunxiansheng.intelligent.analysis.entity.ELKEntity;
import com.sunxiansheng.intelligent.analysis.utils.MailUtil;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;/*** Description: 死信队列监听** @Author sun* @Create 2025/1/2 12:39* @Version 1.0*/
@Component
@Slf4j
public class DlxQueueListener {@Resourceprivate MailUtil mailUtil;private static final String to = "sunxiansehng@gmail.com";/*** 死信队列消息消费方法** @param message 死信队列中的消息*/@RabbitListener(queues = "dlxQueue")public void receiveDlxMessage(Message message) {// 处理死信消息,通常是日志记录、报警或人工干预log.error("DlxQueueListener:接收到死信消息");// 获取消息体String messageBody = new String(message.getBody(), StandardCharsets.UTF_8);// 使用 Jackson 将消息体反序列化为 ELKEntity 对象ObjectMapper objectMapper = new ObjectMapper();// 忽略掉未知属性objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);try {ELKEntity elkEntity = objectMapper.readValue(messageBody, ELKEntity.class);String logger = elkEntity.getLogger();String throwable = elkEntity.getThrowable();String module = elkEntity.getModule();String timestamp = elkEntity.getTimestamp();String host = elkEntity.getHost();String logMessage = elkEntity.getLogMessage();sendMailWithDetails("线上报错(死信队列消息)", "无", timestamp, logger, module, host, logMessage, throwable);} catch (Exception e) {log.error("DlxQueueListene:反序列化消息失败", e);}}private void sendMailWithDetails(String subject, String analysisResult, String timestamp, String logger,String module, String host, String logMessage, String throwable) throws Exception {// 构建邮件的HTML格式内容String htmlContent = buildHtmlContent(subject, analysisResult, timestamp, logger, module, host, logMessage, throwable);// 发送邮件mailUtil.sendHtmlMessage(to, "SunRays-Framework", subject, htmlContent);}public String buildHtmlContent(String subject, String analysisResult, String timestamp, String logger,String module, String host, String logMessage, String throwable) {// 使用 Flexmark 将 analysisResult 转换为 HTMLParser parser = Parser.builder().build();HtmlRenderer renderer = HtmlRenderer.builder().build();String analysisHtml = renderer.render(parser.parse(analysisResult));// 构建 HTML 内容return "<html><body>" +"<h2>" + subject + "</h2>" +"<table border='1' cellpadding='10' cellspacing='0'>" +"<tr><td><strong>时间戳</strong></td><td>" + timestamp + "</td></tr>" +"<tr><td><strong>日志器</strong></td><td>" + logger + "</td></tr>" +"<tr><td><strong>模块</strong></td><td>" + module + "</td></tr>" +"<tr><td><strong>主机</strong></td><td>" + host + "</td></tr>" +"<tr><td><strong>日志信息</strong></td><td>" + logMessage + "</td></tr>" +"<tr><td><strong>异常信息</strong></td><td><pre>" + throwable + "</pre></td></tr>" +"</table>" +"<h3>AI分析结果:</h3>" +"<div>" + analysisHtml + "</div>" +  // 渲染后的分析结果"</body></html>";}
}
10.结果展示
1.combinations-elk-starter-demo 直接抛出异常

CleanShot 2025-01-02 at 21.43.05@2x

2.combinations-intelligent-analysis-starter-demo 开始监听,一旦发生异常,就进行ai分析

CleanShot 2025-01-02 at 21.44.56@2x

CleanShot 2025-01-02 at 21.45.22@2x

3.AI分析的邮件

CleanShot 2025-01-02 at 21.46.06@2x

CleanShot 2025-01-02 at 21.46.31@2x

3.Logstash的配置以及系统执行流程

1.这个配置可以将消息发送到RabbitMQ
input {tcp {port => 9601codec => multiline {# 匹配日志行的开始(时间戳 + 分隔符 XYZ123DELIMITERXYZ123)pattern => "^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} XYZ123DELIMITERXYZ123"negate => truewhat => "previous"auto_flush_interval => 5}}
}filter {dissect {mapping => {"message" => "%{timestamp} XYZ123DELIMITERXYZ123 [%{thread}] XYZ123DELIMITERXYZ123 %{level} XYZ123DELIMITERXYZ123 [PFTID:%{traceId}] XYZ123DELIMITERXYZ123 [Module:%{module}] XYZ123DELIMITERXYZ123 %{logger} XYZ123DELIMITERXYZ123 %{log_message} XYZ123DELIMITERXYZ123 %{throwable}"}remove_field => ["message"]}date {match => ["timestamp", "yyyy-MM-dd HH:mm:ss.SSS"]timezone => "Asia/Shanghai"   # 根据您的实际时区进行设置target => "@timestamp"}}output {elasticsearch {hosts => ["http://guest:9200"]  # 替换为您的 Elasticsearch 地址index => "java-logs-%{+YYYY.MM.dd}"      # 按日期创建索引}# 将 level 为 ERROR 的日志发送到 RabbitMQif [level] == "ERROR" {rabbitmq {host => "guest"port => 6783user => "guest"password => "guest"vhost => "/"exchange => "elk.exchange"exchange_type => "fanout"message_properties => {"content_type" => "application/json""priority" => 1}}}# 调试用,输出到控制台stdout {codec => rubydebug}
}
2.AI智能日志分析系统执行流程
1.Logstash采集日志,当日志为ERROR的时候发送到RabbitMQ
2.RabbitMQ监听到日志进行处理
1.通过javaparser根据异常堆栈来解析出所有自己项目的groupId下的类路径和方法名
2.通过仓库名字+日志中的moudle名+类路径就可以从Gitee中获取这个类的代码
3.再使用javaparser去获取到这个方法的调用链,就是当前方法以及调用了当前方法的内容
4.将方法调用链和异常堆栈进行截取后交给AI智能分析日志,给出解决方案
5.为了解决OpenAI的接口调用速率限制,采用消费者指数退避重试机制加上死信队列的方式确保消息正常消费
6.考虑成本问题,只有当方法调用链不为空的时候才进行AI的日志分析,其余情况(方法调用链为空和死信队列)就会直接将错误日志的消息以邮件的形式发送
http://www.lryc.cn/news/527015.html

相关文章:

  • 试用ChatGPT开发一个大语言模型聊天App
  • Unity Epplus读取excel表并存入So文件举例
  • 连接 OpenAI 模型:基础操作
  • [ Spring ] Spring Cloud Alibaba Message Stream Binder for RocketMQ 2025
  • ubuntu 更新24LTS中断导致“系统出错且无法恢复,请联系系统管理员”
  • 力扣-链表-203 移除链表元素
  • Unity中关于实现 管道水流+瀑布流动+大肠蠕动效果笔记
  • 宏_wps_宏修改word中所有excel表格的格式_设置字体对齐格式_删除空行等
  • Linux——网络(udp)
  • Oracle-Java JDBC 连接超时之后的认知纠正
  • 自定义数据集使用框架的线性回归方法对其进行拟合
  • 15天基础内容-5
  • 82,【6】BUUCTF WEB .[CISCN2019 华东南赛区]Double Secret
  • Android WebView 中网页被劫持的原因及解决方案
  • 特朗普政府将开展新网络攻击
  • 快递代取项目Uniapp+若依后端管理
  • arcgis短整型变为长整型的处理方式
  • 06、Redis相关概念:缓存击穿、雪崩、穿透、预热、降级、一致性等
  • 嵌入式基础 -- PCIe 控制器中断管理之MSI与MSI-X简介
  • websocket实现
  • unity学习20:time相关基础 Time.time 和 Time.deltaTime
  • 【C++】特殊类设计、单例模式与类型转换
  • scratch七彩六边形 2024年12月scratch三级真题 中国电子学会 图形化编程 scratch三级真题和答案解析
  • 代码随想录刷题day16|(哈希表篇)349.两个数组的交集
  • Synology 群辉NAS安装(6)安装mssql
  • 2025年美赛B题-结合Logistic阻滞增长模型和SIR传染病模型研究旅游可持续性-成品论文
  • Hook 函数
  • 蓝桥杯模拟算法:蛇形方阵
  • DeepSeek-R1解读:纯强化学习,模型推理能力提升的新范式?
  • 深度解析:基于Vue 3的教育管理系统架构设计与优化实践