当前位置: 首页 > 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…...

详解三种常用标准化:Batch Norm、Layer Norm和RMSNorm

在深度学习中&#xff0c;标准化技术是提升模型训练速度、稳定性和性能的重要手段。本文将详细介绍三种常用的标准化方法&#xff1a;Batch Normalization&#xff08;批量标准化&#xff09;、Layer Normalization&#xff08;层标准化&#xff09;和 RMS Normalization&#…...

数据压缩算法-差分编码(Delta Encoding)

Delta Encoding&#xff08;差分编码&#xff09;是一种数据压缩技术&#xff0c;其核心思想是存储数据之间的差异&#xff08;delta&#xff09;&#xff0c;而不是原始数据本身。这种方法特别适用于数据序列中相邻元素之间变化较小的情况&#xff0c;可以显著减少存储空间或传…...

Nginx中部署多个前端项目

1&#xff0c;准备前端项目 tlias系统的前端资源 外卖项目的前端资源 2&#xff0c;nginx里面的html文件夹中新建&#xff0c;tlias和sky两个文件夹。 切记这是在nginx/html下创建的 mkdir sky mkdir tlias 把tlias和sky的资源都放到对应的文件夹中 3&#xff0c;编辑配置ngi…...

Level DB --- TableBuilder

TableBuilder是Level DB里面重要的类和模块&#xff0c;它描述了数据如何序列化到文件中&#xff0c;以及数据里面的格式逻辑。它里面包含了之前介绍的多个模块和类。 data block、filter block和index block block格式&#xff0c;之前已经介绍过Level DB --- BlockBuilder-…...

JVM堆空间

一、堆空间的核心概述 一个JVM实例只存在一个堆内存&#xff0c;堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建&#xff0c;其空间大小也就确定了。是JVM管理的最大一块内存空间。 堆内存的大小是可以调节的。堆可以处于物理上不连续的内存空间中&#xff…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.13 降维打击:扁平化操作的六种武器

1.13 降维打击&#xff1a;扁平化操作的六种武器 目录 #mermaid-svg-bbLxDryjxBbXe3tu {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-bbLxDryjxBbXe3tu .error-icon{fill:#552222;}#mermaid-svg-bbLxDryjxBbXe3tu…...

Doris Schema Change 常见问题分析

1. 什么是 Schema Change Schema Change 是在数据库中修改表结构的一种操作&#xff0c;例如添加列、删除列、更改列类型等。 ⚠️Schema Change 限制⚠️ 一张表在同一时间只能有一个 Schema Change 作业在运行。分区列和分桶列不能修改。如果聚合表中有 REPLACE 方式聚合的…...

数据结构之堆排序

文章目录 堆排序版本一图文理解 版本二向下调整建堆向上调整建堆 排升/降序升序 堆排序 版本一 基于已有数组建堆取堆顶元素并删除堆顶元素重新建大根堆&#xff0c;完成排序版本。 图文理解 版本二 前提&#xff1a;必须提供有现成的数据结构堆 数组建堆&#xff0c;首尾…...

实现桌面动态壁纸(三)—— 视频播放的策略

关于动态壁纸这边&#xff0c;其实已经不需要再谈什么东西了&#xff0c;现有的各种文章都在介绍相关的技术。可以说现如今要去制作一个桌面动态壁纸应该不是什么难事。我考虑了很久&#xff0c;决定还是开一篇单独谈谈。可能我说的也不全部正确&#xff0c;您有什么建议随时可…...

C语言程序设计十大排序—希尔排序

文章目录 1.概念✅2.希尔排序&#x1f388;3.代码实现✅3.1 直接写✨3.2 函数✨ 4.总结✅ 1.概念✅ 排序是数据处理的基本操作之一&#xff0c;每次算法竞赛都很多题目用到排序。排序算法是计算机科学中基础且常用的算法&#xff0c;排序后的数据更易于处理和查找。在计算机发展…...

2023年版本IDEA复制项目并修改端口号和运行内存

2023年版本IDEA复制项目并修改端口号和运行内存 1 在idea中打开server面板&#xff0c;在server面板中选择需要复制的项目右键&#xff0c;点击弹出来的”复制配置…&#xff08;Edit Configuration…&#xff09;“。如果idea上没有server面板或者有server面板但没有springbo…...

Ubuntu 安装 QGIS LTR 3.34

QGIS官方提供了安装指南&#xff1a;https://qgis.org/resources/installation-guide/#linux。大多数linux发行版将QGIS拆分为几个包&#xff1a;qgis、qgis-python、qgis-grass、qgis-plugin-grass、qgis-server&#xff0c;有的包最初安装时被跳过&#xff0c;可以在需要使用…...

