go语言编程之旅笔记1~2

前言: 最近把这本书从头到尾敲了一遍,记录下其中一些组件的使用方式。

这本书的github原地址为 go-programming-tour-book

第一章: 使用flag和cobra实现简单命令行工具

  1. flag 基本命令行

    点击展开
    //go run main.go   --name=09   go --name=7655
    var nameFlag Name
    flag.Var(&nameFlag, "name", "help info")//声明一个参数 09
    flag.Parse()
    	
    goCmd := flag.NewFlagSet("go", flag.ExitOnError)//一个新的子命令 go
    goCmd.StringVar(&name, "name", "go project", "help info")//子命令的参数 7655
    phpCmd := flag.NewFlagSet("php", flag.ExitOnError)//另一个新的子命令 php
    phpCmd.StringVar(&name, "n", "php project", "help info")
    
    args := flag.Args()
    switch args[0] {
    case "go":
        _ = goCmd.Parse(args[1:])
    case "php":
        _ = phpCmd.Parse(args[1:])
    }
    
  2. cobra 命令行

    go get -u github.com/spf13/cobra //从来不加version的我在grpc那个项目给自己挖了个坑
    
    1. 建立一个root空命令
    var rootCmd = &cobra.Command{
    	Use:   "",
    	Short: "",
    	Long:  "",
    	Run: func(cmd *cobra.Command, args []string) {
    	},
    }
    
    // Execute Execute  在main中调用此函数
    func Execute() error {
    	return rootCmd.Execute()
    }
    
    func init() {
    	rootCmd.AddCommand(wordCmd) // 这三个都是root的子命令,只贴一个word
    	rootCmd.AddCommand(timeCmd) 
    	rootCmd.AddCommand(sqlCmd) // 这个涉及了template和sql的基本使用
    }
    
    2. 建立一个word子命令
    var str string //俩参数
    var mode int8
    var wordCmd = &cobra.Command{
    	Use:   "word",  // 关键字
    	Short: "change word", // short和long都是说明
    	Long:  desc,
    	Run: func(cmd *cobra.Command, args []string) {
            var content string
            ... //具体内容就不贴了
        }
    }
       
    func init() {
        //两个参数  go run main.go word --str=hello  --mode=0
    	wordCmd.Flags().StringVarP(&str, "str", "s", "", "please input word !")
    	wordCmd.Flags().Int8VarP(&mode, "mode", "m", 0, "please intout change mode !")
    }
    
    

