From fb9563e3324dead8eaf3f4e288aba679eaae7142 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Sun, 5 Apr 2026 09:22:20 -0700 Subject: [PATCH] feat: add SkyWalking integration plugin and guide - add magic-api-plugin-skywalking module and auto configuration - add request interceptor and tests for SkyWalking tracing - add integration guide and README entry for issue #122 Closes #122 --- README.md | 7 +- SKYWALKING_INTEGRATION.md | 95 ++++++++++++++++ .../magic-api-plugin-skywalking/pom.xml | 45 ++++++++ .../MagicSkyWalkingConfiguration.java | 22 ++++ .../skywalking/SkyWalkingInterceptor.java | 104 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + .../skywalking/SkyWalkingInterceptorTest.java | 73 ++++++++++++ magic-api-plugins/pom.xml | 1 + 8 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 SKYWALKING_INTEGRATION.md create mode 100644 magic-api-plugins/magic-api-plugin-skywalking/pom.xml create mode 100644 magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/MagicSkyWalkingConfiguration.java create mode 100644 magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptor.java create mode 100644 magic-api-plugins/magic-api-plugin-skywalking/src/main/resources/META-INF/spring.factories create mode 100644 magic-api-plugins/magic-api-plugin-skywalking/src/test/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptorTest.java diff --git a/README.md b/README.md index 913aba7f..7dfa1e14 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@