win32汇编环境,对话框程序中使用进度条控件

;运行效果 ;win32汇编环境,对话框程序中使用进度条控件 ;进度条控件主要涉及的是长度单位,每步步长,推进的时间。 ;比如你的长度是1000,步长是100,每秒走1次,则10秒走完全程 ;比如你的长度是1000,步长是10,每秒走1次,则100秒走完全程,但每格格子的长度与上面一样 ;以下…...

从ChatGPT热潮看智算崛起

2025年1月7日&#xff0c;科智咨询发布《2025年IDC产业七大发展趋势》&#xff0c;其中提到“ChatGPT开启生成式AI热潮&#xff0c;智能算力需求暴涨&#xff0c;算力供给结构发生转变”。 【图片来源于网络&#xff0c;侵删】 为何会以ChatGPT发布为节点呢&#xff1f;咱们一起…...

APISIX-API服务网关

一、简介 apisix是一款云原生微服务API网关&#xff0c;可以为API提供终极性能、安全性、开源和可扩展的平台。apisix基于Nginx和etcd实现&#xff0c;与传统API网关相比&#xff0c;apisix具有动态路由和插件热加载&#xff0c;特别适合微服务系统下的API管理。 Apisix 的诞生…...

NR_shell运行流程简析

nr_shell 是一套开源 shell 框架&#xff0c;基于框架可创建终端交互功能。 为了记录终端输入指令&#xff0c;以及进行解析处理&#xff0c;nr_shell 提供了一套 cmd 结构体&#xff0c;具体如下&#xff1a;typedef struct static_cmd_function_struct {char cmd[NR_SHELL_CM…...

leetcode_链表 876.链表的中间节点

876.链表的中间节点 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。如果有两个中间结点&#xff0c;则返回第二个中间结点。思路&#xff1a;快慢指针&#xff0c;创建两个指针fast和slow&#xff0c;fast指针每次移动两步&#xff0c;slow指针每次移动…...

idea实用设置

一.View 1.配置工具包方便按 二.File->Settings 点开设置然后进行后面的配置 1.这个看个人习惯 2.更新 3.更改菜单字体大小 4.鼠标控制字体大小 5.文件默认字体大小 6. 代码的智能提示功能 7.自动导包 8.编码 9.取消双击shift搜索...

ui-automator定位官网文档下载及使用

一、ui-automator定位官网文档简介及下载 AndroidUiAutomator&#xff1a;移动端特有的定位方式&#xff0c;uiautomator是java实现的&#xff0c;定位类型必须写成java类型 官方地址&#xff1a;https://developer.android.com/training/testing/ui-automator.html#ui-autom…...

Java数据结构方面的面试试题以及答案解析

Java数据结构是在计算机中存储和组织数据的方式&#xff0c;用于高效地处理和管理数据。 以下是一些常见的Java数据结构&#xff1a; 数组&#xff08;Array&#xff09;&#xff1a;一种线性数据结构&#xff0c;允许通过索引快速访问元素。它存储固定大小的相同类型的元素集…...

微信小程序-点餐(美食屋)02开发实践

目录 概要 整体架构流程 &#xff08;一&#xff09;用户注册与登录 &#xff08;二&#xff09;菜品浏览与点餐 &#xff08;三&#xff09;订单管理 &#xff08;四&#xff09;后台管理 部分代码展示 1.index.wxml 2.list.wxml 3.checkout.wxml 4.detail.wxml 小结优点 概要…...

Redis实战(黑马点评)——关于缓存(缓存更新策略、缓存穿透、缓存雪崩、缓存击穿、Redis工具)