第二章: 一个http应用blogservice

  1. gin web框架

    go get -u github.com/gin-gonic/gin
    
    gin初始化的代码片段
    gin.SetMode(global.ServerSetting.RunModel) // DEBUG
    router := routers.NewRouter()
    s := &http.Server{
    	Addr:           ":" + global.ServerSetting.HttpPort,
    	Handler:        router,
    	ReadTimeout:    global.ServerSetting.ReadTimeout,
    	WriteTimeout:   global.ServerSetting.WriteTimeout,
    	MaxHeaderBytes: 1 << 20,
    }
    go func() {
    	if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    		log.Fatalf("s. listenandserve err: %v", err)
    	}
    }()
    
    配置中间件及路由
    func NewRouter() *gin.Engine {
    	r := gin.New()
    	r.Use(middleware.Tracing())
    	r.Use(middleware.AccessLog()) //原始的Logger()和Recovery()已经被替换了
    	r.Use(middleware.Recovery())
    	r.Use(middleware.RateLimiter(methodLimiters))
    	r.Use(middleware.ContextTimeout(60 * time.Second))
    	r.Use(middleware.Translations())
    
    	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    	r.GET("/auth", api.GetAuth)
    	tag := v1.NewTage()
    	article := v1.NewArticle()
    	r.POST("/upload/file", api.UploadFile)
    	r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath))
    	apiv1 := r.Group("/api/v1")
    	{
    		apiv1.Use(middleware.JWT())
    		apiv1.POST("/tags", tag.Create)
    		...
    	}
    	return r
    }
    
    
    中间件写法举例,习惯写aspnetcore的人会觉得很亲切
    func Recovery() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		defer func() {
    			if err := recover(); err != nil {
    				global.Logger.WithCallersFrames().Errorf(c, "panic recover err : %v", err)
    				app.NewResponse(c).ToErrorResponse(errcode.ServiceError)
    				c.Abort()
    			}
    		}()
    		c.Next()
    	}
    }
    
  2. viper 读配置文件

    go get -u github.com/spf13/viper
    go get -u golang.org/x/sys/...   // 下面这俩做热更新用
    go get -u github.com/fsnotify/fsnotify
    
    viper初始化的代码片段
    type Setting struct {
    	vp *viper.Viper
    }
    
    func NewSetting(configs ...string) (*Setting, error) {
    	vp := viper.New()
    	vp.SetConfigName("config") //文件名
    	for _, config := range configs {
    		if config != "" {
    			vp.AddConfigPath(config) //查找路径
    		}
    	}
    	vp.SetConfigType("yaml") //文件类型
    	err := vp.ReadInConfig()
    	if err != nil {
    		return nil, err
    	}
    	s := &Setting{vp}
    	s.WatchSettingChange()
    	return s, nil
    }
    
    // 监视变更
    func (s *Setting) WatchSettingChange() {
    	go func() {
    		s.vp.WatchConfig()
    		s.vp.OnConfigChange(func(in fsnotify.Event) {
    			_ = s.ReloadAllSection()
    		})
    	}()
    }
    
    // 保存各个sections 主要是为了做热更新而存在的
    var sections = make(map[string]interface{})
    
    // 读取单个section`k`的数据至对象`v`中
    func (s *Setting) ReadSection(k string, v interface{}) error {
    	err := s.vp.UnmarshalKey(k, v)
    	if err != nil {
    		return err
    	}
    	if _, ok := sections[k]; !ok {
    		sections[k] = v
    	}
    	return nil
    }
    
    func (s *Setting) ReloadAllSection() error {
    	for k, v := range sections {
    		err := s.ReadSection(k, v)
    		if err != nil {
    			return err
    		}
    	}
    	return nil
    }
    
    调用方式 比如main.go的init()中
    func init() {
        err := setupSetting()
        ...
    }
    
    func setupSetting() error {
    	setting, err := setting.NewSetting(strings.Split(config, ",")...)
    	if err != nil {
    		return err
    	}
    	err = setting.ReadSection("Server", &global.ServerSetting)
    	if err != nil {
    		return err
    	}
    	...
    	return nil
    }
    
  3. gorm 看名字就知道是orm了

    go get -u github.com/jinzhu/gorm
    
    gorm初始化的代码片段
    // mysql的
    func NewDBEngine(dbsetting *setting.DatabaseSettingS) (*gorm.DB, error) {
    	db, err := gorm.Open(dbsetting.DBType,
    		fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local",
    			dbsetting.Username,
    			dbsetting.Password,
    			dbsetting.Host,
    			dbsetting.DBName,
    			dbsetting.Charset,
    			dbsetting.ParseTime,
    		))
    	if err != nil {
    		return nil, err
    	}
    	if global.ServerSetting.RunModel == "debug" {
    		db.LogMode(true)
    	}
    	db.SingularTable(true)
    	// 自动迁移
    	if db.HasTable(&Tag{}) {
    		db.AutoMigrate(&Tag{})
    	} else {
    		db.CreateTable(&Tag{})
    	}
    	...
        
        // 增删改的回调 用于处理公共字段
    	db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
    	db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
    	db.Callback().Delete().Replace("gorm:delete", deleteCallback)
    
    	db.DB().SetMaxIdleConns(dbsetting.MaxIdleConns)
    	db.DB().SetMaxOpenConns(dbsetting.MaxOpenConns)
    	// 这个是tracing相关 来源自github.com/eddycjy/opentracing-gorm
    	otgorm.AddGormCallbacks(db)
    	return db, nil
    }
    
    回调处理公共字段 有些麻烦
    func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
    	if _, ok := scope.Get("gorm:update_column"); !ok {
    		_ = scope.SetColumn("ModifiedOn", time.Now().Unix())
    	}
    }
    
    func updateTimeStampForCreateCallback(scope *gorm.Scope) {
    	if !scope.HasError() {
    		nowTime := time.Now().Unix()
    		if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
    			if createTimeField.IsBlank {
    				_ = createTimeField.Set(nowTime)
    			}
    		}
    		if modifyTimeField, ok := scope.FieldByName("ModifiedOn"); ok {
    			if modifyTimeField.IsBlank {
    				_ = modifyTimeField.Set(nowTime)
    			}
    		}
    	}
    }
    
    func deleteCallback(scope *gorm.Scope) {
    	if !scope.HasError() {
    		var extraoption string
    		if str, ok := scope.Get("gorm:delete_option"); ok {
    			extraoption = fmt.Sprint(str)
    		}
    		deleteOnField, hasDeletedonField := scope.FieldByName("DeletedOn")
    		isDelFiled, hasIsDelField := scope.FieldByName("IsDel")
    		if !scope.Search.Unscoped && hasDeletedonField && hasIsDelField {
    			now := time.Now().Unix()
    			scope.Raw(fmt.Sprintf(
    				"update %v set %v=%v ,%v=%v%v%v",
    				scope.QuotedTableName(),
    				scope.Quote(deleteOnField.DBName),
    				scope.AddToVars(now),
    				scope.Quote(isDelFiled.DBName),
    				scope.AddToVars(1),
    				addExtraSpaceIfExist(scope.CombinedConditionSql()),
    				addExtraSpaceIfExist(extraoption),
    			)).Exec()
    		} else {
    			scope.Raw(fmt.Sprintf(
    				"delete from %v%v%v",
    				scope.QuotedTableName(),
    				addExtraSpaceIfExist(scope.CombinedConditionSql()),
    				addExtraSpaceIfExist(extraoption),
    			)).Exec()
    		}
    	}
    }
    
    func addExtraSpaceIfExist(str string) string {
    	if str != "" {
    		return " " + str
    	}
    	return ""
    }
    
    curd来一套
    func (t Tag) Count(db *gorm.DB) (int, error) {
    	var count int
    	if t.Name != "" {
    		db = db.Where("name = ?", t.Name)
    	}
    	db = db.Where("state = ?", t.State)
    	if err := db.Model(&t).Where("is_del = ?", 0).Count(&count).Error; err != nil {
    		return 0, err
    	}
    	return count, nil
    }
    
    func (t Tag) Get(db *gorm.DB) (Tag, error) {
    	var tag Tag
    	db = db.Where("id = ? and is_del = ? and state = ?", t.ID, 0, t.State)
    	if err := db.Model(&t).Where("is_del = ?", 0).Find(&tag).Error; err != nil {
    		return tag, err
    	}
    	return tag, nil
    }
    
    func (t Tag) List(db *gorm.DB, pageoffset, pagesize int) ([]*Tag, error) {
    	var tags []*Tag
    	var err error
    	if pageoffset >= 0 && pagesize > 0 {
    		db = db.Offset(pageoffset).Limit(pagesize)
    	}
    	if t.Name != "" {
    		db = db.Where("name = ?", t.Name)
    	}
    	db = db.Where("state = ?", t.State)
    	if err = db.Where("is_del = ?", 0).Find(&tags).Error; err != nil {
    		return nil, err
    	}
    	return tags, nil
    }
    
    func (t Tag) Create(db *gorm.DB) error {
    	return db.Create(&t).Error
    }
    
    func (t Tag) Update(db *gorm.DB, value interface{}) error {
    	return db.Model(&t).Where("id = ? and is_del = ?", t.ID, t.IsDel).Updates(value).Error
    }
    
    func (t Tag) Delete(db *gorm.DB) error {
    	return db.Where("id = ? and is_del = ?", t.Model.ID, 0).Delete(&t).Error
    }
    

    使用orm的目的在于完全屏蔽数据库细节,解放生产力。但书中举例的很多操是在拼sql及硬编码字段名,尤其是where条件里面和用于处理公共字段的回调方法,这完全不是orm的理念。是这本书的作者使用方式不对么?

  4. lumberjack 写日志的

    go get -u gopkg.in/natefinch/lumberjack.v2
    
    作者对log做了大量的封装,使用了很多runtime的方法来获取数据,具体还是看源码吧,我只记下这个组件最基本的用法
    func setupLogger() error {
    	global.Logger = logger.NewLogger(
    		&lumberjack.Logger{
    			Filename:  global.AppSetting.LogSavaPath + "/" + global.AppSetting.LogFileName + global.AppSetting.LogFileExt,
    			MaxSize:   600,
    			MaxAge:    10,
    			LocalTime: true,
    		},
    		"",
    		log.LstdFlags,
    	).WithCaller(2)
    	return nil
    }
    
    type Level int8
    type Fields map[string]interface{}  
    
    type Logger struct {
    	newLogger *log.Logger
    	ctx       context.Context
    	level     Level
    	fields    Fields
    	callers   []string
    }
    
    func NewLogger(w io.Writer, prefix string, flag int) *Logger {
    	l := log.New(w, prefix, flag)
    	return &Logger{newLogger: l}
    }
    
    func (l *Logger) WithCaller(skip int) *Logger {
    	ll := l.clone()
    	pc, file, line, ok := runtime.Caller(skip)
    	if ok {
    		f := runtime.FuncForPC(pc)
    		ll.callers = []string{fmt.Sprintf("%s: %d %s", file, line, f.Name())}
    	}
    	return ll
    }
    
  5. swagger

    go get -u github.com/swaggo/gin-swagger 
    go get -u github.com/swaggo/swag 
    go get -u github.com/alecthomas/template
    swag -v
    
    手写注释很麻烦 写完后swag init就能生成,路由在前面的gin环节已经配置过了
    // @Summary get tags
    // @Produce json
    // @Param name query string false "tag name" maxlength(100)
    // @Param state query int false "state" Enums(0,1) defaulrt(1)
    // @Param page query int false "page"
    // @Param page_size query int false "page_size"
    // @Success 200 {object} model.TagSwagger "success"
    // @Failure 400 {object} errcode.Error "error"
    // @Failure 500 {object} errcode.Error "error"
    // @Router /api/v1/tags [get]
    func (a Tag) List(c *gin.Context) {
        ...
    }
    
  6. validator 接口验证器

    go get -u github.com/go-playground/validator/v10 
    go get -u github.com/go-playground/locales //多语言包
    go get -u github.com/go-playground/universal-translator 翻译器
    

    gin中默认的验证就是用的这个组件,现在对其作一定的定制,并添加到中间件中,参照gin环节

    先写个中间件,主要做国际化和验证器注册
    func Translations() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		uni := ut.New(en.New(), zh.New(), zh_Hant_TW.New())
    		locale := c.GetHeader("locale")
    		trans, _ := uni.GetTranslator(locale)
    		v, ok := binding.Validator.Engine().(*Validator.Validate)
    		if ok {
    			switch locale {
    			case "zh":
    				_ = zh_translations.RegisterDefaultTranslations(v, trans)
    				break
    			case "en":
    				_ = en_translations.RegisterDefaultTranslations(v, trans)
    				break
    			default:
    				_ = zh_translations.RegisterDefaultTranslations(v, trans)
    				break
    			}
    			c.Set("trans", trans)
    		}
    		c.Next()
    	}
    }
    
    
    再对gin的ShouldBind封装个,用中间件中的"trans"对错误国际化
    func BindAndValid(c *gin.Context, v interface{}) (bool, ValidErrors) {
    	var errs ValidErrors
    	err := c.ShouldBind(v)
    	if err != nil {
    		t := c.Value("trans")
    		trans, _ := t.(ut.Translator)
    		verrs, ok := err.(val.ValidationErrors)
    		if !ok {
    			return false, nil
    		}
    		for key, value := range verrs.Translate(trans) {
    			errs = append(errs, &ValidError{
    				Key:     key,
    				Message: value,
    			})
    		}
    		return false, errs
    	}
    	return true, nil
    }
    
    

    下面是使用方式

    制定对象规则
    type UpdateTagRequest struct {
    	Id         uint32 `form:"id" binding:"required,gte=1"`
    	Name       string `form:"name" binding:"required,min=3,max=100"`
    	ModifiedBy string `form:"modified_by" binding:"required,min=3,max=100"`
    	State      uint8  `form:"state,default=1" binding:"oneof=0 1"`
    }
    
    具体的方法调用
    func (a Tag) Update(c *gin.Context) {
    	params := service.UpdateTagRequest{Id: convert.StrTo(c.Param("id")).MustUInt32()}
    	response := app.NewResponse(c)
    	valid, errs := app.BindAndValid(c, &params)
    	if !valid {
    		// global.Logger.Errorf("app bindandvalid err: %v", errs)
    		response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
    		return
    	}
    	...
    }
    
  7. jwt

    go get -u github.com/dgrijalva/jwt-go 
    
    生成一个jwt token
    func GenerateToken(appKey, appSecret string) (string, error) {
    	nowTime := time.Now()
    	expireTime := nowTime.Add(global.JWTSetting.Expire)
    	claims := Claims{
    		AppKey:    util.EncodeMD5(appKey),
    		AppSecret: util.EncodeMD5(appSecret),
    		StandardClaims: jwt.StandardClaims{
    			ExpiresAt: expireTime.Unix(),
    			Issuer:    global.JWTSetting.Issuer,
    		},
    	}
    
    	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    	token, err := tokenClaims.SignedString([]byte(GetJWTSecret()))
    	return token, err
    }
    
    解析一个jwt token
    func ParseToken(token string) (*Claims, error) {
    	tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    		return []byte(GetJWTSecret()), nil
    	})
    	if tokenClaims != nil {
    		if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
    			return claims, nil
    		}
    	}
    	return nil, err
    }
    
    
    获取token的路由方法我就跳过了
    一个jwt中间件
    func JWT() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		var (
    			token string
    			ecode = errcode.Success
    		)
    		if s, exist := c.GetQuery("token"); exist {
    			token = s
    		} else {
    			token = c.GetHeader("token")
    		}
    		if token == "" {
    			ecode = errcode.InvalidParams
    		} else {
    			_, err := app.ParseToken(token)
    			if err != nil {
    				switch err.(*jwt.ValidationError).Errors {
    				case jwt.ValidationErrorExpired:
    					ecode = errcode.UnauthorizedTokenTimeout
    				default:
    					ecode = errcode.UnauthorizedTokenError
    				}
    			}
    		}
    		if ecode != errcode.Success {
    			response := app.NewResponse(c)
    			response.ToErrorResponse(ecode)
    			c.Abort()
    			return
    		}
    		c.Next()
    	}
    }
    
    
    
  8. ratelimit 限流

    go get -u github.com/juju/ratelimit 
    
    封装一个基本的限流器
    type LimiterIface interface {
    	Key(c *gin.Context) string
    	GetBucket(key string) (*ratelimit.Bucket, bool)
    	AddBuckets(rules ...LimiterBucketRule) LimiterIface
    }
    
    type LimiterBucketRule struct {
    	Key          string
    	FillInterval time.Duration
    	Capacity     int64
    	Quantum      int64
    }
    type Limiter struct {
    	LimiterBuckets map[string]*ratelimit.Bucket
    }
    
    
    再次封装成一个对特定路由的限流器
    type MethodLimiter struct {
    	*Limiter
    }
    
    func NewMethodLimiter() LimiterIface {
    	return MethodLimiter{
    		Limiter: &Limiter{LimiterBuckets: make(map[string]*ratelimit.Bucket)},
    	}
    }
    
    func (l MethodLimiter) Key(c *gin.Context) string {
    	uri := c.Request.RequestURI
    	index := strings.Index(uri, "?")
    	if index == -1 {
    		return uri
    	}
    	return uri[:index]
    }
    
    func (l MethodLimiter) GetBucket(key string) (*ratelimit.Bucket, bool) {
    	bucket, ok := l.LimiterBuckets[key]
    	return bucket, ok
    }
    
    func (l MethodLimiter) AddBuckets(rules ...LimiterBucketRule) LimiterIface {
    	for _, rule := range rules {
    		if _, ok := l.LimiterBuckets[rule.Key]; !ok {
    			l.LimiterBuckets[rule.Key] = ratelimit.NewBucketWithQuantum(rule.FillInterval, rule.Capacity, rule.Quantum)
    		}
    	}
    	return l
    }
    
    gin的中间件
    var methodLimiters = limiter.NewMethodLimiter().AddBuckets(limiter.LimiterBucketRule{
    	Key:          "/auth",
    	FillInterval: time.Second,
    	Capacity:     10,
    	Quantum:      10,
    })
    
    r.Use(middleware.RateLimiter(methodLimiters))
    
    func RateLimiter(l limiter.LimiterIface) gin.HandlerFunc {
    	return func(c *gin.Context) {
    		key := l.Key(c)
    		if bucket, ok := l.GetBucket(key); ok {
    			count := bucket.TakeAvailable(1)
    			if count == 0 {
    				response := app.NewResponse(c)
    				response.ToErrorResponse(errcode.TooManyRequests)
    				c.Abort()
    				return
    			}
    		}
    		c.Next()
    	}
    }
    
  9. opentracing and jaeger

    go get -u github.com/opentracing/opentracing-go
    go get -u github.com/uber/jaeger-client-go
    go get -u github.com/eddycjy/opentracing-gorm  //gorm的trace在gorm那里讲过了就一行code
    

    jaeger安装就不写了,直接docker

    写一个tracer,opentracing.SetGlobalTracer(tracer) 这句我当时忘写了,直到做grpc那章追踪连不起来才发现这个错误
    func NewJaegerTracer(servicename, agentHostPort string) (opentracing.Tracer, io.Closer, error) {
    	cfg := &config.Configuration{
    		ServiceName: servicename,
    		Sampler: &config.SamplerConfig{
    			Type:  "const",
    			Param: 1,
    		},
    		Reporter: &config.ReporterConfig{
    			LogSpans:            true,
    			BufferFlushInterval: 1 * time.Second,
    			LocalAgentHostPort:  agentHostPort,
    		},
    	}
    	tracer, closer, err := cfg.NewTracer()
    	if err != nil {
    		return nil, nil, err
    	}
    	opentracing.SetGlobalTracer(tracer)
    	return tracer, closer, nil
    }
    
    // 在main.go里面初始化它
    func setupTracing() error {
    	tacer, _, err := tracer.NewJaegerTracer("blog_service", "127.0.0.1:6831")
    	if err != nil {
    		return err
    	}
    	global.Tracer = tacer
    	return nil
    }
    
    写一个中间件,这段开始也写错,也是到grpc才发现的
    func Tracing() gin.HandlerFunc {
    	return func(c *gin.Context) {
    
    		var newCtx context.Context
    		var span opentracing.Span
    		spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
    		if err != nil {
    			span, newCtx = opentracing.StartSpanFromContextWithTracer(c.Request.Context(), global.Tracer, c.Request.URL.Path)
    		} else {
    			span, newCtx = opentracing.StartSpanFromContextWithTracer(
    				c.Request.Context(),
    				global.Tracer,
    				c.Request.URL.Path,
    				opentracing.ChildOf(spanCtx),
    				opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
    			)
    		}
    		defer span.Finish()
    
    		var tracid string
    		var spanid string
    		var spanContext = span.Context()
    		switch spanContext.(type) {
    		case jaeger.SpanContext:
    			tracid = spanContext.(jaeger.SpanContext).TraceID().String()
    			spanid = spanContext.(jaeger.SpanContext).SpanID().String()
    		}
    		c.Set("X-Trace-ID", tracid)
    		c.Set("X-Span-ID", spanid)
    		c.Request = c.Request.WithContext(newCtx)
    		c.Next()
    	}
    }
    
    对原有的log做改进,要记录下X-Trace-ID X-Span-ID
    func (l *Logger) WithTrace() *Logger {
    	ginCtx, ok := l.ctx.(*gin.Context)
    	if ok {
    		return l.WithFields(Fields{
    			"trace_id": ginCtx.MustGet("X-Trace-ID"),
    			"span_id":  ginCtx.MustGet("X-Span-ID"),
    		})
    	}
    	return l
    } 
    
    // 原有的所有方法都要再加上.WithTrace()
    func (l *Logger) Panicf(ctx context.Context, format string, v ...interface{}) {
    	l.WithLevel(LevelPanic).WithContext(ctx).WithTrace().Output(fmt.Sprintf(format, v...))
    }
    

posted on 2020-10-29 02:42  Alternatives  阅读(342)  评论(0编辑  收藏  举报

导航