并发系统中每秒钟往往有很多请求,在系统记录日志时候一般我们会给每条请求分配一个请求id,当系统有多个微服务时候往往希望一个关联id能把整个系统串联起来,这样当出现问题时候可以方便的排查问题;
现在以目前所开发的go项目为例,简要介绍如何利用logrus中间件将业务日志加上traceId,让每条请求的所有处理日志都带上这个id。
目前的项目主要以web请求处理为主,每个请求过来的时候都会携带一个请求id,如果没有的话,我们可以直接用uuid生成,然后作为后续的traceid,具体生成的位置,可以在HandleFunc地方使用中间件嵌入,或者具体的处理函数中添加;
先介绍使用中间件解析的形式,让下面的每一个HandleFunc 都先经过中间件,中间件中解析请求,在header中嵌入traceId,这样client和server端都具有了相同的id;
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
http.HandleFunc("/xxxx", middle.Middleware(handler.Handle))
func Middleware(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
traceID := r.FormValue("reqid")
if traceID == "" {
uid := uuid.NewV4()
r.Form.Set("reqid", uid.String())
traceID = uid.String()
}
logger.AddHook(hook.NewTraceIdHook(traceID))
w.Header().Set("X-Trace-Id", traceID)
next.ServeHTTP(w, r)
})
}
中间件这里对logrus初始化时候,增加了一个hook ,这样后续这条请求的日志都会记录上traceId
logger.AddHook(hook.NewTraceIdHook(traceID))
具体的hook.go 如下
package hook
import (
"github.com/sirupsen/logrus"
)
type TraceIdHook struct {
TraceId string
}
func NewTraceIdHook(traceId string) logrus.Hook {
hook := TraceIdHook{
TraceId: traceId,
}
return &hook
}
func (hook *TraceIdHook) Fire(entry *logrus.Entry) error {
entry.Data["traceId"] = hook.TraceId
return nil
}
func (hook *TraceIdHook) Levels() []logrus.Level {
return logrus.AllLevels
}
新增logger模块,对logrus包中方法进行扩展
package logger
import (
"context"
"io"
"github.com/sirupsen/logrus"
)
// Define key
const (
TraceIDKey = "trace_id"
UserIDKey = "user_id"
TagKey = "tag"
VersionKey = "version"
StackKey = "stack"
)
var (
version string
)
// Logger Logrus
type Logger = logrus.Logger
// Entry Logrus entry
type Entry = logrus.Entry
// Hook 定义日志钩子别名
type Hook = logrus.Hook
// StandardLogger 获取标准日志
func StandardLogger() *Logger {
return logrus.StandardLogger()
}
// SetLevel 设定日志级别
func SetLevel(level int) {
logrus.SetLevel(logrus.Level(level))
}
// SetFormatter 设定日志输出格式
func SetFormatter(format string) {
switch format {
case "json":
logrus.SetFormatter(new(logrus.JSONFormatter))
default:
logrus.SetFormatter(new(logrus.TextFormatter))
}
}
// SetOutput 设定日志输出
func SetOutput(out io.Writer) {
logrus.SetOutput(out)
}
// SetVersion 设定版本
func SetVersion(v string) {
version = v
}
// AddHook 增加日志钩子
func AddHook(hook Hook) {
logrus.AddHook(hook)
}
type (
traceIDKey struct{}
userIDKey struct{}
tagKey struct{}
stackKey struct{}
)
// NewTraceIDContext 创建跟踪ID上下文
func NewTraceIDContext(ctx context.Context, traceID string) context.Context {
return context.WithValue(ctx, traceIDKey{}, traceID)
}
// FromTraceIDContext 从上下文中获取跟踪ID
func FromTraceIDContext(ctx context.Context) string {
v := ctx.Value(traceIDKey{})
//todo remove
if v != nil {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
// NewUserIDContext 创建用户ID上下文
func NewUserIDContext(ctx context.Context, userID string) context.Context {
return context.WithValue(ctx, userIDKey{}, userID)
}
// FromUserIDContext 从上下文中获取用户ID
func FromUserIDContext(ctx context.Context) string {
v := ctx.Value(userIDKey{})
if v != nil {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
// NewTagContext 创建Tag上下文
func NewTagContext(ctx context.Context, tag string) context.Context {
return context.WithValue(ctx, tagKey{}, tag)
}
// FromTagContext 从上下文中获取Tag
func FromTagContext(ctx context.Context) string {
v := ctx.Value(tagKey{})
if v != nil {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
// NewStackContext 创建Stack上下文
func NewStackContext(ctx context.Context, stack error) context.Context {
return context.WithValue(ctx, stackKey{}, stack)
}
// FromStackContext 从上下文中获取Stack
func FromStackContext(ctx context.Context) error {
v := ctx.Value(stackKey{})
if v != nil {
if s, ok := v.(error); ok {
return s
}
}
return nil
}
// WithContext Use context create entry
func WithContext(ctx context.Context) *Entry {
if ctx == nil {
ctx = context.Background()
}
fields := map[string]interface{}{}
if version != "" {
fields[VersionKey] = version
}
if v := FromTraceIDContext(ctx); v != "" {
fields[TraceIDKey] = v
}
//if v := FromUserIDContext(ctx); v != "" {
// fields[UserIDKey] = v
//}
//
//if v := FromTagContext(ctx); v != "" {
// fields[TagKey] = v
//}
//
//if v := FromStackContext(ctx); v != nil {
// fields[StackKey] = fmt.Sprintf("%+v", v)
//}
return logrus.WithContext(ctx).WithFields(fields)
}
// Define logrus alias
var (
//Tracef = logrus.Tracef
Debugf = logrus.Debugf
Infof = logrus.Infof
Warnf = logrus.Warnf
Errorf = logrus.Errorf
Fatalf = logrus.Fatalf
Panicf = logrus.Panicf
Printf = logrus.Printf
Info = logrus.Info
Debug = logrus.Debug
Error = logrus.Error
)
这种形式较为复杂,还有一种简单的形式,就是只在具体的handlefunc 处理的时候,让logrus 增加一个hook即可;
先添加上面的hook.go,然后在具体的请求处理过程中,判断是debug请求(根据需要)的话,设置logrus的hook即可,这种形式最为简单;
if reqparms.Debug {
logger.AddHook(hook.NewTraceIdHook(clientParams.ReqId))
}
其他业务模块可根据服务传递的traceId按照上面的方法,即可实现在不同模块使用相同traceId记录服务日志,如果能通过kafka、logd、filebeat等收集到es后即可一个id实现所有的关联日志查询; 参考: [1]gin-admin [2]从别人的代码中学习golang系列 [3]opentracing-go
本文链接:https://iokde.com/post/how-to-add-traceId-for-business-system.html,参与评论 »
--EOF--
发表于 2021-08-05 18:50:00。
本站使用「署名 4.0 国际」创作共享协议,转载请注明作者及原网址。tools更多说明 »
提醒:本文最后更新于 1033 天前,文中所描述的信息可能已发生改变,请谨慎使用。
Comments