coding時,你是否有過下列問題(我的親身經歷...)
現在我們來思考一下,我們需要紀錄什麼東西。
在debug時,把阿哩阿紮的變數全部給我print出來,這個好處理,那麼產品上線呢?
一般系統出問題了,我們會想要知道 誰 在 什麼時候 做了什麼?而我們的程式收到了什麼 而後 處理了什麼?
所以在使用者連到我們系統時,我們需要紀錄request的method, URL path, URL query, IP, user-agent, 收到時間,(也許還可以post body) 以及我們回應的 status, 處理時間,訊息
確定好要紀錄的資訊後,現在來思考如何定義log level
gin本身就就有提供log,不過並沒有紀錄這麼多東西,我們可以利用middleware自己客製化一個logger。
這裡使用的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,
}
}
解釋:
基本的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)
}
解釋:
把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