当前位置: 首页 > 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的日志分析,其余情况(方法调用链为空和死信队列)就会直接将错误日志的消息以邮件的形式发送

相关文章:

AI智能日志分析系统

文章目录 1.combinations-intelligent-analysis-starter1.目录结构2.pom.xml3.自动配置1.IntelligentAnalysisAutoConfiguration.java2.spring.factories 2.combinations-intelligent-analysis-starter-demo1.目录结构2.pom.xml3.application.yml4.IntelligentAnalysisApplicat…...

试用ChatGPT开发一个大语言模型聊天App

参考官方文档&#xff0c;安装android studio https://developer.android.com/studio/install?hlzh-cn 参考这个添加permission权限&#xff1a; https://blog.csdn.net/qingye_love/article/details/14452863 参考下面链接完成Android Studio 给项目添加 gradle 依赖 ht…...

Unity Epplus读取excel表并存入So文件举例

目录 此篇需要你有一定的阅读代码的能力&#xff0c;不然点开了也不知道在做什么 这是读表工具 So文件这么写 使用 此篇需要你有一定的阅读代码的能力&#xff0c;不然点开了也不知道在做什么 在此之前你需要知道epplus是干什么的&#xff0c;然后知道其基本api&#xff0…...

连接 OpenAI 模型:基础操作

在这一部分中&#xff0c;我们将介绍如何连接 OpenAI 模型&#xff0c;设置 API 密钥&#xff0c;并使用 Spring AI 的 ChatClient 与 OpenAI 模型进行简单的对话。Spring AI 为集成 OpenAI 模型提供了方便的工具&#xff0c;使得开发者能够更轻松地与 GPT 系列模型进行交互。 …...

[ Spring ] Spring Cloud Alibaba Message Stream Binder for RocketMQ 2025

文章目录 IntroduceProject StructureDeclare Plugins and ModulesApply Plugins and Add DependenciesSender PropertiesSender ApplicationSender ControllerReceiver PropertiesReceiver ApplicationReceiver Message HandlerCongratulationsAutomatically Send Message By …...

ubuntu 更新24LTS中断导致“系统出错且无法恢复,请联系系统管理员”

22LTS to 24LTS 更新过程中手jian把更新程序controlC导致的。 解决 目前企图完成更新来恢复&#xff0c;重启后有软件包冲突&#xff0c;sudo apt upgrade报冲突。无法进行。 将原来source.list重新 sudo dpkg --configure -a sudo apt install -f 这些都不管用。还是显示gno…...

力扣-链表-203 移除链表元素

思路1 处理头节点&#xff0c;然后遍历下一个节点&#xff0c;只有确保下一个节点不是要移除的节点时再跳到下一个节点 代码1 class Solution { public:ListNode* removeElements(ListNode* head, int val) {while(head ! nullptr && head->val val){head head…...

Unity中关于实现 管道水流+瀑布流动+大肠蠕动效果笔记

Unity中关于实现 管道水流瀑布流动大肠蠕动效果笔记 效果展示&#xff1a; 参考资料及链接&#xff1a; 1、如何在 Unity 中创建水效果 - 水弯曲教程 https://www.youtube.com/watch?v3CcWus6d_B8 关于补充个人技能中&#xff1a;顶点噪波影响网格着色器配合粒子实现水特效 …...

宏_wps_宏修改word中所有excel表格的格式_设置字体对齐格式_删除空行等

需求&#xff1a; 将word中所有excel表格的格式进行统一化&#xff0c;修改其中的数字类型为“宋体&#xff0c; 五号&#xff0c;右对齐&#xff0c; 不加粗&#xff0c;不倾斜”&#xff0c;其中的中文为“宋体&#xff0c; 五号&#xff0c; 不加粗&#xff0c;不倾斜” 数…...

Linux——网络(udp)

文章目录 目录 文章目录 前言 一、upd函数及接口介绍 1. 创建套接字 - socket 函数 2. 绑定地址和端口 - bind 函数 3. 发送数据 - sendto 函数 4. 接收数据 - recvfrom 函数 5. 关闭套接字 - close 函数 二、代码示例 1.服务端 2.客户端 总结 前言 Linux——网络基础&#xf…...