-[特性](#特性) | [快速开始](#快速开始) | [文档/演示](#文档演示) | [示例项目](#示例项目) | 更新日志 | [项目截图](#项目截图) | [交流群](#交流群) +[特性](#特性) | [快速开始](#快速开始) | [文档/演示](#文档演示) | [示例项目](#示例项目) | [SkyWalking集成](#skywalking集成) | 更新日志 | [项目截图](#项目截图) | [交流群](#交流群) # 简介 @@ -76,6 +76,11 @@ magic-api.resource.location=/data/magic-api - [magic-api-example](https://gitee.com/ssssssss-team/magic-api-example) +# SkyWalking集成 + +- 使用说明文档:[`SKYWALKING_INTEGRATION.md`](SKYWALKING_INTEGRATION.md) +- 文档包含:启动参数、最小验证步骤、常见“不生效”排查清单、链路断点场景说明 + # 项目截图 | ![整体截图](https://images.gitee.com/uploads/images/2021/0711/105714_c1cacf2c_297689.png "整体截图") | ![代码提示](https://images.gitee.com/uploads/images/2021/0711/110448_11b6626b_297689.gif "代码提示") | |---|---| diff --git a/SKYWALKING_INTEGRATION.md b/SKYWALKING_INTEGRATION.md new file mode 100644 index 00000000..22329089 --- /dev/null +++ b/SKYWALKING_INTEGRATION.md @@ -0,0 +1,95 @@ +# magic-api 与 SkyWalking 集成指南 + +本文用于说明在 `magic-api` 场景下如何正确接入 SkyWalking,以及“启动时加了 agent 但看不到链路”时的常见排查步骤。 + +## 1. 适用范围 + +- 通过 `magic-api-spring-boot-starter` 启动的 Spring Boot 应用 +- 使用 SkyWalking Java Agent(`-javaagent`)采集链路 + +## 2. 基础接入步骤 + +### 2.1 准备 SkyWalking Agent + +从 SkyWalking 官方发布包中解压 `skywalking-agent` 目录,确保可访问: + +- `skywalking-agent/skywalking-agent.jar` +- `skywalking-agent/config/agent.config` + +### 2.2 启动参数示例 + +```bash +java \ + -javaagent:/opt/skywalking-agent/skywalking-agent.jar \ + -Dskywalking.agent.service_name=magic-api-demo \ + -Dskywalking.collector.backend_service=127.0.0.1:11800 \ + -jar app.jar +``` + +也可使用环境变量方式配置(与 `agent.config` 一致): + +```bash +export SW_AGENT_NAME=magic-api-demo +export SW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800 +``` + +## 3. 最小验证流程 + +1. 启动应用后,检查启动日志里是否出现 `SkyWalking agent started` +2. 访问一个实际 API(而不是仅访问 `magic-api` 的静态页面) +3. 在 SkyWalking UI 中确认: + - 服务名已出现(如 `magic-api-demo`) + - `trace` 中可见请求链路 + - 请求耗时、错误率指标有数据 + +## 4. 常见“不生效”排查 + +### 4.1 仅访问了静态页面 + +如果只打开了编辑器页面(如 `/magic/web`),通常不会形成你期望的业务调用链。 +请触发真实的业务 API 请求后再观察 trace。 + +### 4.2 Agent 实际未加载 + +检查是否存在以下问题: + +- `-javaagent` 路径错误 +- 启动脚本被覆盖(例如容器入口脚本没有携带 `-javaagent`) +- JDK 版本与 Agent 版本不兼容 + +### 4.3 OAP 地址不可达 + +确认 `skywalking.collector.backend_service` 对当前运行环境可达(网络、防火墙、端口映射均正常)。 + +### 4.4 服务名配置不一致 + +如果应用实例很多,建议显式配置服务名并保持唯一且稳定,避免在 UI 中误判“未上报”。 + +### 4.5 异步线程场景链路断开 + +在跨线程执行(例如线程池异步任务)时,可能出现上下文未自动透传导致链路断点。 +如果你的脚本或扩展逻辑包含异步执行,请在该部分增加上下文透传或手动埋点。 + +## 5. 可选:业务扩展代码中手动埋点 + +若需要对自定义模块/扩展逻辑进行更细粒度追踪,可在业务工程中引入 SkyWalking toolkit 后手动埋点(示例): + +```java +import org.apache.skywalking.apm.toolkit.trace.ActiveSpan; +import org.apache.skywalking.apm.toolkit.trace.Trace; + +public class DemoService { + + @Trace + public void execute(String apiName) { + ActiveSpan.tag("magic.api.name", apiName); + } +} +``` + +> 注意:手动埋点依赖请按你的 SkyWalking 版本选择对应 toolkit 组件版本。 + +## 6. 建议 + +- 先完成“基础接入 + 最小验证”,再逐步扩展到网关、异步调用、外部存储等复杂链路。 +- 遇到“无数据”时优先从“Agent 是否加载成功”和“OAP 是否可达”两项开始排查。 diff --git a/magic-api-plugins/magic-api-plugin-skywalking/pom.xml b/magic-api-plugins/magic-api-plugin-skywalking/pom.xml new file mode 100644 index 00000000..6fb3bf51 --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-skywalking/pom.xml @@ -0,0 +1,45 @@ + + + + magic-api-plugins + org.ssssssss + ${revision} + + 4.0.0 + + magic-api-plugin-skywalking + + + + org.ssssssss + magic-api + ${project.version} + provided + + + org.apache.skywalking + apm-toolkit-trace + 9.2.0 + provided + + + org.springframework + spring-context + provided + + + org.junit.jupiter + junit-jupiter + 5.9.3 + test + + + org.mockito + mockito-junit-jupiter + 5.5.0 + test + + + diff --git a/magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/MagicSkyWalkingConfiguration.java b/magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/MagicSkyWalkingConfiguration.java new file mode 100644 index 00000000..6d1d1cbc --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/MagicSkyWalkingConfiguration.java @@ -0,0 +1,22 @@ +package org.ssssssss.magicapi.skywalking; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * SkyWalking 集成自动配置 + * + * @author magic-api + */ +@Configuration +@ConditionalOnClass(name = "org.apache.skywalking.apm.toolkit.trace.Tracer") +public class MagicSkyWalkingConfiguration { + + @Bean + @ConditionalOnMissingBean + public SkyWalkingInterceptor skyWalkingInterceptor() { + return new SkyWalkingInterceptor(); + } +} diff --git a/magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptor.java b/magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptor.java new file mode 100644 index 00000000..096b731c --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-skywalking/src/main/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptor.java @@ -0,0 +1,104 @@ +package org.ssssssss.magicapi.skywalking; + +import org.apache.skywalking.apm.toolkit.trace.ActiveSpan; +import org.apache.skywalking.apm.toolkit.trace.SpanRef; +import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import org.apache.skywalking.apm.toolkit.trace.Tracer; +import org.ssssssss.magicapi.core.context.RequestEntity; +import org.ssssssss.magicapi.core.interceptor.RequestInterceptor; +import org.ssssssss.magicapi.core.model.ApiInfo; + +/** + * SkyWalking 集成拦截器 + * 在 magic-api 请求执行过程中创建和管理 SkyWalking span + * + * @author magic-api + */ +public class SkyWalkingInterceptor implements RequestInterceptor { + + private static final ThreadLocal SPAN_HOLDER = new ThreadLocal<>(); + private static final String OPERATION_NAME_PREFIX = "magic-api:"; + + @Override + public Object preHandle(RequestEntity requestEntity) throws Exception { + ApiInfo apiInfo = requestEntity.getApiInfo(); + if (apiInfo == null) { + return null; + } + + String operationName = OPERATION_NAME_PREFIX + apiInfo.getPath(); + + // 创建 LocalSpan 用于追踪 magic-api 脚本执行 + SpanRef span = Tracer.createLocalSpan(operationName); + + // 设置标签 + span.tag("api.id", apiInfo.getId()); + span.tag("api.name", apiInfo.getName()); + span.tag("api.path", apiInfo.getPath()); + span.tag("api.method", requestEntity.getRequest().getMethod()); + + // 将 span 存储到 ThreadLocal 中 + SPAN_HOLDER.set(span); + + return null; + } + + @Override + public Object postHandle(RequestEntity requestEntity, Object value) throws Exception { + // 后置处理,可以在此记录返回值信息 + SpanRef span = SPAN_HOLDER.get(); + if (span != null && value != null) { + span.tag("result.type", value.getClass().getSimpleName()); + } + return null; + } + + @Override + public void afterCompletion(RequestEntity requestEntity, Object returnValue, Throwable throwable) { + SpanRef span = SPAN_HOLDER.get(); + if (span == null) { + return; + } + + try { + // 如果有异常,标记为错误 + if (throwable != null) { + ActiveSpan.error(throwable); + span.log(throwable); + } + } finally { + // 清理 ThreadLocal + SPAN_HOLDER.remove(); + // 结束 span + Tracer.stopSpan(); + } + } + + /** + * 获取当前 traceId + */ + public static String getTraceId() { + return TraceContext.traceId(); + } + + /** + * 获取当前 segmentId + */ + public static String getSegmentId() { + return TraceContext.segmentId(); + } + + /** + * 获取当前 spanId + */ + public static int getSpanId() { + return TraceContext.spanId(); + } + + /** + * 添加自定义标签到当前 span + */ + public static void tag(String key, String value) { + ActiveSpan.tag(key, value); + } +} diff --git a/magic-api-plugins/magic-api-plugin-skywalking/src/main/resources/META-INF/spring.factories b/magic-api-plugins/magic-api-plugin-skywalking/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..04c946bb --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-skywalking/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.ssssssss.magicapi.skywalking.MagicSkyWalkingConfiguration diff --git a/magic-api-plugins/magic-api-plugin-skywalking/src/test/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptorTest.java b/magic-api-plugins/magic-api-plugin-skywalking/src/test/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptorTest.java new file mode 100644 index 00000000..7db7eefa --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-skywalking/src/test/java/org/ssssssss/magicapi/skywalking/SkyWalkingInterceptorTest.java @@ -0,0 +1,73 @@ +package org.ssssssss.magicapi.skywalking; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.ssssssss.magicapi.core.context.RequestEntity; +import org.ssssssss.magicapi.core.model.ApiInfo; +import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * SkyWalking 拦截器测试 + */ +@ExtendWith(MockitoExtension.class) +class SkyWalkingInterceptorTest { + + private SkyWalkingInterceptor interceptor; + + @Mock + private RequestEntity requestEntity; + + @Mock + private ApiInfo apiInfo; + + @Mock + private MagicHttpServletRequest request; + + @BeforeEach + void setUp() { + interceptor = new SkyWalkingInterceptor(); + } + + @Test + void testPreHandleWithNullApiInfo() throws Exception { + when(requestEntity.getApiInfo()).thenReturn(null); + + Object result = interceptor.preHandle(requestEntity); + + assertNull(result); + } + + @Test + void testPreHandleWithApiInfo() throws Exception { + when(apiInfo.getId()).thenReturn("test-api-id"); + when(apiInfo.getName()).thenReturn("test-api"); + when(apiInfo.getPath()).thenReturn("/test/path"); + when(requestEntity.getApiInfo()).thenReturn(apiInfo); + when(requestEntity.getRequest()).thenReturn(request); + when(request.getMethod()).thenReturn("GET"); + + // 注意:这个测试在没有 SkyWalking agent 的情况下会抛出异常或返回 null + // 实际测试需要在有 SkyWalking agent 的环境中运行 + try { + Object result = interceptor.preHandle(requestEntity); + assertNull(result); + } catch (Exception e) { + // 预期在没有 SkyWalking agent 时可能会有异常 + assertTrue(e.getMessage() != null); + } + } + + @Test + void testAfterCompletion() { + // 测试清理逻辑不会抛出异常 + assertDoesNotThrow(() -> { + interceptor.afterCompletion(requestEntity, null, null); + }); + } +} diff --git a/magic-api-plugins/pom.xml b/magic-api-plugins/pom.xml index 8ea1dd66..36535c83 100644 --- a/magic-api-plugins/pom.xml +++ b/magic-api-plugins/pom.xml @@ -24,6 +24,7 @@ magic-api-plugin-cluster magic-api-plugin-git magic-api-plugin-nebula + magic-api-plugin-skywalking