由上文分布式链路追踪入门篇-基础原理与快速应用可以知道分布式链路追踪的作用,但是距离应用到项目中,我们还需要对项目中一些关键组件进行opentelemetry的集成,例如日志库,ORM、http框架、rpc框架等。
一、日志库如何集成opentelemetry?
其实对日志库的集成无非就是调用日志库的方法进行日志打印时,可以把日志信息也被记录下来,推送到可视化的后端(eg:jaeger)。而由上文分布式链路追踪入门篇-基础原理与快速应用可以知道,通过span可以关联上相关的操作信息
二、简单demo演示
例如就对我们go原生的log库进行封装:
log.go
package pkg
import (
`context`
`log`
`os`
`go.opentelemetry.io/otel/attribute`
`go.opentelemetry.io/otel/trace`
)
//封装一下日志
type Logger struct {
log *log.Logger
}
var Log *Logger
func init() {
Log = &Logger{
log: log.New(os.Stderr, "", log.LstdFlags),
}
}
func (l *Logger) Debug(ctx context.Context, msg string) {
span := trace.SpanFromContext(ctx)
span.AddEvent("", trace.WithAttributes(attribute.String("log", msg)))
l.log.Println(msg)
}
//todo 原生日志库只有Print方法,没有INFO、WARNd等分级
func (l *Logger) Info(ctx context.Context, msg string) {}
//todo
func (l *Logger) Warn(ctx context.Context, msg string) {}
//todo
func (l *Logger) Error(ctx context.Context, msg string) {}
//todo
func (l *Logger) Fatal(ctx context.Context, msg string) {}
main.go
package main
import (
"context"
"fmt"
"log"
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/trace/jaeger"
`go.opentelemetry.io/otel/sdk/resource`
sdktrace "go.opentelemetry.io/otel/sdk/trace"
`go.opentelemetry.io/otel/semconv`
`otel/log/pkg`
)
// 初始化 OpenTelemetry
func initTracer() *sdktrace.TracerProvider {
exporter, err := jaeger.NewRawExporter(
jaeger.WithAgentEndpoint(func(options *jaeger.AgentEndpointOptions) {
options.Host = "localhost"
options.Port = "6831"
}),
)
if err != nil {
log.Fatalf("Error creating Jaeger exporter: %v", err)
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.ServiceNameKey.String("demo_service"), // 服务名
)),
)
otel.SetTracerProvider(tp)
return tp
}
func main() {
tp := initTracer()
defer func() {
if cerr := tp.Shutdown(context.Background()); cerr != nil {
log.Fatalf("Error shutting down tracer provider: %v", cerr)
}
}()
//启动http服务器
http.HandleFunc("/log/demo", handleRequest)
go func() {
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Error starting Service A server: %v", err)
}
}()
//模拟请求
SimulateRequest()
}
func SimulateRequest() {
req, err := http.NewRequest("GET", "http://localhost:8080/log/demo", nil)
if err != nil {
log.Fatalf("Creating request fail: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
fmt.Println("Response received from Root Service")
}
func handleRequest(w http.ResponseWriter, req *http.Request) {
tracer := otel.Tracer("root")
//开始创建root span
ctx, span := tracer.Start(req.Context(), "root service")
defer span.End()
pkg.Log.Debug(ctx, "this is root service")
//访问服务A
callServiceA(ctx)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Response from Service Root")
}
// Service A
func callServiceA(ctx context.Context) {
tracer := otel.Tracer("service A")
ctx, span := tracer.Start(ctx, "ServiceA")
defer span.End()
pkg.Log.Debug(ctx, "this is A service")
fmt.Println("Service A")
}
运行程序后,访问jeager:
三、总结
1. 其实日志库的集成就是对原先的日志库进行一层封装,日志打印方法传入上下文,通过上下文获取到操作单元span,然后给span关联上日志信息
2. 上面demo只是一个演示,我们也可以依照这个思路封装我们自己项目中的日志库