Go jaegerde 应用【logger+gorm+grpc+http】
在以前的Go语言jaeger和opentracing 有用来做日志,但是很多时候我们希望数据库的操作也可以记录下来,程序一般作为http或者grpc 服务, 所以grpc和http也是需要用中间件来实现的。首先看程序的目录, 只是一个简单的demo:
因为程序最后会部署到k8s上,计划采用docker来收集,灌到elk或者graylog,所以这里直接输出,程序设计采用切换数据库 实现简单的saas。
来看看主要的几个文件
logger.go
package logger import ( "context" "fmt" "io" "runtime" "strings" "time" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" "github.com/uber/jaeger-client-go/config" "github.com/uber/jaeger-client-go/log" "github.com/uber/jaeger-lib/metrics" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var ( logTimeFormat = "2006-01-02T15:04:05.000+08:00" zapLogger *zap.Logger ) //配置默认初始化 func init() { c := zap.NewProductionConfig() c.EncoderConfig.LevelKey = "" c.EncoderConfig.CallerKey = "" c.EncoderConfig.MessageKey = "logModel" c.EncoderConfig.TimeKey = "" c.Level = zap.NewAtomicLevelAt(zap.DebugLevel) zapLogger, _ = c.Build() } //初始化 Jaeger client func NewJaegerTracer(serviceName string, agentHost string) (tracer opentracing.Tracer, closer io.Closer, err error) { cfg := config.Configuration{ ServiceName: serviceName, Sampler: &config.SamplerConfig{ Type: jaeger.SamplerTypeRateLimiting, Param: 10, }, Reporter: &config.ReporterConfig{ LogSpans: false, BufferFlushInterval: 1 * time.Second, LocalAgentHostPort: agentHost, }, } jLogger := log.StdLogger jMetricsFactory := metrics.NullFactory tracer, closer, err = cfg.NewTracer(config.Logger(jLogger), config.Metrics(jMetricsFactory)) if err == nil { opentracing.SetGlobalTracer(tracer) } return tracer, closer, err } func Error(ctx context.Context, format interface{}, args ...interface{}) { msg := "" if e, ok := format.(error); ok { msg = fmt.Sprintf(e.Error(), args...) } else if e, ok := format.(string); ok { msg = fmt.Sprintf(e, args...) } jsonStdOut(ctx, zap.ErrorLevel, msg) } func Warn(ctx context.Context, format string, args ...interface{}) { jsonStdOut(ctx, zap.WarnLevel, fmt.Sprintf(format, args...)) } func Info(ctx context.Context, format string, args ...interface{}) { jsonStdOut(ctx, zap.InfoLevel, fmt.Sprintf(format, args...)) } func Debug(ctx context.Context, format string, args ...interface{}) { jsonStdOut(ctx, zap.DebugLevel, fmt.Sprintf(format, args...)) } //本地打印 Json func jsonStdOut(ctx context.Context, level zapcore.Level, msg string) { traceId, spanId := getTraceId(ctx) if ce := zapLogger.Check(level, "zap"); ce != nil { ce.Write( zap.Any("message", JsonLogger{ LogTime: time.Now().Format(logTimeFormat), Level: level, Content: msg, CallPath: getCallPath(), TraceId: traceId, SpanId: spanId, }), ) } } type JsonLogger struct { TraceId string `json:"traceId"` SpanId uint64 `json:"spanId"` Content interface{} `json:"content"` CallPath interface{} `json:"callPath"` LogTime string `json:"logDate"` //日志时间 Level zapcore.Level `json:"level"` //日志级别 } func getTraceId(ctx context.Context) (string, uint64) { span := opentracing.SpanFromContext(ctx) if span == nil { return "", 0 } if sc, ok := span.Context().(jaeger.SpanContext); ok { return fmt.Sprintf("%v", sc.TraceID()), uint64(sc.SpanID()) } return "", 0 } func getCallPath() string { _, file, lineno, ok := runtime.Caller(2) if ok { return strings.Replace(fmt.Sprintf("%s:%d", stringTrim(file, ""), lineno), "%2e", ".", -1) } return "" } func stringTrim(s, cut string) string { ss := strings.SplitN(s, cut, 2) if len(ss) == 1 { return ss[0] } return ss[1] }
db.go
package db import ( "context" "database/sql/driver" "fmt" "net/url" "reflect" "regexp" "strings" "tracedemo/logger" "unicode" "github.com/jinzhu/gorm" "github.com/pkg/errors" "sync" "time" _ "github.com/go-sql-driver/mysql" "github.com/opentracing/opentracing-go" ) // DB连接配置信息 type Config struct { DbHost string DbPort int DbUser string DbPass string DbName string Debug bool } // 连接的数据库类型 const ( dbMaster string = "master" jaegerContextKey = "jeager:context" callbackPrefix = "jeager" startTime = "start:time" ) func init() { connMap = make(map[string]*gorm.DB) } var ( connMap map[string]*gorm.DB connLock sync.RWMutex ) // 初始化DB func InitDb(siteCode string, cfg *Config) (err error) { url := url.Values{} url.Add("parseTime", "True") url.Add("loc", "Local") url.Add("charset", "utf8mb4") url.Add("collation", "utf8mb4_unicode_ci") url.Add("readTimeout", "0s") url.Add("writeTimeout", "0s") url.Add("timeout", "0s") dsn := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?%s", cfg.DbUser, cfg.DbPass, cfg.DbHost, cfg.DbPort, cfg.DbName, url.Encode()) conn, err := gorm.Open("mysql", dsn) if err != nil { return errors.Wrap(err, "fail to connect db") } //新增gorm插件 if cfg.Debug == true { registerCallbacks(conn) } //打印日志 //conn.LogMode(true) conn.DB().SetMaxIdleConns(30) conn.DB().SetMaxOpenConns(200) conn.DB().SetConnMaxLifetime(60 * time.Second) if err := conn.DB().Ping(); err != nil { return errors.Wrap(err, "fail to ping db") } connLock.Lock() dbName := fmt.Sprintf("%s-%s", siteCode, dbMaster) connMap[dbName] = conn connLock.Unlock() go mysqlHeart(conn) return nil } func GetMaster(ctx context.Context) *gorm.DB { connLock.RLock() defer connLock.RUnlock() siteCode := fmt.Sprintf("%v", ctx.Value("SiteCode")) if strings.Contains(siteCode, "nil") { panic(errors.New("当前上下文没有找到DB")) } dbName := fmt.Sprintf("%s-%s", siteCode, dbMaster) ctx = context.WithValue(ctx, "DbName", dbName) db := connMap[dbName] if db == nil { panic(errors.New(fmt.Sprintf("当前上下文没有找到DB:%s", dbName))) } return db.Set(jaegerContextKey, ctx) } func mysqlHeart(conn *gorm.DB) { for { if conn != nil { err := conn.DB().Ping() if err != nil { fmt.Println(fmt.Sprintf("mysqlHeart has err:%v", err)) } } time.Sleep(3 * time.Minute) } } func registerCallbacks(db *gorm.DB) { driverName := db.Dialect().GetName() switch driverName { case "postgres": driverName = "postgresql" } spanTypePrefix := fmt.Sprintf("gorm.db.%s.", driverName) querySpanType := spanTypePrefix + "query" execSpanType := spanTypePrefix + "exec" type params struct { spanType string processor func() *gorm.CallbackProcessor } callbacks := map[string]params{ "gorm:create": { spanType: execSpanType, processor: func() *gorm.CallbackProcessor { return db.Callback().Create() }, }, "gorm:delete": { spanType: execSpanType, processor: func() *gorm.CallbackProcessor { return db.Callback().Delete() }, }, "gorm:query": { spanType: querySpanType, processor: func() *gorm.CallbackProcessor { return db.Callback().Query() }, }, "gorm:update": { spanType: execSpanType, processor: func() *gorm.CallbackProcessor { return db.Callback().Update() }, }, "gorm:row_query": { spanType: querySpanType, processor: func() *gorm.CallbackProcessor { return db.Callback().RowQuery() }, }, } for name, params := range callbacks { params.processor().Before(name).Register( fmt.Sprintf("%s:before:%s", callbackPrefix, name), newBeforeCallback(params.spanType), ) params.processor().After(name).Register( fmt.Sprintf("%s:after:%s", callbackPrefix, name), newAfterCallback(), ) } } func newBeforeCallback(spanType string) func(*gorm.Scope) { return func(scope *gorm.Scope) { ctx, ok := scopeContext(scope) if !ok { return } //新增链路追踪 span, ctx := opentracing.StartSpanFromContext(ctx, spanType) if span.Tracer() == nil { span.Finish() ctx = nil } scope.Set(jaegerContextKey, ctx) scope.Set(startTime, time.Now().UnixNano()) } } func newAfterCallback() func(*gorm.Scope) { return func(scope *gorm.Scope) { ctx, ok := scopeContext(scope) if !ok { return } span := opentracing.SpanFromContext(ctx) if span == nil { return } defer span.Finish() duration := int64(0) if t, ok := scopeStartTime(scope); ok { duration = (time.Now().UnixNano() - t) / 1e6 } logger.Debug(ctx, "[gorm] [%vms] [RowsReturned(%v)] %v ", duration, scope.DB().RowsAffected, gormSQL(scope.SQL, scope.SQLVars)) for _, err := range scope.DB().GetErrors() { if gorm.IsRecordNotFoundError(err) || err == errors.New("sql: no rows in result set") { continue } //打印错误日志 logger.Error(ctx, "%v", err.Error()) } //span.LogFields(traceLog.String("sql", scope.SQL)) } } func scopeContext(scope *gorm.Scope) (context.Context, bool) { value, ok := scope.Get(jaegerContextKey) if !ok { return nil, false } ctx, _ := value.(context.Context) return ctx, ctx != nil } func scopeStartTime(scope *gorm.Scope) (int64, bool) { value, ok := scope.Get(startTime) if !ok { return 0, false } t, ok := value.(int64) return t, ok } /*===============Log=======================================*/ var ( sqlRegexp = regexp.MustCompile(`\?`) numericPlaceHolderRegexp = regexp.MustCompile(`\$\d+`) ) func gormSQL(inputSql interface{}, value interface{}) string { var sql string var formattedValues []string for _, value := range value.([]interface{}) { indirectValue := reflect.Indirect(reflect.ValueOf(value)) if indirectValue.IsValid() { value = indirectValue.Interface() if t, ok := value.(time.Time); ok { if t.IsZero() { formattedValues = append(formattedValues, fmt.Sprintf("'%v'", "0000-00-00 00:00:00")) } else { formattedValues = append(formattedValues, fmt.Sprintf("'%v'", t.Format("2006-01-02 15:04:05"))) } } else if b, ok := value.([]byte); ok { if str := string(b); isPrintable(str) { formattedValues = append(formattedValues, fmt.Sprintf("'%v'", str)) } else { formattedValues = append(formattedValues, "'<binary>'") } } else if r, ok := value.(driver.Valuer); ok { if value, err := r.Value(); err == nil && value != nil { formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value)) } else { formattedValues = append(formattedValues, "NULL") } } else { switch value.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool: formattedValues = append(formattedValues, fmt.Sprintf("%v", value)) default: formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value)) } } } else { formattedValues = append(formattedValues, "NULL") } } if formattedValues == nil || len(formattedValues) < 1 { return sql } // differentiate between $n placeholders or else treat like ? if numericPlaceHolderRegexp.MatchString(inputSql.(string)) { sql = inputSql.(string) for index, value := range formattedValues { placeholder := fmt.Sprintf(`\$%d([^\d]|$)`, index+1) sql = regexp.MustCompile(placeholder).ReplaceAllString(sql, value+"$1") } } else { formattedValuesLength := len(formattedValues) for index, value := range sqlRegexp.Split(inputSql.(string), -1) { sql += value if index < formattedValuesLength { sql += formattedValues[index] } } } return sql } func isPrintable(s string) bool { for _, r := range s { if !unicode.IsPrint(r) { return false } } return true }
server.go
package apiserver import ( contextV2 "context" "fmt" "runtime/debug" "tracedemo/apiserver/userinfo" "tracedemo/logger" "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/context" "github.com/opentracing/opentracing-go" ) func StartApiServerr() { addr := ":8080" app := iris.New() app.Use(openTracing()) app.Use(withSiteCode()) app.Use(withRecover()) app.Get("/", func(c context.Context) { c.WriteString("pong") }) initIris(app) logger.Info(contextV2.Background(), "[apiServer]开始监听%s,", addr) err := app.Run(iris.Addr(addr), iris.WithoutInterruptHandler) if err != nil { logger.Error(contextV2.Background(), "[apiServer]开始监听%s 错误%v,", addr,err) } } func initIris(app *iris.Application) { api:= userinfo.ApiServer{} userGroup := app.Party("/user") { userGroup.Get("/test",api.TestUserInfo) userGroup.Get("/rpc",api.TestRpc) } } func openTracing() context.Handler { return func(c iris.Context) { span := opentracing.GlobalTracer().StartSpan("apiServer") c.ResetRequest(c.Request().WithContext(opentracing.ContextWithSpan(c.Request().Context(), span))) logger.Info(c.Request().Context(), "Api请求地址%v", c.Request().URL) c.Next() } } func withSiteCode() context.Handler { return func(c iris.Context) { siteCode := c.GetHeader("SiteCode") if len(siteCode) < 1 { siteCode = "001" } ctx := contextV2.WithValue(c.Request().Context(), "SiteCode", siteCode) c.ResetRequest(c.Request().WithContext(ctx)) c.Next() } } func withRecover() context.Handler { return func(c iris.Context) { defer func() { if e := recover(); e != nil { stack := debug.Stack() logger.Error(c.Request().Context(), fmt.Sprintf("Api has err:%v, stack:%v", e, string(stack))) } }() c.Next() } }
grpc的中间件middleware.go
package middleware import ( "context" "encoding/json" "fmt" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "runtime/debug" "strings" "time" "tracedemo/logger" ) type MDCarrier struct { metadata.MD } func (m MDCarrier) ForeachKey(handler func(key, val string) error) error { for k, strs := range m.MD { for _, v := range strs { if err := handler(k, v); err != nil { return err } } } return nil } func (m MDCarrier) Set(key, val string) { m.MD[key] = append(m.MD[key], val) } // ClientInterceptor 客户端拦截器 func ClientTracing(tracer opentracing.Tracer) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, request, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { //一个RPC调用的服务端的span,和RPC服务客户端的span构成ChildOf关系 var parentCtx opentracing.SpanContext parentSpan := opentracing.SpanFromContext(ctx) if parentSpan != nil { parentCtx = parentSpan.Context() } span := tracer.StartSpan( method, opentracing.ChildOf(parentCtx), opentracing.Tag{Key: string(ext.Component), Value: "gRPC Client"}, ext.SpanKindRPCClient, ) defer span.Finish() md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = metadata.New(nil) } else { md = md.Copy() } err := tracer.Inject( span.Context(), opentracing.TextMap, MDCarrier{md}, // 自定义 carrier ) if err != nil { logger.Error(ctx, "ClientTracing inject span error :%v", err.Error()) } ///SiteCode siteCode := fmt.Sprintf("%v", ctx.Value("SiteCode")) if len(siteCode) < 1 || strings.Contains(siteCode, "nil") { siteCode = "001" } md.Set("SiteCode", siteCode) // newCtx := metadata.NewOutgoingContext(ctx, md) err = invoker(newCtx, method, request, reply, cc, opts...) if err != nil { logger.Error(ctx, "ClientTracing call error : %v", err.Error()) } return err } } func ClientSiteCode() grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, request, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = metadata.New(nil) } else { md = md.Copy() } ///SiteCode siteCode := fmt.Sprintf("%v", ctx.Value("SiteCode")) if len(siteCode) < 1 || strings.Contains(siteCode, "nil") { siteCode = "001" } md.Set("SiteCode", siteCode) return invoker(ctx, method, request, reply, cc, opts...) } } func ClientTimeLog() grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, request, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { defer func() { if e := recover(); e != nil { stack := debug.Stack() logger.Error(ctx, fmt.Sprintf("grpc-client has err:%v, stack:%v", e, string(stack))) } }() startTime := time.Now().UnixNano() err := invoker(ctx, method, request, reply, cc, opts...) duration := (time.Now().UnixNano() - startTime) / 1e6 requestByte, _ := json.Marshal(request) responseByte, _ := json.Marshal(reply) logger.Info(ctx, fmt.Sprintf("grpc-client:方法名:%v,耗时:%vms,请求数据:%v,返回数据:%v", method, duration, string(requestByte), string(responseByte))) if err != nil { logger.Error(ctx, fmt.Sprintf("grpc-client:方法名:%v,耗时:%vms,请求数据:%v,返回错误:%v", method, duration, string(requestByte), err)) } return err } } // ServerInterceptor Server 端的拦截器 func ServerTracing(tracer opentracing.Tracer) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { md = metadata.New(nil) } spanContext, err := tracer.Extract( opentracing.TextMap, MDCarrier{md}, ) if err != nil && err != opentracing.ErrSpanContextNotFound { logger.Error(ctx, "ServerInterceptor extract from metadata err: %v", err) } else { span := tracer.StartSpan( info.FullMethod, ext.RPCServerOption(spanContext), opentracing.Tag{Key: string(ext.Component), Value: "(gRPC Server)"}, ext.SpanKindRPCServer, ) defer span.Finish() ctx = opentracing.ContextWithSpan(ctx, span) } return handler(ctx, req) } } func ServerSiteCode() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (_ interface{}, err error) { //读取siteCode incomingContext, ok := metadata.FromIncomingContext(ctx) siteCode := "" if ok { siteCodeArr := incomingContext.Get("SiteCode") if siteCodeArr != nil && len(siteCodeArr) > 0 { siteCode = siteCodeArr[0] } } else { incomingContext = metadata.New(nil) } if len(siteCode) < 1 { siteCode = "001" } //设置siteCode到上下文 c2 := context.WithValue(ctx, "001", siteCode) return handler(c2, req) } } func ServerTimeLog() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (_ interface{}, err error) { defer func() { if e := recover(); e != nil { stack := debug.Stack() logger.Error(ctx, fmt.Sprintf("grpc-client has err:%v, stack:%v", e, string(stack))) } }() startTime := time.Now().UnixNano() ret, err := handler(ctx, req) duration := (time.Now().UnixNano() - startTime) / 1e6 requestByte, _ := json.Marshal(req) responseStr := "" if err == nil { responseByte, _ := json.Marshal(ret) responseStr = string(responseByte) } logger.Info(ctx, fmt.Sprintf("grpc-server:方法名:%v,耗时:%vms,请求数据:%v,返回数据:%v", info.FullMethod, duration, string(requestByte), responseStr)) if err != nil { logger.Error(ctx, fmt.Sprintf("grpc-server:方法名:%v,耗时:%vms,请求数据:%v,返回错误:%v", info.FullMethod, duration, string(requestByte), err)) } return ret, err } }
grpc的调用userinfo.go
package userinfo import ( "fmt" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/opentracing/opentracing-go" "tracedemo/middleware" pb "tracedemo/protos" "tracedemo/service" "google.golang.org/grpc" "github.com/kataras/iris/v12" ) type ApiServer struct{} func (t *ApiServer) TestUserInfo(ctx iris.Context) { err := service.TestUserInfo(ctx.Request().Context()) if err != nil { ctx.WriteString("err:" + err.Error()) } else { ctx.WriteString("ok") } } func (t *ApiServer) TestRpc(ctx iris.Context) { addr := "localhost:9090" opts := []grpc.DialOption{ grpc.WithInsecure(), grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient( middleware.ClientTracing(opentracing.GlobalTracer()), middleware.ClientSiteCode(), middleware.ClientTimeLog(), )), } conn, err := grpc.Dial(addr, opts...) if err != nil { fmt.Println(err) } defer conn.Close() client := pb.NewGreeterClient(conn) request := &pb.HelloRequest{Name: "gavin"} response, err := client.SayHello(ctx.Request().Context(), request) if err != nil { ctx.WriteString("err:" + err.Error()) } else { ctx.WriteString("rpc:" + response.Message) } }
整个main.go
package main import ( "fmt" "os" "tracedemo/apiserver" "tracedemo/db" "tracedemo/grpcserver" "tracedemo/logger" ) func main() { //init log jaegerHost := "192.168.100.30:6831" serverName, _ := os.Hostname() serverName = "trace-" + serverName _, _, err := logger.NewJaegerTracer(serverName, jaegerHost) if err != nil { fmt.Println(fmt.Sprintf("初始化JaegerTracer错误%v", err)) } //初始化DB dbConfig := db.Config{ DbHost: "192.168.100.30", DbPort: 3306, DbUser: "root", DbPass: "root", DbName: "demo", Debug: true, } err = db.InitDb("001", &dbConfig) if err != nil { fmt.Println(fmt.Sprintf("初始化Db错误%v", err)) } //启动api go apiserver.StartApiServerr() //启动GRPC go grpcserver.StartGrpcServer() select {} }
service里面的实现非常简单
package service import ( "context" "tracedemo/db" "tracedemo/logger" "tracedemo/model" ) func TestUserInfo(ctx context.Context) error { info := model.UserInfo{ Name: "gavin", Hobby: "demo", } gormDb := db.GetMaster(ctx) err := info.Create(gormDb) if err != nil { logger.Error(ctx, "Create err %v", err) } else { logger.Info(ctx, "create Success!") } //update info.Name = "test" err = info.Update(gormDb) if err != nil { logger.Warn(ctx, "Update err %v", err) } else { logger.Debug(ctx, "Update Success!") } //get infos, err := model.GetAllUser(ctx) if err != nil { logger.Warn(ctx, "Get err %v", err) } else { logger.Debug(ctx, "Get Success %v", len(infos)) } //delete err = info.Delete(gormDb) if err != nil { logger.Error(ctx, "Delete err %v", err) } else { logger.Info(ctx, "Delete Success!") } return nil }
运行的日志如下:
Now listening on: http://localhost:8080 Application started. Press CTRL+C to shut down. {"logModel":"zap","message":{"traceId":"1055f374f34f44da","spanId":1177114561251067098,"content":"Api请求地址/user/rpc","callPath":":/Project/GoProject/tracedemo/logger/logger.go:78","logDate":"2021-05-13T14:22:48.236+08:00", "level":"info"}} ?[36m[INFO]?[0m 2021/05/13 14:22 200 9.8638496s ::1 GET /user/rpc {"logModel":"zap","message":{"traceId":"1055f374f34f44da","spanId":3696752721395975441,"content":"GRPC Send: Resuest By:gavin Response By :172.17.116.1","callPath":":/Project/GoProject/tracedemo/logger/logger.go:82","logDate" :"2021-05-13T14:22:58.098+08:00","level":"debug"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":4789528025624132547,"content":"Api请求地址/user/test","callPath":":/Project/GoProject/tracedemo/logger/logger.go:78","logDate":"2021-05-13T14:24:02.817+08:00" ,"level":"info"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":6843463915167349993,"content":"[gorm] [4ms] [RowsReturned(1)] INSERT INTO `userInfo` (`name`,`hobby`) VALUES ('gavin','demo') ","callPath":":/Project/GoProje ct/tracedemo/logger/logger.go:82","logDate":"2021-05-13T14:24:08.620+08:00","level":"debug"}} ?[36m[INFO]?[0m 2021/05/13 14:24 200 5.8228809s ::1 GET /user/test {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":4789528025624132547,"content":"create Success!","callPath":":/Project/GoProject/tracedemo/logger/logger.go:78","logDate":"2021-05-13T14:24:08.623+08:00","leve l":"info"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":3084142635960672264,"content":"[gorm] [7ms] [RowsReturned(1)] UPDATE `userInfo` SET `name` = 'test', `hobby` = 'demo' WHERE `userInfo`.`id` = 5 ","callPath" :":/Project/GoProject/tracedemo/logger/logger.go:82","logDate":"2021-05-13T14:24:08.631+08:00","level":"debug"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":4789528025624132547,"content":"Update Success!","callPath":":/Project/GoProject/tracedemo/logger/logger.go:82","logDate":"2021-05-13T14:24:08.634+08:00","leve l":"debug"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":4286223975661076968,"content":"[gorm] [1ms] [RowsReturned(1)] ","callPath":":/Project/GoProject/tracedemo/logger/logger.go:82","logDate":"2021-05-13T14:24:0 8.635+08:00","level":"debug"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":4789528025624132547,"content":"Get Success 1","callPath":":/Project/GoProject/tracedemo/logger/logger.go:82","logDate":"2021-05-13T14:24:08.635+08:00","level" :"debug"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":349092158832079207,"content":"[gorm] [1ms] [RowsReturned(1)] DELETE FROM `userInfo` WHERE `userInfo`.`id` = 5 ","callPath":":/Project/GoProject/tracedemo/lo gger/logger.go:82","logDate":"2021-05-13T14:24:08.637+08:00","level":"debug"}} {"logModel":"zap","message":{"traceId":"4277d25d011abbc3","spanId":4789528025624132547,"content":"Delete Success!","callPath":":/Project/GoProject/tracedemo/logger/logger.go:78","logDate":"2021-05-13T14:24:08.639+08:00","leve l":"info"}}
windows技术爱好者