This is an automated email from the ASF dual-hosted git repository.
wangtao29 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozhera.git
The following commit(s) were added to refs/heads/master by this push:
new cab5b4e2 refactor: refactor ozhera intelligence (#612)
cab5b4e2 is described below
commit cab5b4e2f71b56a4ee8d744a39ea220321cf952d
Author: EricDing <[email protected]>
AuthorDate: Mon Nov 17 09:31:54 2025 +0800
refactor: refactor ozhera intelligence (#612)
* refactor: refactor ozhera intelligence
* feat: add coder tool and git mcp
---
ozhera-intelligence/README_cn.md | 2 +-
.../{MarkDownParam.java => CodeFixInfo.java} | 39 +--
.../domain/rootanalysis/MarkDownParam.java | 4 +
.../domain/rootanalysis/TracePromptResult.java | 3 +
.../domain/rootanalysis/constant/Prompts.java | 32 +++
.../bootstrap/IntelligenceBootStrap.java | 2 +-
.../intelligence/config/DubboConfiguration.java | 4 +-
.../intelligence/filter/TokenValidationFilter.java | 4 +-
.../src/main/resources/application.properties | 24 +-
.../resources/config/opensource-outer.properties | 23 +-
.../ozhera-intelligence-service/pom.xml | 22 ++
.../intelligence/agents/config/AgentConfig.java | 70 +++--
.../intelligence/agents/tool/CodeFixTool.java | 319 +++++++++++++++++++++
.../agents/tool/TraceAnalysisTool.java | 217 ++++++++++++++
.../ozhera/intelligence/service/LogService.java | 2 +-
.../intelligence/service/MetricsService.java | 4 +-
.../ozhera/intelligence/service/PromptService.java | 27 ++
.../intelligence/service/TraceAnalysisService.java | 8 +-
.../ozhera/intelligence/service/TraceService.java | 129 ++++++++-
19 files changed, 877 insertions(+), 58 deletions(-)
diff --git a/ozhera-intelligence/README_cn.md b/ozhera-intelligence/README_cn.md
index 8e167bab..e22b3486 100644
--- a/ozhera-intelligence/README_cn.md
+++ b/ozhera-intelligence/README_cn.md
@@ -38,7 +38,7 @@
+ TraceService,取链路数据
# 重要依赖
-会依赖小米开源的一款[Agent构建平台——m78](https://github.com/XiaoMi/mone/tree/master/m78-all)
+会依赖小米开源的一款[Agent平台——hive](https://github.com/XiaoMi/mone/tree/master/m78-all)
# 智能化工作流程(时序图)
diff --git
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MarkDownParam.java
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/CodeFixInfo.java
similarity index 65%
copy from
ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MarkDownParam.java
copy to
ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/CodeFixInfo.java
index 64f36f3b..5ba95e3e 100644
---
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MarkDownParam.java
+++
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/CodeFixInfo.java
@@ -18,26 +18,31 @@
*/
package org.apache.ozhera.intelligence.domain.rootanalysis;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
+import lombok.NoArgsConstructor;
+/**
+ * Code fix information containing projectId, envId and stacktrace
+ */
@Data
@Builder
-public class MarkDownParam {
-
- private String traceReason;
-
- private String application;
-
- private String logReason;
-
- private String metricsReason;
-
- private String simpleReason;
-
- private String traceId;
-
- private String timestamp;
-
- private String env;
+@NoArgsConstructor
+@AllArgsConstructor
+public class CodeFixInfo {
+ /**
+ * Project ID extracted from service name
+ */
+ private String projectId;
+
+ /**
+ * Environment ID extracted from process tags
+ */
+ private String envId;
+
+ /**
+ * Exception stack trace extracted from span logs
+ */
+ private String stacktrace;
}
diff --git
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MarkDownParam.java
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MarkDownParam.java
index 64f36f3b..927d7299 100644
---
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MarkDownParam.java
+++
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MarkDownParam.java
@@ -29,6 +29,10 @@ public class MarkDownParam {
private String application;
+ private String projectId;
+
+ private String envId;
+
private String logReason;
private String metricsReason;
diff --git
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TracePromptResult.java
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TracePromptResult.java
index 1c3f3b58..fa913fcf 100644
---
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TracePromptResult.java
+++
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TracePromptResult.java
@@ -35,6 +35,9 @@ public class TracePromptResult {
// Whether it is the root cause, determined based on analysis
private boolean root;
+ // project ID of the application.
+ private String projectId;
+
// Environment ID of the application,
// obtained from the value of service.env.id in process.tags of the
problem node
private String envId;
diff --git
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/constant/Prompts.java
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/constant/Prompts.java
index 30860208..44560de8 100644
---
a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/constant/Prompts.java
+++
b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/constant/Prompts.java
@@ -172,4 +172,36 @@ public class Prompts {
<simpleReason>在TestService的252行发生空指针异常,导致返回了500错误码</simpleReason>
</result>
""";
+
+ public static final String CODE_FIX_ANALYSIS_PROMPT = """
+
作为一位经验丰富的Java开发工程师,你负责分析请求链路中各个节点的Span信息,找出导致异常的根本原因节点的spanId。我们会提供一个包含请求链路全部节点的数据集。你的任务是:
+
+ 1. 确认服务之间的调用关系和依赖,从而确定每个节点在链路中的依赖层级。可以通过节点中的 references.spanID
来确认当前节点的父节点。
+ 2. 识别存在的异常节点,并找到最底层的异常节点。
+ 3. 如果 span.kind 是 client 节点,并且报服务调用超时异常,请判断其调用的 server 节点的耗时,若
server 节点耗时过高,则根本原因是 server 节点而非 client 节点。
+ 4. 返回异常根因节点的 spanId。
+
+ 请使用以下 XML 格式返回分析结果:
+ ```xml
+ <result>
+ <spanId>异常根因节点的spanId</spanId>
+ </result>
+ ```
+
+ 请确保结果为有效的 XML 数据。如果传入的 data 数据为空,则返回以下固定的内容:
+ ```xml
+ <result>
+ <spanId></spanId>
+ </result>
+ ```
+
+ ## 返回结果示例:
+ ```xml
+ <result>
+ <spanId>a291ab32156dca70</spanId>
+ </result>
+ ```
+
+ 以下是提供的数据集:
+ """;
}
diff --git
a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/bootstrap/IntelligenceBootStrap.java
b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/bootstrap/IntelligenceBootStrap.java
index fc258db6..a9e4dfcd 100644
---
a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/bootstrap/IntelligenceBootStrap.java
+++
b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/bootstrap/IntelligenceBootStrap.java
@@ -24,7 +24,7 @@ import
org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@EnableAutoConfiguration
-@ComponentScan(basePackages = {"org.apache.ozhera.intelligence"})
+@ComponentScan(basePackages = {"org.apache.ozhera.intelligence",
"run.mone.mcp.git"})
@Slf4j
public class IntelligenceBootStrap {
public static void main(String... args) {
diff --git
a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/DubboConfiguration.java
b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/DubboConfiguration.java
index f672299f..5886d7db 100644
---
a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/DubboConfiguration.java
+++
b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/DubboConfiguration.java
@@ -37,13 +37,13 @@ public class DubboConfiguration {
@Value("${server.port}")
private String httpGateWayPort;
- @NacosValue("${dubbo.registry.address}")
+ @Value("${dubbo.registry.address}")
private String regAddress;
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
- applicationConfig.setName("Trace-etl-manager");
+ applicationConfig.setName("ozhera-intelligence");
applicationConfig.setParameters(Maps.newHashMap());
applicationConfig.getParameters().put("http_gateway_port",
httpGateWayPort);
applicationConfig.getParameters().put("dubbo_version", "1.0");
diff --git
a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/filter/TokenValidationFilter.java
b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/filter/TokenValidationFilter.java
index 572da052..f0eb8c08 100644
---
a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/filter/TokenValidationFilter.java
+++
b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/filter/TokenValidationFilter.java
@@ -18,9 +18,9 @@
*/
package org.apache.ozhera.intelligence.filter;
-import com.alibaba.nacos.api.config.annotation.NacosValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -35,7 +35,7 @@ public class TokenValidationFilter extends
OncePerRequestFilter {
private static final Logger logger =
LoggerFactory.getLogger(TokenValidationFilter.class);
- @NacosValue("${analyze.token}")
+ @Value("${analyze.token}")
private String validToken;
@Override
diff --git
a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties
b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties
index 8bebaee4..56b6c1eb 100644
---
a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties
+++
b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties
@@ -32,4 +32,26 @@ log.query.version=${log.query.version}
nacos.address=${nacos.address}
# agents name
-trace.agent.name=${trace.agent.name}
\ No newline at end of file
+mcp.agent.name=${mcp.agent.name}
+
+# Trace span sectional API
+trace.span.sectional.url=${trace.span.sectional.url}
+# Trace query mode: http or dubbo
+trace.query.mode=${trace.query.mode}
+
+hive.manager.base-url=${hive.manager.base-url}
+hive.manager.reg.switch=${hive.manager.reg.switch}
+hive.manager.token=${hive.manager.token}
+# mcp llm config from LLMProvider
+mcp.llm=${mcp.llm}
+mcp.transport.type=${mcp.transport.type}
+mcp.grpc.port=${mcp.grpc.port}
+
+hera.trace.url=${hera.trace.url}
+dubbo.registry.address=${dubbo.registry.address}
+analyze.token=${analyze.token}
+prometheus.api.url=${prometheus.api.url}
+
+# Git Default Configuration
+git.default.username=${git.default.username}
+git.default.token=${git.default.token}
\ No newline at end of file
diff --git
a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties
b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties
index 88360392..f279dcd9 100644
---
a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties
+++
b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties
@@ -31,4 +31,25 @@ log.query.version=1.0
nacos.address=nacos:80
-trace.agent.name=trace-analysis
\ No newline at end of file
+mcp.agent.name=trace-analysis
+
+# Trace span sectional API
+trace.span.sectional.url=http://xx
+# Trace query mode: http or dubbo,
+trace.query.mode=dubbo
+
+hive.manager.base-url=http://xxxx
+hive.manager.reg.switch=true
+hive.manager.token=xxxxx
+mcp.llm=
+mcp.transport.type=grpc
+mcp.grpc.port=9288
+
+hera.trace.url=
+dubbo.registry.address=nacos://nacos:80
+analyze.token=
+prometheus.api.url=
+
+# Git Default Configuration
+git.default.username=xxx
+git.default.token=xxx
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-service/pom.xml
b/ozhera-intelligence/ozhera-intelligence-service/pom.xml
index 73b222c7..35469a45 100644
--- a/ozhera-intelligence/ozhera-intelligence-service/pom.xml
+++ b/ozhera-intelligence/ozhera-intelligence-service/pom.xml
@@ -36,6 +36,28 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>run.mone</groupId>
+ <artifactId>mcp-miline</artifactId>
+ <version>1.6.1-jdk21-SNAPSHOT</version>
+ <exclusions>
+ <exclusion>
+ <artifactId>spring-webmvc</artifactId>
+ <groupId>org.springframework</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>run.mone</groupId>
+ <artifactId>mcp-git</artifactId>
+ <version>1.6.1-jdk21-SNAPSHOT</version>
+ <exclusions>
+ <exclusion>
+ <artifactId>spring-webmvc</artifactId>
+ <groupId>org.springframework</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
<dependency>
<groupId>org.apache.ozhera</groupId>
<artifactId>ozhera-intelligence-domain</artifactId>
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/config/AgentConfig.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/config/AgentConfig.java
index a5b0cae2..eedac345 100644
---
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/config/AgentConfig.java
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/config/AgentConfig.java
@@ -19,48 +19,82 @@
package org.apache.ozhera.intelligence.agents.config;
import com.google.common.collect.Lists;
-import org.apache.ozhera.intelligence.agents.function.TraceAnalysisFunction;
+import org.apache.ozhera.intelligence.agents.tool.CodeFixTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import run.mone.hive.mcp.function.ChatFunction;
-import run.mone.hive.mcp.service.Rag;
import run.mone.hive.mcp.service.RoleMeta;
-import run.mone.hive.mcp.service.WebQuery;
-import run.mone.hive.roles.tool.AskTool;
-import run.mone.hive.roles.tool.AttemptCompletionTool;
-import run.mone.hive.roles.tool.ChatTool;
-import run.mone.hive.roles.tool.SpeechToTextTool;
-import run.mone.hive.roles.tool.TextToSpeechTool;
+import run.mone.hive.roles.tool.*;
+import run.mone.mcp.git.tool.GitCloneTool;
+import run.mone.mcp.git.tool.GitCommitTool;
+import run.mone.mcp.git.tool.GitPushTool;
+import run.mone.mcp.miline.tools.GetPipelineDetailTool;
+import run.mone.mcp.miline.tools.RunPipelineTool;
+
@Configuration
public class AgentConfig {
- @Value("${trace.agent.name}")
+ @Value("${mcp.agent.name}")
private String agentName;
@Autowired
- private TraceAnalysisFunction traceAnalysisFunction;
+ private CodeFixTool codeFixTool;
+
+ @Autowired
+ private GitCloneTool gitCloneTool;
+ @Autowired
+ private GitCommitTool gitCommitTool;
+ @Autowired
+ private GitPushTool gitPushTool;
+
+ private boolean isRemoteFile = false;
@Bean
public RoleMeta roleMeta() {
+ ChatFunction chat = new ChatFunction(agentName, 20);
+
chat.setDesc("和%s聊天,问问%s关于OzHera可观测性的服务监控和链路追踪相关的数据。支持各种形式如:'%s'、'请%s告诉我监控数据'、'让%s帮我看看服务状态'、'%s你知道服务有什么问题'等。支持上下文连续对话。");
+
return RoleMeta.builder()
- .profile("你是一名优秀的工程师,你擅长分析logging tracing
metrics,并且根据这些信息发现代码问题.")
- .goal("你的目标是帮助用户发现线上问题")
- .constraints("不要探讨任何和技术不相关的东西,如果用户问你,你可以直接拒绝掉")
+ .name("OzHera可观测系统专家")
+ .profile("你是OzHera可观测系统专家,精通分布式系统的监控和链路追踪,能够帮助用户诊断和解决复杂的系统问题")
+
.goal("你的目标是根据用户输入返回OzHera中专业的监控数据和链路追踪数据,帮助用户快速定位和解决系统中的异常和性能问题")
+ .constraints("不要探讨一些负面的东西,如果用户问你,你可以直接拒绝掉")
//允许自动从知识库获取内容(意图识别的小模型)
-
.webQuery(WebQuery.builder().autoWebQuery(false).modelType("bert").version("").releaseServiceName("").build())
-
.rag(Rag.builder().autoRag(false).modelType("").version("").releaseServiceName("").build())
+//
.webQuery(WebQuery.builder().autoWebQuery(true).modelType("bert").version("finetune-bert-20250605-73a29258").releaseServiceName("bert-is-network").build())
+//
.rag(Rag.builder().autoRag(true).modelType("bert").version("finetune-bert-20250605-ed8acbcf").releaseServiceName("bert-is-knowledge-base").build())
//内部工具
.tools(Lists.newArrayList(
new ChatTool(),
new AskTool(),
new AttemptCompletionTool(),
- new SpeechToTextTool(),
- new TextToSpeechTool()))
+ codeFixTool,
+ new ListFilesTool(isRemoteFile),
+ new ExecuteCommandToolOptimized(),
+ new ReadFileTool(isRemoteFile),
+ new SearchFilesTool(isRemoteFile),
+ new ReplaceInFileTool(isRemoteFile),
+ new ListCodeDefinitionNamesTool(),
+ new WriteToFileTool(isRemoteFile),
+ new RunPipelineTool(),
+ new GetPipelineDetailTool(),
+ gitCloneTool,
+ gitCommitTool,
+ gitPushTool
+ ))
//mcp工具
- .mcpTools(Lists.newArrayList(new ChatFunction(agentName,60),
traceAnalysisFunction))
+ .mcpTools(Lists.newArrayList(chat))
+ .workflow("""
+ 你是代码级自动异常修复系统,严格按照以下步骤执行:
+ 1、根据traceId获取链路上根因节点的项目信息与异常信息
+ 2、根据根因节点的projectId和envId获取流水线详情
+
3、根据流水线详情中的gitUrl、gitBranch、gitCommitId调用git_clone工具进行git clone
+ 4、根据trace链路上的异常信息,结合项目代码进行异常修复
+ 5、修复完成后,将本地代码使用git_commit工具进行git
commit,commit信息是自动代码修复, 使用git_push进行git push
+ 6、根据projectId和envId调用RunPipelineTool进行发布
+ """)
.build();
}
}
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/tool/CodeFixTool.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/tool/CodeFixTool.java
new file mode 100644
index 00000000..5f42f7d7
--- /dev/null
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/tool/CodeFixTool.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.agents.tool;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ozhera.intelligence.domain.rootanalysis.CodeFixInfo;
+import org.apache.ozhera.intelligence.domain.rootanalysis.TraceQueryParam;
+import org.apache.ozhera.intelligence.service.PromptService;
+import org.apache.ozhera.intelligence.service.TraceService;
+import org.apache.ozhera.trace.etl.domain.jaegeres.JaegerAttribute;
+import org.apache.ozhera.trace.etl.domain.jaegeres.JaegerLogs;
+import org.apache.ozhera.trace.etl.domain.tracequery.Span;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import run.mone.hive.roles.ReactorRole;
+import run.mone.hive.roles.tool.ITool;
+
+import java.util.List;
+
+/**
+ * Code fix tool for analyzing trace and extracting error information
+ * <p>
+ * This tool analyzes trace data to find the root cause of exceptions,
+ * and extracts project ID, environment ID and stack trace information
+ * for code fixing purposes.
+ */
+@Slf4j
+@Component
+public class CodeFixTool implements ITool {
+
+ @Value("${server.type}")
+ private String env;
+
+ /**
+ * Tool name
+ */
+ public static final String name = "code_fix";
+
+ /**
+ * Trace service for querying spans
+ */
+ @Autowired
+ private TraceService traceService;
+
+ /**
+ * Prompt service for AI analysis
+ */
+ @Autowired
+ private PromptService promptService;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean needExecute() {
+ return true;
+ }
+
+ @Override
+ public boolean show() {
+ return true;
+ }
+
+ @Override
+ public String description() {
+ return """
+ 根据traceId智能分析trace链路,提取异常根因节点的相关信息用于代码修复。
+ 该工具会自动识别异常节点,并提取项目ID、环境ID和异常堆栈信息。
+
+ **使用场景:**
+ - 分析异常trace链路,定位错误根因
+ - 提取异常信息用于自动化代码修复
+ - 获取项目和环境信息用于问题定位
+
+ **分析能力:**
+ - 自动识别链路中的异常节点
+ - 提取异常堆栈信息
+ - 解析项目ID和环境ID
+ - 返回完整的代码修复所需信息
+
+ **重要提示:**
+ - traceId必须是32位由0-9和a-f组成的随机字符串
+ - 确保traceId对应的链路数据已经采集完整
+ """;
+ }
+
+ @Override
+ public String parameters() {
+ return """
+ - traceId: (必填) 追踪ID,32位由0-9和a-f组成的随机字符串
+ """;
+ }
+
+ @Override
+ public String usage() {
+ String taskProgress = """
+ <task_progress>
+ 任务进度记录(可选)
+ </task_progress>
+ """;
+ if (!taskProgress()) {
+ taskProgress = "";
+ }
+ return """
+ <code_fix>
+ <traceId>32位追踪ID</traceId>
+ %s
+ </code_fix>
+ """.formatted(taskProgress);
+ }
+
+ @Override
+ public String example() {
+ return """
+ 示例: 分析异常trace并提取修复信息
+ <code_fix>
+ <traceId>a1b2c3d4e5f6789012345678abcdef01</traceId>
+ </code_fix>
+ """;
+ }
+
+ @Override
+ public JsonObject execute(ReactorRole role, JsonObject inputJson) {
+ JsonObject result = new JsonObject();
+
+ try {
+ // Validate required parameter
+ if (!inputJson.has("traceId") ||
StringUtils.isBlank(inputJson.get("traceId").getAsString())) {
+ log.error("code_fix operation missing required parameter
traceId");
+ result.addProperty("error", "缺少必填参数 'traceId'");
+ return result;
+ }
+
+ // Get traceId parameter
+ String traceId = inputJson.get("traceId").getAsString().trim();
+
+ if (traceId.isEmpty()) {
+ log.warn("traceId parameter is empty");
+ result.addProperty("error", "参数错误:traceId不能为空");
+ return result;
+ }
+
+ // Validate traceId format (32-character hexadecimal string)
+ if (!isValidTraceId(traceId)) {
+ log.warn("Invalid traceId format: {}", traceId);
+ result.addProperty("error",
"参数错误:traceId必须是32位由0-9和a-f组成的字符串");
+ return result;
+ }
+
+ log.info("Starting code fix analysis for traceId: {}", traceId);
+
+ // Query spans from trace service
+ TraceQueryParam queryParam = TraceQueryParam.builder()
+ .traceId(traceId)
+ .env(env)
+ .build();
+ List<Span> spans = traceService.queryTraceRootAnalysis(queryParam);
+
+ if (spans == null || spans.isEmpty()) {
+ log.warn("No spans found for traceId: {}", traceId);
+ result.addProperty("error", "未找到对应的trace数据");
+ return result;
+ }
+
+ // Convert spans to JSON for AI analysis
+ Gson gson = new Gson();
+ String spansJson = gson.toJson(spans);
+
+ // Call AI to analyze and get root cause spanId
+ String rootCauseSpanId = promptService.codeFixAnalysis(spansJson);
+
+ if (StringUtils.isBlank(rootCauseSpanId)) {
+ log.warn("Failed to identify root cause spanId for traceId:
{}", traceId);
+ result.addProperty("error", "无法识别异常根因节点");
+ return result;
+ }
+
+ log.info("Identified root cause spanId: {}", rootCauseSpanId);
+
+ // Find the span with matching spanId
+ Span rootCauseSpan = null;
+ for (Span span : spans) {
+ if (rootCauseSpanId.equals(span.getSpanID())) {
+ rootCauseSpan = span;
+ break;
+ }
+ }
+
+ if (rootCauseSpan == null) {
+ log.warn("Root cause span not found with spanId: {}",
rootCauseSpanId);
+ result.addProperty("error", "未找到对应的根因节点");
+ return result;
+ }
+
+ // Extract information from the root cause span
+ CodeFixInfo codeFixInfo = extractCodeFixInfo(rootCauseSpan);
+
+ if (codeFixInfo == null) {
+ log.warn("Failed to extract code fix info from span");
+ result.addProperty("error", "提取代码修复信息失败");
+ return result;
+ }
+
+ // Set success response
+ result.addProperty("success", true);
+ result.addProperty("traceId", traceId);
+ result.addProperty("spanId", rootCauseSpanId);
+ result.addProperty("projectId", codeFixInfo.getProjectId());
+ result.addProperty("envId", codeFixInfo.getEnvId());
+ result.addProperty("stacktrace", codeFixInfo.getStacktrace());
+
+ log.info("Successfully analyzed code fix info for traceId: {}",
traceId);
+
+ return result;
+
+ } catch (Exception e) {
+ log.error("Error executing code_fix operation", e);
+ result.addProperty("error", "代码修复分析失败:" + e.getMessage());
+ result.addProperty("success", false);
+ return result;
+ }
+ }
+
+ /**
+ * Extract code fix information from a span
+ *
+ * @param span the root cause span
+ * @return CodeFixInfo containing projectId, envId and stacktrace
+ */
+ private CodeFixInfo extractCodeFixInfo(Span span) {
+ try {
+ // Extract projectId from serviceName
+ String serviceName = span.getProcess().getServiceName();
+ String projectId = null;
+ if (serviceName != null && serviceName.contains("-")) {
+ projectId = serviceName.split("-")[0];
+ }
+
+ // Extract envId from process tags
+ String envId = null;
+ List<JaegerAttribute> tags = span.getProcess().getTags();
+ if (tags != null) {
+ for (JaegerAttribute tag : tags) {
+ if ("service.env.id".equals(tag.getKey())) {
+ envId = tag.getValue();
+ break;
+ }
+ }
+ }
+
+ // Extract stacktrace from logs
+ String stacktrace = null;
+ List<JaegerLogs> logs = span.getLogs();
+ if (logs != null) {
+ for (JaegerLogs log : logs) {
+ List<JaegerAttribute> fields = log.getFields();
+ if (fields != null) {
+ for (JaegerAttribute field : fields) {
+ if ("exception.stacktrace".equals(field.getKey()))
{
+ stacktrace = field.getValue();
+ break;
+ }
+ }
+ if (stacktrace != null) {
+ break;
+ }
+ }
+ }
+ }
+
+ return CodeFixInfo.builder()
+ .projectId(projectId)
+ .envId(envId)
+ .stacktrace(stacktrace)
+ .build();
+
+ } catch (Exception e) {
+ log.error("Error extracting code fix info from span", e);
+ return null;
+ }
+ }
+
+ /**
+ * Validate traceId format
+ * TraceId should be a 32-character string composed of 0-9 and a-f
+ *
+ * @param traceId trace ID
+ * @return whether the format is correct
+ */
+ private boolean isValidTraceId(String traceId) {
+ if (traceId == null || traceId.length() != 32) {
+ return false;
+ }
+ // Check if it only contains 0-9 and a-f characters
+ return traceId.matches("[0-9a-fA-F]{32}");
+ }
+}
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/tool/TraceAnalysisTool.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/tool/TraceAnalysisTool.java
new file mode 100644
index 00000000..ba2dbc1a
--- /dev/null
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/agents/tool/TraceAnalysisTool.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.agents.tool;
+
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ozhera.intelligence.domain.rootanalysis.TraceQueryParam;
+import org.apache.ozhera.intelligence.service.TraceAnalysisService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import run.mone.hive.roles.ReactorRole;
+import run.mone.hive.roles.tool.ITool;
+
+/**
+ * Trace链路分析工具
+ * <p>
+ * 该工具用于根据traceId分析trace链路上异常或慢查询出现的根本原因。
+ * 通过AI智能分析,帮助开发人员快速定位问题根源,提高问题排查效率。
+ * <p>
+ * 使用场景:
+ * - 分析异常trace链路,定位错误根因
+ * - 分析慢查询trace链路,找出性能瓶颈
+ * - 排查分布式系统中的链路问题
+ * - 辅助故障诊断和性能优化
+ */
+@Slf4j
+@Component
+public class TraceAnalysisTool implements ITool {
+
+ @Value("${server.type}")
+ private String env;
+
+ /**
+ * 工具名称
+ */
+ public static final String name = "trace_analysis";
+
+ /**
+ * Trace分析服务
+ */
+ @Autowired
+ private TraceAnalysisService traceAnalysisService;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean needExecute() {
+ return true;
+ }
+
+ @Override
+ public boolean show() {
+ return true;
+ }
+
+ @Override
+ public String description() {
+ return """
+ 根据traceId智能分析trace链路上异常或慢查询出现的根本原因。该工具利用AI能力,
+ 深度分析分布式链路追踪数据,帮助快速定位问题根源。
+
+ **使用场景:**
+ - 分析异常trace链路,快速定位错误根因
+ - 分析慢查询trace链路,找出性能瓶颈所在
+ - 排查分布式系统中的调用链路问题
+ - 辅助开发人员进行故障诊断
+ - 支持性能优化和系统调优
+
+ **分析能力:**
+ - 自动识别链路中的异常节点
+ - 分析慢查询的原因和影响范围
+ - 提供问题根因的详细说明
+ - 给出优化建议和解决方案
+
+ **重要提示:**
+ - traceId必须是32位由0-9和a-f组成的随机字符串
+ - 默认分析线上环境(online)的trace数据
+ - 确保traceId对应的链路数据已经采集完整
+ """;
+ }
+
+ @Override
+ public String parameters() {
+ return """
+ - traceId: (必填) 追踪ID,32位由0-9和a-f组成的随机字符串
+ """;
+ }
+
+ @Override
+ public String usage() {
+ String taskProgress = """
+ <task_progress>
+ 任务进度记录(可选)
+ </task_progress>
+ """;
+ if (!taskProgress()) {
+ taskProgress = "";
+ }
+ return """
+ <trace_analysis>
+ <traceId>32位追踪ID</traceId>
+ %s
+ </trace_analysis>
+ """.formatted(taskProgress);
+ }
+
+ @Override
+ public String example() {
+ return """
+ 示例 1: 分析异常trace链路
+ <trace_analysis>
+ <traceId>a1b2c3d4e5f6789012345678abcdef01</traceId>
+ </trace_analysis>
+
+ 示例 2: 分析慢查询trace链路
+ <trace_analysis>
+ <traceId>fedcba9876543210f6e5d4c3b2a10000</traceId>
+ </trace_analysis>
+
+ 示例 3: 排查分布式链路问题
+ <trace_analysis>
+ <traceId>123456789abcdef0123456789abcdef0</traceId>
+ </trace_analysis>
+ """;
+ }
+
+ @Override
+ public JsonObject execute(ReactorRole role, JsonObject inputJson) {
+ JsonObject result = new JsonObject();
+
+ try {
+ // 验证必填参数
+ if (!inputJson.has("traceId") ||
StringUtils.isBlank(inputJson.get("traceId").getAsString())) {
+ log.error("trace_analysis 操作缺少必填参数 traceId");
+ result.addProperty("error", "缺少必填参数 'traceId'");
+ return result;
+ }
+
+ // 获取traceId参数
+ String traceId = inputJson.get("traceId").getAsString().trim();
+
+ if (traceId.isEmpty()) {
+ log.warn("traceId 参数为空");
+ result.addProperty("error", "参数错误:traceId不能为空");
+ return result;
+ }
+
+ // 验证traceId格式(32位16进制字符串)
+ if (!isValidTraceId(traceId)) {
+ log.warn("traceId 格式不正确: {}", traceId);
+ result.addProperty("error",
"参数错误:traceId必须是32位由0-9和a-f组成的字符串");
+ return result;
+ }
+
+ log.info("开始分析trace链路,traceId: {}", traceId);
+
+ // 调用服务分析trace根本原因,默认环境为online
+ TraceQueryParam queryParam = TraceQueryParam.builder()
+ .traceId(traceId)
+ .env(env)
+ .build();
+ String analysisResult =
traceAnalysisService.analyzeTraceRoot(queryParam);
+
+ // 设置成功响应
+ result.addProperty("result", analysisResult);
+ result.addProperty("traceId", traceId);
+ result.addProperty("environment", "online");
+ result.addProperty("success", true);
+
+ log.info("成功分析trace链路,traceId: {}", traceId);
+
+ return result;
+
+ } catch (Exception e) {
+ log.error("执行 trace_analysis 操作时发生异常", e);
+ result.addProperty("error", "分析trace链路失败:" + e.getMessage());
+ result.addProperty("success", false);
+ return result;
+ }
+ }
+
+ /**
+ * 验证traceId格式是否正确
+ * traceId应该是32位由0-9和a-f组成的字符串
+ *
+ * @param traceId 追踪ID
+ * @return 格式是否正确
+ */
+ private boolean isValidTraceId(String traceId) {
+ if (traceId == null || traceId.length() != 32) {
+ return false;
+ }
+ // 检查是否只包含0-9和a-f字符
+ return traceId.matches("[0-9a-fA-F]{32}");
+ }
+}
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java
index 7502c4a8..0069f5af 100644
---
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java
@@ -30,7 +30,7 @@ import java.util.Map;
@Service
public class LogService {
- @DubboReference(interfaceClass = HeraLogApiService.class, group =
"${log.query.group}", version = "${log.query.version}")
+ @DubboReference(interfaceClass = HeraLogApiService.class, group =
"${log.query.group}", version = "${log.query.version}", check = false)
private HeraLogApiService heraLogApiService;
/**
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java
index 506fdf38..cf5a1330 100644
---
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java
@@ -18,7 +18,6 @@
*/
package org.apache.ozhera.intelligence.service;
-import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.utils.URIBuilder;
@@ -26,6 +25,7 @@ import
org.apache.ozhera.intelligence.domain.rootanalysis.HeraRootCaseAnalyseRes
import
org.apache.ozhera.intelligence.domain.rootanalysis.MetricDataRangeSetVector;
import
org.apache.ozhera.intelligence.domain.rootanalysis.MetricRangeResponseVector;
import org.apache.ozhera.intelligence.util.HttpClient;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.apache.ozhera.intelligence.domain.rootanalysis.MetricsQueryParam;
@@ -46,7 +46,7 @@ import static
org.apache.ozhera.intelligence.util.CommitPoolUtil.HERA_SOLUTION_M
@Slf4j
public class MetricsService {
- @NacosValue("${prometheus.api.url}")
+ @Value("${prometheus.api.url}")
private String prometheusUrl;
private static final String URI_QUERY_RANGE = "/api/v1/query_range";
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/PromptService.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/PromptService.java
index 1456dcdd..ac06c16e 100644
---
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/PromptService.java
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/PromptService.java
@@ -75,6 +75,7 @@ public class PromptService {
.traceReason(keyValuePairs.get(TracePromptResult.TRACE_REASON_KEY))
.application(keyValuePairs.get(TracePromptResult.APPLICATION))
.root(Boolean.parseBoolean(keyValuePairs.get(TracePromptResult.ROOT)))
+ .projectId(keyValuePairs.get(TracePromptResult.PROJECT_ID))
.envId(keyValuePairs.get(TracePromptResult.ENV_ID))
.startTime(keyValuePairs.get(TracePromptResult.START_TIME))
.duration(keyValuePairs.get(TracePromptResult.DURATION))
@@ -179,4 +180,30 @@ public class PromptService {
// Return simplified analysis result
return keyValuePairs.get("simpleReason");
}
+
+ /**
+ * Perform LLM analysis based on trace data to find the root cause spanId
for code fix
+ *
+ * @param trace trace link data
+ * @return spanId of the root cause node
+ */
+ public String codeFixAnalysis(String trace) {
+ // Get the corresponding prompt
+ String prompt = Prompts.CODE_FIX_ANALYSIS_PROMPT + "\n" + trace;
+
+ // Call LLM for analysis
+ String llmRes = llm.chat(prompt);
+
+ // Parse the XML result returned by LLM
+ List<ToolDataInfo> tools = new MultiXmlParser().parse(llmRes);
+ if (tools == null || tools.isEmpty()) {
+ // If parsing result is empty, return null
+ return null;
+ }
+
+ Map<String, String> keyValuePairs = tools.get(0).getKeyValuePairs();
+
+ // Return spanId
+ return keyValuePairs.get("spanId");
+ }
}
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceAnalysisService.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceAnalysisService.java
index 12cafa7b..083d5aa5 100644
---
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceAnalysisService.java
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceAnalysisService.java
@@ -18,12 +18,12 @@
*/
package org.apache.ozhera.intelligence.service;
-import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.ozhera.intelligence.domain.rootanalysis.*;
import org.apache.ozhera.trace.etl.domain.tracequery.Span;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -36,7 +36,7 @@ import java.util.Map;
@Service
public class TraceAnalysisService {
- @NacosValue("${hera.trace.url}")
+ @Value("${hera.trace.url}")
private String heraTraceUrl;
@Autowired
@@ -148,6 +148,8 @@ public class TraceAnalysisService {
// Get simplified root cause analysis results
String application = traceResult != null ?
traceResult.getApplication() : "";
String traceReason = traceResult != null ?
traceResult.getTraceReason() : "";
+ String projectId = traceResult != null ? traceResult.getProjectId() :
"";
+ String envId = traceResult != null ? traceResult.getEnvId() : "";
String logReason = logResult != null ? logResult.getLogReason() : "";
String metricsReason = metricsResult != null ?
metricsResult.getMetricsReason() : "";
@@ -155,6 +157,8 @@ public class TraceAnalysisService {
// Build MarkDownParam parameters
MarkDownParam markDownParam = MarkDownParam.builder()
+ .projectId(projectId)
+ .envId(envId)
.application(application)
.traceReason(traceReason)
.logReason(logReason)
diff --git
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java
index d2740da8..928709f4 100644
---
a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java
+++
b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java
@@ -18,17 +18,25 @@
*/
package org.apache.ozhera.intelligence.service;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
+import org.apache.http.client.utils.URIBuilder;
import org.apache.ozhera.intelligence.domain.rootanalysis.TraceTreeNode;
+import org.apache.ozhera.intelligence.util.HttpClient;
import org.apache.ozhera.trace.etl.api.service.TraceQueryService;
import org.apache.ozhera.trace.etl.domain.jaegeres.JaegerAttribute;
import org.apache.ozhera.trace.etl.domain.tracequery.Span;
import org.apache.ozhera.trace.etl.domain.tracequery.Trace;
import org.apache.ozhera.trace.etl.domain.tracequery.TraceIdQueryVo;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.apache.ozhera.intelligence.domain.rootanalysis.TraceQueryParam;
import org.apache.ozhera.trace.etl.domain.jaegeres.JaegerProcess;
+import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -36,15 +44,28 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+@Slf4j
@Service
public class TraceService {
/**
* call trace-etl-manager TraceQueryService, The group and version in
Dubbo should be consistent with the group and version in TraceQueryServiceImpl
in trace-etl-manager.
*/
- @DubboReference(interfaceClass = TraceQueryService.class, group =
"${trace.query.group}", version = "${trace.query.version}")
+ @DubboReference(interfaceClass = TraceQueryService.class, group =
"${trace.query.group}", version = "${trace.query.version}", check = false)
private TraceQueryService traceQueryService;
+ /**
+ * Trace span sectional API URL
+ */
+ @Value("${trace.span.sectional.url}")
+ private String traceSpanSectionalUrl;
+
+ /**
+ * Trace query mode: http or dubbo
+ */
+ @Value("${trace.query.mode:dubbo}")
+ private String traceQueryMode;
+
/**
* The time range threshold for querying before and after the given
timestamp.
* The actual query range is calculated by subtracting and adding
QUERY_TIME_RANGE to the given timestamp.
@@ -65,18 +86,106 @@ public class TraceService {
/**
* Query trace based on the specified trace query conditions.
*
- * @param param
- * @return
+ * @param param trace query parameters
+ * @return list of filtered spans
*/
public List<Span> queryTraceRootAnalysis(TraceQueryParam param) {
- Trace traceResult =
traceQueryService.getByTraceId(buildTraceIdQueryVo(param));
- List<Span> result = new ArrayList<>();
- if (traceResult != null && traceResult.getSpans() != null &&
!traceResult.getSpans().isEmpty()) {
- // analyze and cut span
- List<Span> analyze = analyzeTrace(traceResult.getSpans(),
TRACE_DEEP);
- result = getSpansFilter(analyze);
+ // Check query mode configuration
+ if ("http".equalsIgnoreCase(traceQueryMode)) {
+ // Use HTTP API - return result directly without filtering
(already filtered by API)
+ log.info("Using HTTP mode to query trace spans");
+ try {
+ List<Span> spans = querySpansBySectionalApi(param);
+ if (spans != null) {
+ log.info("Successfully fetched {} spans from sectional
API", spans.size());
+ return spans;
+ } else {
+ log.warn("HTTP API returned null, returning empty list");
+ return new ArrayList<>();
+ }
+ } catch (Exception e) {
+ log.error("Failed to fetch spans from sectional API", e);
+ return new ArrayList<>();
+ }
+ } else {
+ // Use Dubbo mode - apply analysis and filtering logic
+ log.info("Using Dubbo mode to query trace spans");
+ Trace traceResult =
traceQueryService.getByTraceId(buildTraceIdQueryVo(param));
+ List<Span> result = new ArrayList<>();
+ if (traceResult != null && traceResult.getSpans() != null &&
!traceResult.getSpans().isEmpty()) {
+ // analyze and cut span
+ List<Span> analyze = analyzeTrace(traceResult.getSpans(),
TRACE_DEEP);
+ result = getSpansFilter(analyze);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Query spans from the sectional API via HTTP
+ *
+ * @param param trace query parameters
+ * @return list of spans
+ */
+ private List<Span> querySpansBySectionalApi(TraceQueryParam param) {
+ try {
+ String url = buildSectionalApiUrl(param);
+ log.info("Calling sectional API: {}", url);
+
+ String response = HttpClient.get(url, new HashMap<>());
+ if (response == null || response.isEmpty()) {
+ log.error("Empty response from sectional API");
+ return null;
+ }
+
+ // Parse the JSON response into Result<List<Span>>
+ Gson gson = new Gson();
+ JsonObject jsonObject = gson.fromJson(response, JsonObject.class);
+
+ // Check if response is successful (code == 0 or code == 200)
+ if (jsonObject.has("code")) {
+ int code = jsonObject.get("code").getAsInt();
+ if (code != 0 && code != 200) {
+ String message = jsonObject.has("message") ?
jsonObject.get("message").getAsString() : "Unknown error";
+ log.error("Failed to get spans from sectional API, code:
{}, message: {}", code, message);
+ return null;
+ }
+ }
+
+ // Extract data field and parse as List<Span>
+ if (jsonObject.has("data") &&
!jsonObject.get("data").isJsonNull()) {
+ TypeToken<List<Span>> typeToken = new TypeToken<List<Span>>()
{};
+ List<Span> spans = gson.fromJson(jsonObject.get("data"),
typeToken.getType());
+ return spans;
+ } else {
+ log.warn("No data field in response from sectional API");
+ return null;
+ }
+ } catch (Exception e) {
+ log.error("Error calling sectional API", e);
+ return null;
+ }
+ }
+
+ /**
+ * Build the URL for the sectional API
+ *
+ * @param param trace query parameters
+ * @return complete URL with query parameters
+ */
+ private String buildSectionalApiUrl(TraceQueryParam param) {
+ try {
+ URIBuilder builder = new URIBuilder(traceSpanSectionalUrl);
+ builder.addParameter("traceId", param.getTraceId());
+ builder.addParameter("env", param.getEnv());
+ if(param.getTimeStamp() != null) {
+ builder.addParameter("time",
String.valueOf(param.getTimeStamp()));
+ }
+ return builder.build().toString();
+ } catch (URISyntaxException e) {
+ log.error("Error building sectional API URL", e);
+ throw new RuntimeException("Failed to build sectional API URL", e);
}
- return result;
}
/**
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]