redis实现查询缓存的业务逻辑 service层实现 Overridepublic Result queryById(Long id) {String key CACHE_SHOP_KEY id;// 现查询redis内有没有数据String shopJson (String) redisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(shopJson)){ // 如果redis的数…...

DroneXtract:一款针对无人机的网络安全数字取证工具

关于DroneXtract DroneXtract是一款使用 Golang 开发的适用于DJI无人机的综合数字取证套件&#xff0c;该工具可用于分析无人机传感器值和遥测数据、可视化无人机飞行地图、审计威胁活动以及提取多种文件格式中的相关数据。 功能介绍 DroneXtract 具有四个用于无人机取证和审…...

uniapp使用uni.navigateBack返回页面时携带参数到上个页面

我们平时开发中也经常遇到这种场景&#xff0c;跳转一个页面会进行一些操作&#xff0c;操作完成后再返回上个页面同时要携带着一些参数 其实也很简单&#xff0c;也来记录一下吧 假设从A页面 跳转到 B页面 A页面 直接上完整代码了哈&#xff0c;很简单&#xff1a; <t…...

2025美赛MCM数学建模A题:《石头台阶的“记忆”:如何用数学揭开历史的足迹》(全网最全思路+模型)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ 《石头台阶的“记忆”&#xff1a;如何用数学揭开历史的足迹》 目录 《石头台阶的“记忆”&#xff1a;如何用数学揭开历史的足迹》 ✨摘要✨ ✨引言✨ 1. 引言的结构 2. 撰写步骤 &#xff08;1&#xff09;研究背景 &#…...

python远程获取数据库中的相关数据并存储至json文件

1. conn中的5个变量的含义&#xff1a; ① Driver&#xff1a;数据库驱动程序&#xff0c;我使用的是SQL Server数据库。 ② Server&#xff1a;数据库所在的服务器地址。 ③ Database&#xff1a;要连接的数据库的名称。 ④ UID&#xff1a;登录 SQL Server 数据库的用户名…...

DeepSeek-R1解读:纯强化学习,模型推理能力提升的新范式?

DeepSeek-R1解读&#xff1a;纯强化学习&#xff0c;模型推理能力提升的新范式&#xff1f; 1. Impressive Points2. 纯强化学习&#xff0c;LLM推理能力提升新范式&#xff1f;2.1 DeepSeek-R1-Zero2.2 DeepSeek-R1 3. 端侧模型能力提升&#xff1a;蒸馏>强化学习 1. Impre…...

系统安全及应用

一&#xff1a;账号安全控制 1.1 系统账号清理 1.1.1 将非登陆用户的Shell 设置为 /sbin/nologin (设置为这个解释器&#xff0c;禁止用户登陆&#xff09; [rootlocalhost ~]# usermod -s /sbin/nologin zhangsan #将用户zhangsan 的登录解释器 设置为 /sbin/n…...

ubuntu解决普通用户无法进入root

项目场景&#xff1a; 在RK3566上移植Ubuntu20.04之后普通用户无法进入管理员模式 问题描述 在普通用户使用sudo su试图进入管理员模式的时候报错 解决方案&#xff1a; 1.使用 cat /etc/passwd 查看所有用户.最后一行是 若无用户&#xff0c;则使用 sudo useradd -r -m -s /…...

Mac m1,m2,m3芯片使用nvm安装node14报错

使用nvm安装了node 12/16/18都没有问题&#xff0c;到14就报错了。第一次看到这个报错有点懵&#xff0c;查询资料发现是Mac芯片的问题。 Issue上提供了两个方案&#xff1a; 1、为了在arm64的Mac上安装node 14&#xff0c;需要使用Rosseta&#xff0c;可以通过以下命令安装 …...

IDEA工具下载、配置和Tomcat配置

1. IDEA工具下载、配置 1.1. IDEA工具下载 1.1.1. 下载方式一 官方地址下载 1.1.2. 下载方式二 官方地址下载&#xff1a;https://www.jetbrains.com/idea/ 1.1.3. 注册账户 官网地址&#xff1a;https://account.jetbrains.com/login 1.1.4. JetBrains官方账号注册…...

实战网络安全:渗透测试与防御指南

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在数字化时代&#xff0c;网络安全已成为企业和个人不可忽视的重要课题。网络攻击的复杂性与日俱增&#xff0c;从数据泄露…...

Vue 3 中的 toRef 与 toRefs:使用与案例解析

在 Vue 3 的响应式系统中&#xff0c;toRef 和 toRefs 是两个非常实用的工具函数。它们主要用于将响应式对象的属性转换为单独的 ref&#xff0c;以便在模板或逻辑中更方便地使用。本文将详细介绍 toRef 和 toRefs 的用法&#xff0c;并通过一个老师信息的案例来演示它们的实际…...

如何让Dev-C++支持C++11及以上标准

目录 问题描述解决方案步骤1&#xff1a;打开编译选项 问题描述 在Dev-C中使用C11/17/20新特性&#xff08;如pop_back()等&#xff09;时&#xff0c;可能出现编译错误&#xff1a; #include <iostream> #include<string> using namespace std; int main() {str…...

java8-日期时间Api

目录 LocalDate更新时间LocalTimeLocalDateTimeInstantPeriod Duration格式化、解析日期-时间对象时区 java.util.Date java.util.Calendar 不支持时区 线程不安全 月份从0起线程不安全&#xff0c;只有包裹在ThreadLocal中才安全 java.text.DateFormat java.text.SimpleDateFo…...

电脑无法开机,重装系统后没有驱动且驱动安装失败

电脑无法开机&#xff0c;重装系统后没有驱动且驱动安装失败 前几天电脑突然坏了&#xff0c;电脑卡住后&#xff0c;强制关机&#xff0c;再开机后开机马上就关机。尝试无数次开机后失败&#xff0c;进入BIOS界面&#xff0c;发现已经没有Windows系统了。重新安装系统后&…...

Vue.js 传递路由参数和查询参数

Vue.js 传递路由参数和查询参数 在 Vue.js 开发中&#xff0c;Vue Router 提供了灵活的方式来处理路由参数和查询参数&#xff0c;使得组件能够根据不同的路径或查询条件渲染相应的内容。 路由参数 路由参数&#xff08;也称为路径参数&#xff09;是 URL 路径的一部分&…...

Java 反射机制介绍

Java学习资料 Java学习资料 Java学习资料 一、引言 在 Java 编程里&#xff0c;反射机制是一项强大且独特的特性。它赋予程序在运行时动态地获取类的信息&#xff0c;并且可以操作类或对象的属性、方法和构造函数等。借助反射机制&#xff0c;程序的灵活性和可扩展性得到显著…...

Final2x--开源AI图片放大工具

Final2x--开源AI图片放大工具 链接&#xff1a;https://pan.xunlei.com/s/VOHSklukQAquUn3GE7eHJXfOA1?pwdr3r3#...

TCP全连接队列

1. 理解 int listen(int sockfd, int backlog) 第二个参数的作用 backlog&#xff1a;表示tcp全连接队列的连接个数1。 如果连接个数等于backlog1&#xff0c;后续连接就会失败&#xff0c;假设tcp连接个数为0&#xff0c;最大连接个数就为1&#xff0c;并且不accept获取连接…...

前端力扣刷题 | 4:hot100之 子串

560. 和为K的子数组 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2 法一&#xff1a;暴力法 var subar…...

【精选】基于数据挖掘的招聘信息分析与市场需求预测系统 职位分析、求职者趋势分析 职位匹配、人才趋势、市场需求分析数据挖掘技术 职位需求分析、人才市场趋势预测

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

字节跳动发布UI-TARS,超越GPT-4o和Claude,能接管电脑完成复杂任务

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

2025课题推荐——USBL和DVL的误差建模与补偿

在海洋探测与研究中&#xff0c;定位技术的准确性至关重要。超短基线&#xff08;USBL&#xff09;和多普勒速度计&#xff08;DVL&#xff09;是两种广泛应用于水下定位和导航的技术&#xff0c;它们在深海探测、海洋工程和水下机器人等领域发挥着重要作用。然而&#xff0c;这…...

mysql_use_result的概念和使用案例

mysql_use_result 是 MySQL C API 中的一个函数&#xff0c;它用于检索一个结果集&#xff0c;并且不同于 mysql_store_result&#xff0c;它不会立即将整个结果集读入客户端。相反&#xff0c;它会初始化一个结果集&#xff0c;客户端随后可以通过调用 mysql_fetch_row 来逐行…...

微信小程序date picker的一些说明

微信小程序的picker是一个功能强大的组件&#xff0c;它可以是一个普通选择器&#xff0c;也可以是多项选择器&#xff0c;也可以是时间、日期、省市区选择器。 官方文档在这里 这里讲一下date picker的用法。 <view class"section"><view class"se…...

FLTK - FLTK1.4.1 - demo - adjuster.exe

文章目录 FLTK - FLTK1.4.1 - demo - adjuster.exe概述笔记根据代码&#xff0c;用fluid重建一个adjuster.fl 备注 - fluid生成的代码作为参考代码好了修改后可用的代码END FLTK - FLTK1.4.1 - demo - adjuster.exe 概述 想过一遍 FLTK1.4.1的demo和测试工程&#xff0c;工程…...

神经网络入门:从感知机到激活函数

什么是神经网络 神经网络&#xff08;Neural Network&#xff09; 是一种模拟生物神经系统工作方式的计算模型&#xff0c;是机器学习和深度学习的核心组成部分。它的设计灵感来源于人脑中神经元之间的连接和信息传递方式。神经网络通过模拟这种结构&#xff0c;能够从数据中学…...

第04章 13 管道信息对象分类说明

在VTK&#xff08;Visualization Toolkit&#xff09;中&#xff0c;管道信息&#xff08;Pipeline Information&#xff09;是一个重要的概念&#xff0c;它允许管道中的不同对象之间传递元数据和执行策略。这些信息对象帮助协调管道中各个组件的操作&#xff0c;确保数据在传…...