分布式链路追踪实战篇-日志库集成opentelemetry的思路

由上文分布式链路追踪入门篇-基础原理与快速应用可以知道分布式链路追踪的作用,但是距离应用到项目中,我们还需要对项目中一些关键组件进行opentelemetry的集成,例如日志库,ORM、http框架、rpc框架等。

一、日志库如何集成opentelemetry?

其实对日志库的集成无非就是调用日志库的方法进行日志打印时,可以把日志信息也被记录下来,推送到可视化的后端(eg:jaeger)。而由上文分布式链路追踪入门篇-基础原理与快速应用可以知道,通过span可以关联上相关的操作信息

二、简单demo演示

例如就对我们go原生的log库进行封装:

log.go

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

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只是一个演示,我们也可以依照这个思路封装我们自己项目中的日志库

相关推荐
蒙娜丽宁2 天前
Go语言错误处理详解
ios·golang·go·xcode·go1.19
qq_172805593 天前
GO Govaluate
开发语言·后端·golang·go
littleschemer3 天前
Go缓存系统
缓存·go·cache·bigcache
程序者王大川4 天前
【GO开发】MacOS上搭建GO的基础环境-Hello World
开发语言·后端·macos·golang·go
Grassto4 天前
Gitlab 中几种不同的认证机制(Access Tokens,SSH Keys,Deploy Tokens,Deploy Keys)
go·ssh·gitlab·ci
高兴的才哥5 天前
kubevpn 教程
kubernetes·go·开发工具·telepresence·bridge to k8s
少林码僧6 天前
sqlx1.3.4版本的问题
go
蒙娜丽宁6 天前
Go语言结构体和元组全面解析
开发语言·后端·golang·go
蒙娜丽宁6 天前
深入解析Go语言的类型方法、接口与反射
java·开发语言·golang·go
三里清风_6 天前
Docker概述
运维·docker·容器·go