第一章:Go实现截图:跨平台能力全景概览
Go 语言凭借其静态编译、无依赖运行时和统一的系统调用抽象层,天然具备出色的跨平台能力。在截图功能实现中,这种特性尤为关键——无需为 Windows、macOS 和 Linux 分别维护三套 GUI 框架绑定代码,而是通过适配各平台原生 API 的轻量封装,达成一致接口与差异化实现的平衡。
主流 Go 截图方案依赖以下底层机制:
- Windows:调用
gdi32.dll的BitBlt+GetDC获取桌面设备上下文 - macOS:使用
CoreGraphics框架的CGDisplayCreateImageForRect捕获指定区域 - Linux:通过 X11 的
XGetImage或现代 Wayland 协议(需xdg-desktop-portal支持)
一个典型的跨平台截图函数签名如下:
// CaptureScreen captures the entire primary display as PNG bytes
func CaptureScreen() ([]byte, error) {
// 自动检测当前 OS 并路由至对应实现
switch runtime.GOOS {
case "windows":
return captureWindows()
case "darwin":
return captureMacOS()
case "linux":
return captureLinux()
default:
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
}
该函数返回 []byte 而非文件路径,便于进一步处理(如网络传输、内存压缩或嵌入 Web 服务响应)。实际项目中推荐使用成熟库 github.com/mitchellh/gox 进行多平台构建验证,并配合 GitHub Actions 的 ubuntu-latest / macos-latest / windows-latest 矩阵测试确保 ABI 兼容性。
| 平台 | 推荐依赖库 | 是否支持多显示器 | 最小 Go 版本 |
|---|---|---|---|
| Windows | golang.org/x/sys/windows |
✅ | 1.16 |
| macOS | github.com/jeffwilk/CGO 封装 |
✅ | 1.18 |
| Linux (X11) | github.com/BurntSushi/xgb |
✅ | 1.17 |
值得注意的是,Wayland 环境下直接截图受限于安全策略,必须通过 org.freedesktop.portal.Screenshot D-Bus 接口委托桌面环境授权执行,这要求调用方集成 Portal 客户端逻辑而非绕过沙箱。
第二章:核心截图原理与底层API适配实践
2.1 Windows GDI/Windows Graphics Capture API双路径实现与版本兼容策略
为兼顾 Win10 1803+ 新旧系统,采用运行时特征检测的双路径架构:
路径选择逻辑
- 优先尝试
GraphicsCaptureSession(Win10 1803+) - 失败则回退至
BitBlt+GetDC(HWND_DESKTOP)(兼容 Win7–Win10 1709)
// 检测 Graphics Capture 是否可用
auto factory = winrt::get_activation_factory<winrt::Windows::Graphics::Capture::GraphicsCaptureSession>();
bool useNewAPI = SUCCEEDED(factory.try_as<winrt::Windows::Foundation::IInspectable>());
逻辑分析:
try_as避免异常开销;若返回S_OK说明系统支持 UWP 图形捕获。参数IInspectable是 ABI 兼容性探针,不触发实际初始化。
版本兼容对照表
| Windows 版本 | GDI 支持 | Graphics Capture | 推荐路径 |
|---|---|---|---|
| Win7 | ✅ | ❌ | GDI |
| Win10 1709 | ✅ | ❌ | GDI |
| Win10 1803+ | ✅ | ✅ | Graphics Capture |
graph TD
A[启动捕获] --> B{IsGraphicsCaptureAvailable?}
B -->|Yes| C[创建GraphicsCaptureSession]
B -->|No| D[启用GDI BitBlt循环]
2.2 macOS Quartz Display Services与ScreenCaptureKit的渐进式降级机制设计
当 ScreenCaptureKit(macOS 13+)不可用时,系统需无缝回退至 Quartz Display Services(QDS),形成运行时能力探测 → 优先启用新框架 → 降级执行的闭环。
能力探测与初始化策略
func setupCaptureSession() -> ScreenCaptureSession? {
// 检查运行时系统版本与权限
guard #available(macOS 13.0, *) else { return nil }
guard SCShareableContent.isAvailable else { return nil }
return try? ScreenCaptureSession()
}
该逻辑在启动时执行:#available 确保 API 可用性,isAvailable 检测用户是否授权屏幕录制。失败则跳过 SC 初始化,进入 QDS 分支。
降级路径决策表
| 条件 | 主路径 | 备选路径 | 延迟(ms) |
|---|---|---|---|
| macOS ≥13 & 权限 granted | ScreenCaptureKit | — | 0 |
| macOS ≥13 & 权限 denied | QDS + TCC prompt fallback | — | ~800 |
| macOS | QDS only | — | 0 |
流程控制
graph TD
A[启动捕获] --> B{macOS ≥13?}
B -->|是| C{SCShareableContent.isAvailable?}
B -->|否| D[使用QDS CGDisplayStream]
C -->|是| E[创建SCSession]
C -->|否| D
2.3 Linux X11/XCB与Wayland DMA-BUF截图协议的运行时自动探测与切换
现代Linux截图工具需在X11(via XCB)与Wayland(via wlr-screencopy/xdg-desktop-portal)间无缝切换,核心依赖运行时显示协议探测与DMA-BUF共享能力协商。
协议探测优先级策略
- 首查
WAYLAND_DISPLAY环境变量非空且wl_display_connect()成功 - 回退至
DISPLAY存在且xcb_connect()可用 - 最终验证
DMA_BUF是否被服务端支持(通过zwp_linux_dmabuf_v1或XCB_RENDER_QUERY_VERSION+XCB_RENDER_PICT_TYPE_DIRECT)
DMA-BUF 共享能力检测示例
// 检测 Wayland 是否支持 dmabuf screencopy
struct zwlr_screencopy_manager_v1 *mgr =
zxdg_output_manager_v1_get_screencopy_manager(output_mgr);
// 若 mgr == NULL → 回退至 X11 或 fallback SHM
该调用触发 Wayland 合成器能力协商;失败则表明合成器不支持零拷贝DMA-BUF截图,需启用wl_shm回退路径。
| 协议 | DMA-BUF 支持 | 零拷贝 | 延迟典型值 |
|---|---|---|---|
| Wayland + dmabuf | ✅ | 是 | |
| X11 + XShm | ❌ | 否 | ~15ms |
graph TD
A[启动截图] --> B{env: WAYLAND_DISPLAY?}
B -->|yes| C[连接 wl_display]
B -->|no| D[连接 xcb_connection_t]
C --> E{zwp_linux_dmabuf_v1 available?}
E -->|yes| F[DMA-BUF screencopy]
E -->|no| G[wl_shm fallback]
2.4 跨平台像素格式统一:BGRA→RGBA→YUV转换链与内存布局对齐实践
像素通道重排的底层动因
不同平台默认采用差异化的原生像素布局:Windows GDI/GPU 偏好 BGRA,macOS Metal 默认 RGBA,而视频编解码器(如AV1、H.264)普遍要求 YUV420p。跨平台渲染管线必须在零拷贝前提下完成语义一致的通道映射。
内存对齐关键约束
- 所有行首地址需 16 字节对齐(满足 AVX2/SSE 加速)
- YUV 平面间 stride 必须为 32 的整数倍(规避 ARM NEON 边界陷阱)
核心转换流程
// BGRA8888 → RGBA8888(字节级通道置换,in-place)
for (int i = 0; i < size; i += 4) {
uint8_t b = src[i + 0];
src[i + 0] = src[i + 2]; // R ← B
src[i + 2] = b; // B ← R
}
逻辑说明:
src[i+0]为B分量,src[i+2]为R分量;仅交换第0/2位实现BGRA→RGBA。该操作无内存分配,延迟
YUV420p 布局规范
| 平面 | 宽度占比 | 高度占比 | 对齐要求 |
|---|---|---|---|
| Y | 100% | 100% | 32-byte |
| U | 50% | 50% | 32-byte |
| V | 50% | 50% | 32-byte |
graph TD
A[GPU Texture BGRA] -->|CPU memcpy + shuffle| B[RGBA Buffer]
B -->|SIMD-optimized RGB2YUV| C[Y Plane]
B -->|Subsampled U/V| D[U Plane]
B -->|Subsampled U/V| E[V Plane]
2.5 截图性能瓶颈分析:帧率控制、内存拷贝优化与零拷贝共享缓冲区实测
截图性能瓶颈常集中于三处:高频采集导致的 CPU 调度压力、memcpy 引发的带宽争抢,以及用户态/内核态间重复数据搬运。
帧率自适应控制逻辑
通过滑动窗口动态调节采样间隔,避免硬编码固定 FPS:
// 基于上一周期实际耗时(us)动态调整下一帧延迟
int64_t actual_us = get_elapsed_us(&start, &end);
int64_t target_us = 1000000 / target_fps;
int64_t delay_us = MAX(0, target_us - actual_us); // 防负值
usleep(delay_us);
target_us 决定理论帧间隔;actual_us 反馈真实处理开销;delay_us 补偿抖动,保障平均帧率稳定。
内存拷贝开销对比(1080p RGBA)
| 方式 | 带宽占用 | 平均延迟 | 备注 |
|---|---|---|---|
memcpy() |
3.2 GB/s | 4.7 ms | 用户态全量拷贝 |
memmove() |
3.1 GB/s | 4.5 ms | 重叠安全但无实质优化 |
mmap + DMA |
0.4 GB/s | 0.9 ms | 零拷贝共享缓冲区 |
共享缓冲区同步流程
graph TD
A[GPU 输出帧] --> B[DRM PRIME FD]
B --> C[用户态 mmap 映射]
C --> D[直接读取 vaddr]
D --> E[AVFrame 指针复用]
第三章:Go语言跨平台截图SDK架构设计
3.1 接口抽象层(Screenshoter)与平台特化驱动注册机制
Screenshoter 是一个面向多端截屏能力的统一接口抽象,屏蔽 Windows/macOS/Linux/iOS/Android 底层差异。
核心契约定义
class Screenshoter(ABC):
@abstractmethod
def capture_region(self, x: int, y: int, w: int, h: int) -> bytes:
"""返回PNG格式字节流;坐标系原点为屏幕左上角"""
@abstractmethod
def list_monitors(self) -> List[Dict[str, Any]]:
"""返回[{id: 0, name: "Built-in", dpi: 2, scale: 2.0}]"""
capture_region参数语义:x/y为逻辑像素坐标(经缩放因子归一化),w/h为设备无关像素(DIP),驱动需自动转换为物理像素并调用原生 API。
驱动注册机制
| 平台 | 驱动类名 | 自动发现方式 |
|---|---|---|
| Windows | WinGDIScreenshoter | sys.platform == "win32" |
| macOS | AVFoundationScreenshoter | sys.platform == "darwin" |
| Linux (X11) | X11Screenshoter | os.getenv("DISPLAY") |
graph TD
A[App 调用 Screenshoter.capture_region] --> B{Runtime 检测平台}
B -->|Windows| C[WinGDIScreenshoter]
B -->|macOS| D[AVFoundationScreenshoter]
B -->|Linux| E[X11Screenshoter]
3.2 并发安全的截图会话管理与生命周期控制(Start/Stop/Cancel)
截图会话需在多线程/协程并发调用下保持状态一致,避免 Start 与 Cancel 竞态导致资源泄漏或重复释放。
数据同步机制
采用 sync.RWMutex 保护会话核心字段,读多写少场景下兼顾性能与安全性:
type ScreenshotSession struct {
mu sync.RWMutex
state SessionState // pending/running/stopped/canceled
cancelFn context.CancelFunc
deadline time.Time
}
mu保证state和cancelFn的原子读写;cancelFn非空即已调用context.WithTimeout初始化,避免 nil-call panic;deadline用于超时自动Stop,无需额外定时器。
生命周期状态迁移
| 当前状态 | 允许操作 | 结果状态 |
|---|---|---|
| pending | Start | running |
| running | Stop / Cancel | stopped / canceled |
| stopped | — | 不变 |
graph TD
A[pending] -->|Start| B[running]
B -->|Stop| C[stopped]
B -->|Cancel| D[canceled]
C & D -->|Reset| A
安全终止保障
Stop() 和 Cancel() 均执行双重检查 + CAS 风格状态跃迁,确保幂等性。
3.3 错误分类体系:OS权限拒绝、显示器热插拔、GPU上下文丢失等异常建模
现代图形应用需精准区分三类底层异常,以触发差异化恢复策略:
- OS权限拒绝:
EACCES或EPERM导致的 DRM/KMS 设备节点访问失败 - 显示器热插拔:
DRM_EVENT_HOTPLUG事件伴随modeset重配置需求 - GPU上下文丢失:
VK_ERROR_DEVICE_LOST或 OpenGLGL_CONTEXT_LOST信号
异常检测与映射表
| 原始错误码 | 分类标签 | 恢复动作 |
|---|---|---|
VK_ERROR_DEVICE_LOST |
GPU上下文丢失 | 重建VkDevice + 重载资源 |
DRM_MODE_OBJECT_CONNECTOR + status=DISCONNECTED |
显示器热插拔 | 触发drmModeGetResources重枚举 |
// Vulkan上下文丢失检测(同步检查)
VkResult result = vkQueueSubmit(queue, 1, &submitInfo, fence);
if (result == VK_ERROR_DEVICE_LOST) {
// ⚠️ 不可调用vkDeviceWaitIdle——设备已不可用
vkDestroyDevice(device, nullptr); // 安全释放句柄
recreatePhysicalDeviceAndDevice(); // 启动完整重建流程
}
该代码块中,vkQueueSubmit 返回 VK_ERROR_DEVICE_LOST 表明GPU驱动已终止设备上下文,此时任何vk*调用(除vkDestroy*)均未定义行为;recreatePhysicalDeviceAndDevice()需重新执行物理设备枚举、队列族选择及逻辑设备创建。
graph TD
A[捕获原始错误] --> B{错误来源}
B -->|DRM ioctl| C[热插拔事件]
B -->|Vulkan/OpenGL API| D[GPU上下文丢失]
B -->|open/mmap系统调用| E[OS权限拒绝]
C --> F[增量重配置]
D --> G[全量重建]
E --> H[权限引导或降级渲染]
第四章:全平台实测验证与工程化落地
4.1 Windows全版本矩阵测试:Win7(GDI)至Win11(Graphics Capture)兼容性验证报告
为覆盖企业存量环境与新兴平台,我们构建了跨代图形捕获能力验证矩阵:
| OS Version | Primary API | Desktop Composition | UAC Bypass Required | DPI-Aware |
|---|---|---|---|---|
| Windows 7 | BitBlt (GDI) |
Disabled | No | Partial |
| Windows 10 | Desktop Duplication API |
Enabled (DWM) | Yes (admin token) | Full |
| Windows 11 | Graphics Capture API |
Enabled (DWM + VAIL) | No (brokered) | Full |
核心适配逻辑演进
// Win10+ Desktop Duplication 初始化片段(简化)
auto hr = CreateDesktopDuplication(&dupl); // 参数隐含D3D11 device、output idx
// ⚠️ 注意:Win7不支持此API,需fallback至GDI BitBlt + GetDC(GetDesktopWindow())
该调用依赖D3D11设备及显卡驱动支持;失败时自动降级至GDI路径,但牺牲性能与多显示器一致性。
捕获策略决策流
graph TD
A[OS Version Detection] -->|Win7| B[GDI BitBlt + Screen DC]
A -->|Win8.1–10| C[Desktop Duplication]
A -->|Win11 22H2+| D[Graphics Capture API]
B --> E[Low CPU, High Latency]
C --> F[Medium CPU, Multi-Monitor Sync]
D --> G[Low CPU, HDR/Variable Refresh Aware]
4.2 macOS全版本覆盖:10.15(Quartz)到14(ScreenCaptureKit)的API可用性与沙盒适配实录
核心API演进路径
CGDisplayStream(10.15+):需com.apple.security.device.screen权限,不支持沙盒内直接捕获主屏AVCaptureScreenInput(11.0+):依赖AVCaptureSession,需com.apple.security.device.microphone(误配常致静默失败)ScreenCaptureKit(14.0+):强制沙盒兼容,需com.apple.security.screencapture+ 运行时用户授权
权限与运行时检查(Swift)
func checkScreenCaptureAvailability() -> Bool {
#if os(macOS) && !targetEnvironment(simulator)
return #available(macOS 14.0, *) // 编译期守卫
#else
return false
#endif
}
该函数利用 Swift 的 #available 编译期检查,避免在旧系统调用未定义符号;targetEnvironment(simulator) 排除模拟器环境——ScreenCaptureKit 在模拟器中不可用且无降级路径。
API可用性对照表
| macOS 版本 | Quartz/CV | AVFoundation | ScreenCaptureKit | 沙盒兼容 |
|---|---|---|---|---|
| 10.15 | ✅ | ❌ | ❌ | ⚠️(需硬编码权限) |
| 13.0 | ✅ | ✅ | ❌ | ✅(需microphone权限) |
| 14.0+ | ✅ | ✅ | ✅ | ✅(原生支持) |
graph TD
A[macOS 10.15] -->|CGDisplayStream| B[需TCC授权+硬编码权限]
A -->|无SCSK| C[无法获取窗口层级元数据]
D[macOS 14.0+] -->|ScreenCaptureKit| E[自动处理窗口/应用/屏幕粒度]
E --> F[返回SCSWindowDescriptor等结构体]
4.3 Ubuntu多桌面环境实测:18.04(X11)至24.04(Wayland默认)的显示协议识别与fallback验证
显示协议自动识别脚本
# 检测当前会话协议类型及fallback状态
echo "Session Type: $(loginctl show-session $(loginctl | grep -m1 'session' | awk '{print $1}') -p Type | cut -d= -f2)"
echo "XDG_SESSION_TYPE: $XDG_SESSION_TYPE"
echo "WAYLAND_DISPLAY: ${WAYLAND_DISPLAY:-"not set"}"
echo "DISPLAY: ${DISPLAY:-"not set"}"
该脚本通过 loginctl 查询底层会话类型,并结合环境变量交叉验证。Type 字段直接反映 systemd-logind 记录的会话协议(wayland/x11),而 XDG_SESSION_TYPE 是桌面环境读取的权威标识;WAYLAND_DISPLAY 和 DISPLAY 的存在性辅助判断是否进入 fallback 流程。
Ubuntu 版本协议演进对比
| Ubuntu 版本 | 默认显示服务器 | Fallback 触发条件(典型) | GNOME Session 类型 |
|---|---|---|---|
| 18.04 | X11 | 无(X11 为原生) | x11 |
| 22.04 | Wayland | NVIDIA 闭源驱动 / HiDPI 多屏异常 | wayland → x11 |
| 24.04 | Wayland | 启用 systemd early-boot DRM 权限失败 |
wayland (forced) |
Fallback 自动降级流程
graph TD
A[GNOME Session 启动] --> B{检测 WAYLAND_DISPLAY & GPU 支持}
B -->|成功| C[启动 mutter-wayland]
B -->|失败| D[设置 XDG_SESSION_TYPE=x11]
D --> E[启动 mutter-x11]
4.4 多显示器/HiDPI/缩放因子/旋转屏幕等边缘场景截图精度与坐标系校准实践
在高分屏与多屏混布环境中,系统级坐标(逻辑像素)与设备像素常存在非整数缩放映射,导致 screenshot() 坐标偏移。
坐标系对齐关键步骤
- 查询每个显示器的
scaleFactor和rotation(0°/90°/180°/270°) - 将用户指定的逻辑坐标逆向映射至目标屏的物理像素空间
- 对旋转屏幕需先应用坐标旋转变换,再按缩放因子归一化
缩放感知截图示例(Python + mss)
import mss
from screeninfo import get_monitors
mon = get_monitors()[1] # 第二块屏
scale = mon.scale # 如 2.0(Retina)
rot = mon.rotation # 90 → 需交换宽高并偏移
# 物理区域:逻辑坐标 (100,100,300,200) → 物理像素
left, top = int(100 * scale), int(100 * scale)
width, height = int(200 * scale), int(100 * scale)
with mss.mss() as sct:
monitor = {"top": top, "left": left, "width": width, "height": height, "mon": 2}
img = sct.grab(monitor) # ✅ 精确捕获物理帧缓冲
此处
mon: 2指定物理屏索引;scale直接作用于坐标与尺寸,避免 macOS/Windows DPI混合缩放下的亚像素截断误差。
多屏坐标映射关系表
| 屏幕 | 逻辑分辨率 | 物理分辨率 | 缩放因子 | 旋转 | 坐标变换要点 |
|---|---|---|---|---|---|
| 内置 Retina | 1440×900 | 2880×1800 | 2.0 | 0° | 坐标 ×2,无旋转 |
| 外接 4K | 3840×2160 | 3840×2160 | 1.0 | 90° | 交换x/y,y→(height−x) |
graph TD
A[用户输入逻辑坐标] --> B{获取当前屏元数据}
B --> C[应用缩放因子缩放]
B --> D[应用旋转矩阵变换]
C --> E[映射至物理帧缓冲地址]
D --> E
E --> F[调用底层grab API]
第五章:开源项目地址与未来演进方向
项目核心仓库与镜像源
本项目的主代码库托管于 GitHub,地址为:https://github.com/aiops-observability/traceflow-core。该仓库采用 Apache 2.0 协议,包含完整的 Go 语言实现、Kubernetes Operator 控制器、CLI 工具 tfctl 及 OpenTelemetry Collector 扩展插件。国内用户可通过清华 TUNA 镜像站同步获取:https://mirrors.tuna.tsinghua.edu.cn/github-release/aiops-observability/traceflow-core/。截至 v1.8.3 版本,仓库已累计接收来自 47 个国家的 219 名贡献者提交的 PR,其中 32% 的补丁直接源自生产环境故障复盘(如某金融客户在高并发链路采样率突降场景中提交的动态采样策略修复)。
生产就绪型部署套件
项目提供 Helm Chart 官方包(Chart 版本 3.4.0),支持一键部署至混合云环境:
| 组件 | 部署方式 | 典型配置示例 |
|---|---|---|
| 分布式追踪后端 | helm install traceflow oci://ghcr.io/aiops-observability/charts/traceflow --set persistence.storageClass=ceph-rbd |
启用 Ceph RBD 持久化存储 |
| 边缘节点探针 | kubectl apply -k github.com/aiops-observability/traceflow-core//deploy/overlays/edge?ref=v1.8.3 |
使用 Kustomize 启用 eBPF 内核级采集 |
| 多集群联邦网关 | docker run -d --network host -v /etc/traceflow:/etc/traceflow ghcr.io/aiops-observability/federator:v1.8.3 |
容器化部署联邦聚合节点 |
社区驱动的演进路线图
根据 2024 年 Q2 社区投票(共 1,248 份有效票),以下特性已纳入 v2.0 正式开发周期:
- 原生支持 W3C Trace Context v2 规范(RFC 9445),兼容 Service Mesh 中 Envoy 1.29+ 的新传播头字段;
- 构建基于 WASM 的可编程过滤器沙箱,允许用户通过 Rust 编写自定义采样逻辑并热加载(示例代码见下);
- 实现与 Prometheus Remote Write 协议的双向桥接,使 trace 指标(如
trace_duration_seconds_bucket)可直写 VictoriaMetrics。
// 示例:WASM 过滤器中的动态采样逻辑(src/filters/sampling.rs)
#[no_mangle]
pub extern "C" fn should_sample(trace_id: *const u8, span_name: *const u8) -> u8 {
let name = unsafe { std::ffi::CStr::from_ptr(span_name).to_str().unwrap() };
if name.starts_with("payment.") && get_env_var("PAYMENT_SAMPLING_RATE").parse::<f64>().unwrap_or(0.1) > 0.05 {
1 // 采样
} else {
0 // 丢弃
}
}
跨生态集成验证案例
在某头部电商的双十一大促保障中,团队将 traceflow-core 与 CNCF 项目 Thanos 结合,构建了「全链路指标-日志-追踪」联合分析流水线:
- 使用
traceflow-exporter将 span 数据按 service_name 分片推送至对象存储(S3 兼容接口); - Thanos Query 层通过
traces_to_metrics插件实时生成 P99 延迟热力图; - 当检测到
/checkout服务延迟突增时,自动触发tfctl search --span-name checkout.process --start 2h --min-duration 2s检索关联异常链路。该方案在 2023 年双十一期间成功定位 17 起跨 AZ 网络抖动引发的分布式事务超时问题。
安全与合规增强计划
v2.0 将引入 FIPS 140-3 认证的加密模块,所有 trace 数据在内存中全程 AES-256-GCM 加密;审计日志增加 OpenZiti 零信任网络访问控制记录,每条 trace 存储操作均附带 SPIFFE ID 签名。Mermaid 流程图展示敏感数据脱敏流程:
flowchart LR
A[原始 Span] --> B{是否含 PCI 字段?}
B -->|是| C[调用 PCI-Safe SDK 执行掩码]
B -->|否| D[直通存储]
C --> E[生成 masked_span_id]
E --> F[写入 ClickHouse 表 trace_spans_masked] 