本文内容主要翻译(意译)自Yurishkuro大神的opentracing-tutorial java,加了一些补充说明,方便理解,习惯看英文的也可以看原文。总共4篇,本文是第1篇。如果你还没接触过OpenTracing,建议先读这篇文章《OpenTracing概念术语介绍》和官方文档。
目标
学习如何:
- 实例化一个Tracer
- 创建一个简单的链路
- 给链路增加一些注解(annotation):即增加Tag和Log。
开发步骤
说明:源代码的exercise包下面的类是空的,是留给我们按教程一步步补充完善的;solution包是已经编写好的代码。我翻译的时候,都直接运行的是solution里面的代码,但教程里面是逐步完善代码的,也就是会有一个中间状态。所以我会根据内容作了一些必要的注释和修改。但如果你是第一次看的话,建议按照教程自己手动在exercise里面完善。跟着教程一步步学习。
创建一个简单的Hello-World程序
创建一个简单的打印程序:接受一个参数,输出"Hello, {arg}!"。代码如下:
package lesson01.exercise;
public class Hello {
private void sayHello(String helloTo) {
String helloStr = String.format("Hello, %s!", helloTo);
System.out.println(helloStr);
}
public static void main(String[] args) {
if (args.length != 1) {
throw new IllegalArgumentException("Expecting one argument");
}
String helloTo = args[0];
new Hello().sayHello(helloTo);
}
}
运行:
$ ./run.sh lesson01.exercise.Hello Bryan
Hello, Bryan!
创建一个trace
一个trace是由若干span组成的有向无环图。一个span代表应用中的一个逻辑操作,每个span至少包含三个属性:操作名(an operation time)、开始时间(start time)、结束时间(finish time)。
下面我们使用一个io.opentracing.Tracer
实例创建由一个span组成的trace,可以使用io.opentracing.util.GlobalTracer.get()
创建一个全局的Tracer实例。代码如下:
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
public class Hello {
private final Tracer tracer;
private Hello(Tracer tracer) {
this.tracer = tracer;
}
private void sayHello(String helloTo) {
Span span = tracer.buildSpan("say-hello").start();
String helloStr = String.format("Hello, %s!", helloTo);
System.out.println(helloStr);
span.finish();
}
public static void main(String[] args) {
if (args.length != 1) {
throw new IllegalArgumentException("Expecting one argument");
}
String helloTo = args[0];
new Hello(GlobalTracer.get()).sayHello(helloTo);
}
}
这里我们使用了OpenTracing API的一些基本特性:
- 调用
tracer
实例的buildSpan()
方法创建一个span buildSpan()
方法的参数就是span的操作名- 调用
start()
方法真正创建出一个span - 通过调用
finish()
方法结束一个span - span的开始时间和结束时间由具体的tracer实现自动生成(获取创建和结束span时的系统时间戳)
此时,我们运行程序并不会和原来的程序有什么区别,也不会产生链路数据。因为OpenTracing只提供了SDK,并没有提供具体的链路实现,所以要产生真正的链路数据,需要借助具体的链路实现。
部署Jaeger(补充段落,原文没有)
这里我们选择Uber开源的Jaeger(发音为\ˈyā-gər\ ),因为它对OpenTracing支持的比较好,而且部署使用也非常简单。另外Jaeger的作者就是Yurishkuro。这里就不介绍Jaeger的细节了,有兴趣的可以去官网了解:Jaeger官网。
Jaeger部署非常简单,从这里下载安装包或者下载docker镜像。这里我下载的macOS的安装包,解压后可以看到如下文件:
example-hotrod
jaeger-agent
jaeger-all-in-one
jaeger-collector
jaeger-ingester
jaeger-query
直接运行./jaeger-all-in-one
便可以启动一个完整的Jaeger。此时访问http://localhost:16686/即可查看Jaeger的UI:
这样,一个OpenTracing的实现(Jaeger)就有了。接下来我们看如何在代码中集成。
集成Jaeger
在pom.xml中引入Jaeger的依赖:
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-client</artifactId>
<version>0.32.0</version>
</dependency>
然后写一个创建tracer的函数:
import io.jaegertracing.Configuration;
import io.jaegertracing.Configuration.ReporterConfiguration;
import io.jaegertracing.Configuration.SamplerConfiguration;
import io.jaegertracing.internal.JaegerTracer;
public static JaegerTracer initTracer(String service) {
SamplerConfiguration samplerConfig = SamplerConfiguration.fromEnv().withType("const").withParam(1);
ReporterConfiguration reporterConfig = ReporterConfiguration.fromEnv().withLogSpans(true);
Configuration config = new Configuration(service).withSampler(samplerConfig).withReporter(reporterConfig);
return config.getTracer();
}
最后,修改原来代码中的main函数:
Tracer tracer = initTracer("hello-world");
new Hello(tracer).sayHello(helloTo);
注意我们给initTracer()
方法传入了一个参数hello-world,这个是服务名。该服务里面产生的所有span公用这个服务名,一般服务名会用来做过滤和聚合。
现在运行代码,可以看到日志中有输出产生的span信息,而且也能看到Tracer实例的一些信息:
19:07:10.645 [main] DEBUG io.jaegertracing.thrift.internal.senders.ThriftSenderFactory - Using the UDP Sender to send spans to the agent.
19:07:10.729 [main] DEBUG io.jaegertracing.internal.senders.SenderResolver - Using sender UdpSender()
# tracer实例信息
19:07:10.776 [main] INFO io.jaegertracing.Configuration - Initialized tracer=JaegerTracer(version=Java-1.1.0, serviceName=hello-world, reporter=CompositeReporter(reporters=[RemoteReporter(sender=UdpSender(), closeEnqueueTimeout=1000), LoggingReporter(logger=Logger[io.jaegertracing.internal.reporters.LoggingReporter])]), sampler=ConstSampler(decision=true, tags={sampler.type=const, sampler.param=true}), tags={hostname=NYC-MacBook, jaeger.version=Java-1.1.0, ip=192.168.0.109}, zipkinSharedRpcSpan=false, expandExceptionLogs=false, useTraceId128Bit=false)
Hello, Bryan!
# span信息
19:07:10.805 [main] INFO io.jaegertracing.internal.reporters.LoggingReporter - Span reported: a86d76defe28d413:a86d76defe28d413:0:1 - say-hello
当然也可以以调试模式启动,观察更多细节。
这个时候,我们打开Jaeger的UI,左侧的Service选择“hello-world”,然后点击最下面的“Find Traces”,就可以查到刚才这次程序运行产生的Trace信息了:
点击链路详情进去后,再次点击操作名,可以查看一些基本信息,Jaeger默认已经加了一些基本的信息。
下面我们来看如何加一些自定义的信息。
增加Tags和Logs
OpenTracing规定了可以给Span增加三种类型的注解信息:
- Tags:key-value格式的数据,key和value完全由用户自定义。需要注意的是Tags增加的信息应该是属于描述整个span的,也就是它是span的一个静态属性,记录的信息适用于span从创建到完成的任何时刻。再说直白点就是记录和时间点无关的信息,这个主要是和下面的Logs作区分。
- Logs:和Tags类似,也是key-value格式的数据,区别在于Logs的信息都会带一个时间戳属性,记录这条属性产生的时间戳,所以比较适合记录日志、异常栈等一些和时间相关的信息。
- Baggage Items:这个主要是用于跨进程全局传输数据,后面的lesson04专门演示这个特性,这里先不展开介绍了。
Tags和Logs的记录非常的简单和方便:
private void sayHello(String helloTo) {
Span span = tracer.buildSpan("say-hello").start();
// 增加Tags信息
span.setTag("hello-to", helloTo);
String helloStr = String.format("Hello, %s!", helloTo);
// 增加Logs信息
span.log(ImmutableMap.of("event", "string-format", "value", helloStr));
System.out.println(helloStr);
// 增加Logs信息
span.log(ImmutableMap.of("event", "println"));
span.finish();
}
注意这里使用了Guava's ImmutableMap.of()
来构造一个Map。
再次运行程序,同样会产生一个span,但这次span会多了一个Tag和Log信息(Jaeger默认已经加了一些内部的tag数据):
从图中可以看到代码中加的Tags信息和Logs信息,而且Logs信息是带了时间了(这里展示的是从span开始时间经过的毫秒数)。关于Tags和Logs的规范,OpenTracing做了一些引导规范,可以参考:semantic_conventions.
总结
本文主要展示了如何创建一个span,下篇文章演示如何如果创建一个包含多个span的trace,以及如何在进程内部(不同方法间)传递span信息。
评论已关闭