如何利用 APM 追踪完整的类函数调用

通常,应用接入 APM 后,可以追踪到应用相关组件、服务间的调用链路情况,如 Tomcat、Redis、MySQL 等,这是因为 APM 对于标准性组件做了插桩处理,从而更好的观测到在实际使用过程中组件调用对应用的影响。

而在实际生产过程中,非标代码即业务代码,常常对业务影响程度更深,不同程度的开发人员对代码理解程度、编写能力也存在这差异,对于业务代码,如何追踪完整的类函数调用,找到关键问题所在仍是至关重要。

所幸的是 APM 研发人员也是科班出生,也曾体会过人间疾苦,同样对 APM 赋予了更高的期望。DataDog、OpenTelemetry 也提供了相关的功能,让我们一窥究竟。

以 JAVA 为例,分别探究 DDTrace(DataDog)、OpenTelemetry。

如何使用 DDTrace 追踪函数调用

准备一段代码

java 复制代码
    @Autowired
    private TestService testService;

    @GetMapping("/user")
    @ResponseBody
    public String getUser(){
        logger.info("do getUser");
        return testService.users();
    }

Service 接口

java 复制代码
public interface TestService {

    String users();
}

Service 实现类

java 复制代码
package com.zy.observable.server.service;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class TestServiceImpl implements TestService {
    private static final Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
    public String getUsername(){
        return "lr";
    }

    public String users(){
        Map<Integer,Student> users =new HashMap<>();
        users.put(1,new Student("tom",18));
        users.put(2,new Student("joy",20));
        users.put(3,new Student("lucy",30));
        users.forEach((k,v)->print(k,v));
        return getUsername();
    }

    public void print(Integer level,Student student){
        logger.info("level:{},username:{}",level,student.getUsername());
    }
}

当 Controller 层调用 testService.users(),默认情况下,users不会作为链路的一部分,用以下命令运行

shell 复制代码
java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-jar springboot-server.jar

或者在 idea 工具进行调试均可。

请求 http://localhost:8090/user

可以观察到链路情况,有两个 span。

如何将 users方法也体现在链路中,DDTrace 提供了以下参数可以发现业务代码信息。

  • 参数方式:-Ddd.trace.methods
  • 环境变量方式:DD_TRACE_METHODS

用以下命令

shell 复制代码
java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-Ddd.trace.methods="com.zy.observable.server.service.TestService[users]" \
-jar springboot-server.jar

如果想追踪一个类中的所有函数调用,则用*来表示。

shell 复制代码
java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-Ddd.trace.methods="com.zy.observable.server.service.TestService[*]" \
-jar springboot-server.jar

尽管TestService只有一个接口方法,用通配符*,仍会出现多个 span 信息。

细心的你可能已经发现了问题,TestServiceImpl 分别提供了三个函数getUsernameusersprint,其中 users 分别调用了 getUsernameprint,为什么链路上有print却没有getUsername

  • 为什么链路上有print

原因在于 TestServiceImpl 实现了 TestService 接口,而 * 则代表所有,ddtrace 实际是对TestService的实现类TestServiceImpl进行了增强处理。相当于-Ddd.trace.methods="com.zy.observable.server.service.TestServiceImpl[*]"

  • 为什么链路上没有getUsername

主要是因为 DDTrace 对一些关键方法做了屏蔽,对于以下类型的方法函数将不会做增强处理。

  • constructors
  • getters
  • setters
  • synthetic
  • toString
  • equals
  • hashcode
  • finalizer method calls

值得注意的是,以上链路print函数出现了三个 span,是由于stus是一个 Map 集合,循环了三次,调用了三次 print。这在某些场景下是致命的,试想想,如果stus集合是 1000,print则会生成对应数量的 span。这不管是查看链路还是成本估算,都有着很高的代价。前者由于 span 太多,会导致浏览器内存占用高,造成 UI 界面卡顿,后者增加了存储和查询成本。

如何使用 OpenTelemetry 追踪函数调用

继续沿用上面的代码,在不添加其参数的情况下,用以下命令运行应用

shell 复制代码
-javaagent:/home/liurui/agent/opentelemetry-javaagent-1.26.1-guance.jar
-Dotel.traces.exporter=otlp
-Dotel.exporter.otlp.endpoint=http://localhost:4317
-Dotel.resource.attributes=service.name=springboot-server

可以观察到 OpenTelemetry 链路 Span 与 DDTrace 基本一致。

在无法修改代码的情况下,OpenTelemetry 也提供了以下配置 Java 代理来捕获特定方法的跨度。

  • 参数方式:-Dotel.instrumentation.methods.include
  • 环境变量方式:OTEL_INSTRUMENTATION_METHODS_INCLUDE

需要注意的是,otel 不支持同配符的方式进行配置。

运行以下命令

shell 复制代码
-javaagent:/home/liurui/agent/opentelemetry-javaagent-1.26.1-guance.jar
-Dotel.traces.exporter=otlp
-Dotel.exporter.otlp.endpoint=http://localhost:4317
-Dotel.resource.attributes=service.name=springboot-server
-Dotel.instrumentation.methods.include="com.zy.observable.server.service.TestService[users]"

同样 users 函数也被添加了 span 信息。

SDK 方式

以上分别介绍了 DDTrace 和 OpenTelemetry 在非侵入式的情况下对业务特定函数方法进行链路追踪。同样它们也提供了 SDK 的方式,具有一定的代码侵入性,但表现为更灵活。

  • DDTrace 使用注解 @Trace配置业务 Span,实际用法以及相关依赖,参考链接
  • OpenTelemetry 使用注解 @WithSpan 配置业务 Span,参考链接

关于 SDK 方式,这里不再一一分析。

参考文档

DDTrace Agent 下载地址

OpenTelemetry Agent 下载地址

OpenTelemetry methods

SpringBoot-Server demo

相关推荐
为思念酝酿的痛14 小时前
POSIX信号量
linux·运维·服务器·后端
小羊在睡觉14 小时前
力扣84. 柱状图中最大的矩形
后端·算法·leetcode·golang·go
专业白嫖怪14 小时前
什么是docker
运维·docker·容器
swipe14 小时前
Neo4j + Graph RAG 医疗知识图谱工程实践:患者教育问答真正需要的是“关系可追溯”
后端·langchain·llm
人还是要有梦想的15 小时前
linux下用搜狗输入法,中英文切换
linux·运维·服务器
北京智和信通15 小时前
某部队IT基础设施及机房动环统一运维建设实例
运维·网管平台·网管软件·网络管理系统·网络运维平台·网络运维系统
乐维_lwops15 小时前
从 “救火运维” 到 “自动驾驶”:运维智能体到底解决了什么?
运维·人工智能·运维智能体
bush415 小时前
嵌入式linux学习记录二
linux·运维·学习
源码宝15 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
weixin_4684668516 小时前
MoneyPrinterTurbo 短视频自动化生产实战指南
运维·人工智能·自动化·大模型·音视频·moneyprinter