Oracle-Java JDBC 连接超时之后的认知纠正

背景 偶然读到熊老师的文章《老熊的三分地-JDBC中语句超时与事务》了解到&#xff1a;JAVA代码的最后正常断开数据库连接&#xff0c;在默认情况下&#xff0c;正常断开的数据库连接会自动提交没有提交的事务。   通过文章的测试JAVA程序&#xff0c;可以表明&#xff0c;JDB…...

自定义数据集使用框架的线性回归方法对其进行拟合

代码 import torch import numpy as np import torch.nn as nncriterion nn.MSELoss()data np.array([[-0.5, 7.7],[1.8, 98.5],[0.9, 57.8],[0.4, 39.2],[-1.4, -15.7],[-1.4, -37.3],[-1.8, -49.1],[1.5, 75.6],[0.4, 34.0],[0.8, 62.3]])x_data data[:, 0] y_data data…...

15天基础内容-5

day13 【String类、StringBuilder类】 主要内容 String类常用方法【重点】 String类案例【重点】 StringBuilder类【重点】 StringBuilder类常用方法【重点&#xff1a; append】 StringBuilder类案例【理解】 第一章String类 1.1 String类的判断方法 String类实现判断功能…...

82,【6】BUUCTF WEB .[CISCN2019 华东南赛区]Double Secret

进入靶场 提到了secret&#xff0c;那就访问 既然这样&#xff0c;那就传参看能不能报错 这个页面证明是有用的 传参长一点就会报错&#xff0c;传什么内容无所谓 所以网站是flask框架写的 有一个颜色深一点&#xff0c;点开看看 rc4加密url编码 import base64 from urllib…...

Android WebView 中网页被劫持的原因及解决方案

文章目录 一、原因分析二、解决方案一览三、解决方案代码案例3.1 使用 HTTPS3.2 验证 URL3.3 禁用 JavaScript3.4 使用安全的 WebView 设置3.5 监控网络请求3.6 使用安全的 DNS 四、案例深入分析4.1 问题4.2 分析 五、结论 在 Android 应用开发中&#xff0c;WebView 是一个常用…...

特朗普政府将开展新网络攻击

近日&#xff0c;特朗普政府已表态&#xff1a;减少物理战争&#xff0c;网络战将代替&#xff0c;以实现美国的全球优势。 特朗普也指示美国网络司令部可以在没有总统批准的情况下开展更广泛行动&#xff0c;尤其是应对一些突发事件&#xff0c;这其实成为了后续美国通过网络…...

快递代取项目Uniapp+若依后端管理

快递接单代取得uniappspringboot项目 实际效果图...

arcgis短整型变为长整型的处理方式

1.用QGIS的重构字段工具进行修改&#xff0c;亲测比arcgis的更改字段工具有用 2.更换低版本的arcgis10.2.2&#xff0c;亲测10.5和10.6都有这个毛病&#xff0c;虽然官方文档里面说的是10.6.1及以上 Arcgis10.2.2百度链接&#xff1a;https://pan.baidu.com/s/1HYTwgnBJsBug…...

06、Redis相关概念:缓存击穿、雪崩、穿透、预热、降级、一致性等

Redis相关概念&#xff1a;缓存击穿、雪崩、穿透、预热、降级、一致性等 Redis缓存雪崩、缓存击穿、缓存预热热点key、缓存降级、短链接、分布式锁秒杀、预减库存、 堆外缓存Redis架构设计、Redis动态刷新、Redis和DB双写一致性、过期删除策略、集群数据倾斜等一、缓存雪崩 缓…...

嵌入式基础 -- PCIe 控制器中断管理之MSI与MSI-X简介

PCIe 控制器中断管理技术文档 1. 背景 在现代计算机系统中&#xff0c;中断是设备与 CPU 通信的重要机制&#xff0c;PCIe 控制器提供了从传统线中断到基于消息的中断&#xff08;MSI/MSI-X&#xff09;的演进&#xff0c;以提升中断处理效率和可扩展性。x86 和 ARM 架构虽然…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...