Skip to content

在线日志与追踪

概述

Snail AI 客户端模式提供了完整的生产级可观测性体系,涵盖日志记录、分布式追踪和上下文传播三大维度。这套体系的核心优势在于:追踪数据在客户端本地生成和收集,开发者对 AI 交互的每个环节都拥有完全的可见性——这是自主可控理念在可观测性领域的具体体现。

与 SaaS 平台提供的基础日志不同,Snail AI 的追踪系统基于 Micrometer ObservationRegistry 构建,原生支持 GENERATION(LLM 调用)和 TOOL_CALLING(工具调用)两种 Observation 类型,并通过 TracingToolCallbackWrapperAgentChatContextHolder 实现了跨线程、跨组件的 Trace Context 传播。

LoggingInterceptor 内置日志

LoggingInterceptor 是 Snail AI 提供的开箱即用的日志拦截器,可通过一行配置开启:

yaml
snail-ai:
  agent:
    logging-interceptor: true

开启后,每次 LLM 调用都会自动记录以下信息:

日志阶段记录内容说明
请求前消息数量(messages count)包含系统提示词、历史消息和当前用户消息的总数
响应后完成原因(finishReason)STOPLENGTHTOOL_CALLS

日志输出示例:

[SnailAI] Request messages count: 5
[SnailAI] Response finishReason: STOP

何时使用

LoggingInterceptor 适合开发调试阶段的快速诊断。对于生产环境的全链路追踪需求,建议结合 Micrometer Observation 体系使用。

Micrometer ObservationRegistry 集成

Snail AI 客户端深度集成了 Micrometer 的 ObservationRegistry,为 AI 交互的关键环节创建标准化的 Observation:

Observation 类型

类型名称说明
GENERATIONgen_ai.chat.completionsLLM 调用的完整生命周期追踪
TOOL_CALLINGgen_ai.tool.call工具调用的执行追踪

GENERATION Observation

每次 LLM 调用会创建一个 GENERATION 类型的 Observation,记录:

  • 请求维度:模型名称、provider、prompt tokens 数量、消息列表
  • 响应维度:completion tokens、total tokens、finish reason、响应时长
  • 上下文维度:traceId、spanId、关联的 agentId、conversationId

TOOL_CALLING Observation

每次工具调用会创建一个 TOOL_CALLING 类型的 Observation,记录:

  • 调用维度:工具名称、输入参数、调用来源
  • 执行维度:执行时长、输出结果、是否成功
  • 关联维度:parentToolObservationId、所属的 GENERATION Observation

TracingToolCallbackWrapper

TracingToolCallbackWrapper 是 Snail AI 实现工具调用追踪的核心组件。它以装饰器模式包裹每一个工具的 ToolCallback,在工具执行前后自动注入追踪上下文:

工作原理

java
// TracingToolCallbackWrapper 包裹逻辑(简化示意)
public class TracingToolCallbackWrapper implements ToolCallback {

    private final ToolCallback delegate;        // 原始工具
    private final ObservationRegistry registry; // Observation 注册表

    @Override
    public String call(String toolInput) {
        // 1. 创建 TOOL_CALLING Observation
        Observation observation = Observation.createNotStarted(
            "gen_ai.tool.call", registry);

        // 2. 注入追踪上下文
        ChatContext ctx = AgentChatContextHolder.get();
        observation.lowCardinalityKeyValue("traceId", ctx.getTraceId());
        observation.lowCardinalityKeyValue("toolName", delegate.getName());
        observation.highCardinalityKeyValue("parentObservationId",
            ctx.getCurrentToolObservationId());

        // 3. 开始观测
        observation.start();

        try {
            // 4. 执行原始工具
            String result = delegate.call(toolInput);

            // 5. 记录结果
            observation.highCardinalityKeyValue("result", result);
            return result;
        } catch (Exception e) {
            observation.error(e);
            throw e;
        } finally {
            // 6. 关闭观测
            observation.stop();
        }
    }
}

