iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 6
0

log

coding時,你是否有過下列問題(我的親身經歷...)

  • debug的時候print刷起來快又方便,但是之後要清理print好麻煩
  • 完成的程式突然error但你不知道使用者輸入了什麼,無從debug
    上述問題,就要靠log來搞定了,初步認識可以看這篇

現在我們來思考一下,我們需要紀錄什麼東西。

在debug時,把阿哩阿紮的變數全部給我print出來,這個好處理,那麼產品上線呢?
一般系統出問題了,我們會想要知道 什麼時候 做了什麼?而我們的程式收到了什麼 而後 處理了什麼

所以在使用者連到我們系統時,我們需要紀錄request的method, URL path, URL query, IP, user-agent, 收到時間,(也許還可以post body) 以及我們回應的 status, 處理時間,訊息

確定好要紀錄的資訊後,現在來思考如何定義log level

  • DEBUG:debug時隨意呼叫
  • INFO: 在重要的位置呼叫,紀錄重要資訊與程式處理流程
  • WARN: 使用者錯誤的輸入,或我們已知能處理的error
  • ERROR: 未知的錯誤

gin本身就就有提供log,不過並沒有紀錄這麼多東西,我們可以利用middleware自己客製化一個logger。

code

這裡使用的log工具是zap

zap是uber開發的log工具,最大的賣點是效率,主要的原理是減少reflect來做型態轉換與斷言,所以必須特別宣告型態才能讓打印log資訊,如果是自訂的型態,可以使用Any來使用reflect(但會對效能造成影響),也可以自己實做MarshalLogObject來處理自訂型態。

建立log package,創建logger.go,寫入

package log

import (
	appErr "app/apperr"
	"app/setting"
	"app/util/debug"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

const (
	TaskOK      = "OK"    // success
	TaskFail    = "Fail"  // fail but not error (warn)
	TaskError   = "Error" // panic error occure
	TaskOnGoing = "OnGoing"
	TaskUnknow  = "Unknow"
)

var Logger *zap.Logger

// config logger here
func newLogger() (logger *zap.Logger) {
	if setting.Config.Servers["main"].RunMode == "release" {
		cfg := zap.NewProductionConfig()
		cfg.OutputPaths = []string{
			"./log/creater.log",
		}
		logger, _ = cfg.Build()
	} else {
		logger, _ = zap.NewDevelopment()
	}
	return logger
}

func init() {
	Logger = newLogger()
}

// fuction data record for logger
type FuncDataStruct struct {
	debug.FuncDataStruct
}

// implment zap json-encode for FuncData
func (f *FuncDataStruct) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	enc.AddString("locate", f.Loacate)
	enc.AddString("func", f.Function)
	return nil
}

type FuncDataStructs []*debug.FuncDataStruct

// implment zap json-encode for []FuncData
func (f FuncDataStructs) MarshalLogArray(enc zapcore.ArrayEncoder) error {
	for _, funcData := range f {
		if err := enc.AppendObject(&FuncDataStruct{*funcData}); err != nil {
			return err
		}
	}
	return nil
}

type LogErrorMeta struct {
	appErr.ErrorMetaStruct
}

// implement zap json-encode for ErrorMetaStruct
func (f *LogErrorMeta) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	if f.Msg != "" {
		enc.AddString("Msg", f.Msg)
	}
	if f.Error != nil {
		enc.AddString("Error", f.Error.Error())
	}
	if len(f.Stack) != 0 {
		// implement array marshal
		if err := enc.AddArray("CallStack", FuncDataStructs(f.Stack)); err != nil {
			return err
		}
	}
	return nil
}

type LogErrorData struct {
	appErr.ErrorDataStruct
}

// implment zap json-encode for ErrorDataStruct
func (f *LogErrorData) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	enc.AddInt("code", f.Code)
	enc.AddString("msg", f.Msg)
	return nil
}

// recored msg in function for logger
type LogDataStruct struct {
	TaskStatus string        `zap:"taskStatus"`
	ErrorData  *LogErrorData `zap:"ErrorData, omitempty"`
	ErrorMeta  *LogErrorMeta `zap:"ErrorMeta, omitempty"`
}

// implment zap json-encode for  InfoMsg
func (f *LogDataStruct) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	enc.AddString("taskStatus", f.TaskStatus)
	if f.ErrorData != nil {
		if err := enc.AddObject("ErrorData", f.ErrorData); err != nil {
			return err
		}
	}
	if f.ErrorMeta != nil {
		if err := enc.AddObject("ErrorMeta", f.ErrorMeta); err != nil {
			return err
		}
	}
	return nil
}

// new a logger data struct
func NewLogData(taskStatus string, errorData *LogErrorData, errorMeta *LogErrorMeta) *LogDataStruct {
	return &LogDataStruct{
		TaskStatus: taskStatus,
		ErrorData:  errorData,
		ErrorMeta:  errorMeta,
	}
}

解釋:

  • MarshalLogObject自訂的類型需要實做的function,具體可以參考zapcore MapObjectEncoder
  • MarshalLogArray是自訂的slice或array型態,基本上跟MarshalLogObject一樣
  • LogErrorMeta與LogErrorData是對ErrorMetaStruct與ErrorDataStruct的擴展,另外ErrorMetaStruct裡面有[]*debug.FuncDataStruct,所以我們也需要處理這部份

基本的log設定完成,現在來寫函式來使用,創建logging.go,寫入

package log

import (
	appErr "app/apperr"
	"app/util/debug"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

const LogCtxKey = "LogData"

// set logger data
func LogHandle(c *gin.Context, logData *LogDataStruct) {
	c.Set(LogCtxKey, logData)
}

解釋:

  • c.Set,將一個map[string]interface{}存在context之中,可以用c.Get取出

總結

把log基礎配置寫完,明天來寫DEBUG等函式與middleware

目前的工作環境

.
├── app
│   ├── apperr
│   │   ├── error.go
│   │   └── handle.go
│   ├── config
│   │   └── app
│   │       ├── app.yaml
│   │       └── error.yaml
│   ├── go.mod
│   ├── go.sum
│   ├── log
│   │   ├── logger.go
│   │   └── logging.go
│   ├── main.go
│   ├── middleware
│   │   └── error.go
│   ├── router
│   │   ├── host_switch.go
│   │   └── main.go
│   ├── serve
│   │   ├── main.go
│   │   └── main_test.go
│   ├── setting
│   │   └── setting.go
│   └── util
│       └── debug
│           ├── stack.go
│           └── stack_test.go
└── database

上一篇
Day5 錯誤處理(下)
下一篇
Day7 Log處理(下)
系列文
從coding到上線-打造自己的blog系統30

尚未有邦友留言

立即登入留言