Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<a target="_blank" href="LICENSE"><img src="https://img.shields.io/:license-MIT-blue.svg"></a>
</p>

[特性](#特性) | [快速开始](#快速开始) | [文档/演示](#文档演示) | [示例项目](#示例项目) | <a target="_blank" href="http://ssssssss.org/changelog.html">更新日志</a> | [项目截图](#项目截图) | [交流群](#交流群)
[特性](#特性) | [快速开始](#快速开始) | [文档/演示](#文档演示) | [示例项目](#示例项目) | [SkyWalking集成](#skywalking集成) | <a target="_blank" href="http://ssssssss.org/changelog.html">更新日志</a> | [项目截图](#项目截图) | [交流群](#交流群)

# 简介

Expand Down Expand Up @@ -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 "代码提示") |
|---|---|
Expand Down
95 changes: 95 additions & 0 deletions SKYWALKING_INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -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 是否可达”两项开始排查。
45 changes: 45 additions & 0 deletions magic-api-plugins/magic-api-plugin-skywalking/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?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">
<parent>
<artifactId>magic-api-plugins</artifactId>
<groupId>org.ssssssss</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>magic-api-plugin-skywalking</artifactId>

<dependencies>
<dependency>
<groupId>org.ssssssss</groupId>
<artifactId>magic-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>9.2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<SpanRef> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.ssssssss.magicapi.skywalking.MagicSkyWalkingConfiguration
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
1 change: 1 addition & 0 deletions magic-api-plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<module>magic-api-plugin-cluster</module>
<module>magic-api-plugin-git</module>
<module>magic-api-plugin-nebula</module>
<module>magic-api-plugin-skywalking</module>
</modules>
<dependencies>
<dependency>
Expand Down