所有工具(内置的 ShellTool、HttpTool、ReadSkillTool,以及通过 MCP 和 Skill 动态注册的工具)都会自动被 TracingToolCallbackWrapper 包裹,无需额外配置。

AgentChatContextHolder 追踪上下文

AgentChatContextHolder 是 Snail AI 追踪体系的上下文载体,通过 ThreadLocal 在整个请求链路中传播追踪信息。

ChatContext 数据结构

java
public class ChatContext {
    /** 全局追踪 ID,贯穿整个对话请求的生命周期 */
    private String traceId;

    /** 根 Span ID,标识本次请求的根节点 */
    private String rootSpanId;

    /** Agent 执行的 Span ID */
    private String agentExecutionSpanId;

    /** 当前 GENERATION Observation 的 ID */
    private String currentGenerationId;

    /** 当前工具调用 Observation 的 ID(嵌套工具调用时会更新) */
    private String currentToolObservationId;
}

上下文关系图

Reactor 线程间传播

在流式处理场景中,请求可能跨越多个 Reactor 线程。AgentChatContextThreadLocalAccessor 确保追踪上下文在线程切换时不会丢失:

java
public class AgentChatContextThreadLocalAccessor implements ThreadLocalAccessor<ChatContext> {

    public static final String KEY = "snailai.chatcontext";

    @Override
    public Object key() {
        return KEY;
    }

    @Override
    public ChatContext getValue() {
        return AgentChatContextHolder.get();
    }

    @Override
    public void setValue(ChatContext value) {
        AgentChatContextHolder.set(value);
    }

    @Override
    public void reset() {
        AgentChatContextHolder.clear();
    }
}

这个 Accessor 通过 Reactor 的 Context 机制注册,确保在以下场景中追踪上下文都能正确传播:

  • Flux / Mono 的操作符切换线程(如 publishOnsubscribeOn
  • gRPC 流回调切换到不同线程
  • 工具调用的异步执行

追踪数据结构

一次完整的 Agent 对话追踪形成如下树形结构:

管理后台实时日志查看

追踪数据会通过 gRPC 实时上报到 Server,管理后台提供可视化的日志和追踪查看界面:

日志视图功能

功能说明
实时日志流实时展示客户端节点的日志输出,支持按级别过滤
结构化查询按 traceId、agentId、时间范围筛选
日志级别过滤INFO / WARN / ERROR 分级展示
关键词搜索全文检索日志内容

追踪瀑布图

瀑布图以时间线方式可视化每个 Observation 的耗时分布,帮助开发者快速定位性能瓶颈:

图表元素含义
横轴时间轴(毫秒)
每行一个 Observation(GENERATION 或 TOOL_CALLING)
条形长度该 Observation 的执行耗时
嵌套关系缩进表示父子关系(如 TOOL_CALLING 嵌套在 GENERATION 下)
颜色编码不同颜色区分 Observation 类型和状态

最佳实践

1. 开发环境:开启 LoggingInterceptor

yaml
snail-ai:
  agent:
    logging-interceptor: true

快速查看每次 LLM 调用的基本信息,适合开发调试。

2. 生产环境:利用 Observation 体系

生产环境中建议结合 Micrometer 的导出器(如 Prometheus、Zipkin)将追踪数据输出到外部监控系统:

yaml
management:
  tracing:
    enabled: true
    sampling:
      probability: 1.0  # 生产环境可调低采样率

3. 自定义追踪扩展

通过实现自定义 ObservationHandler 可以扩展追踪行为:

java
@Component
public class CustomObservationHandler implements ObservationHandler<Observation.Context> {

    @Override
    public boolean supportsContext(Observation.Context context) {
        // 只处理 AI 相关的 Observation
        return context.getName().startsWith("gen_ai.");
    }

    @Override
    public void onStop(Observation.Context context) {
        // 将追踪数据发送到自定义监控系统
        customMonitor.report(context);
    }
}

性能考量

在高并发场景下,追踪数据的采集和上报可能产生额外开销。建议在生产环境中合理设置采样率(management.tracing.sampling.probability),在可观测性和性能之间取得平衡。

Apache 2.0 Licensed