go web: 2 封裝日誌包log
- 2019 年 11 月 21 日
- 筆記
在web項目中,記日誌是非常重要的。所以,我做的第一件事,就是向log包動手。 和Python相比,log包功能上遜色不少,但它給我們提供了基礎的構架,讓我們能自己稍微封裝下。
需求
對日誌包我的要求很低,只要滿足: 1. 提供Error, Info方法即可 2. 日誌按天分割,即每隔一天,把昨天的日誌保存為 logname.20170823這樣的文件
代碼
在原來的基礎上,我們在src中創建文件夾logger,在裏面創建文件logger.go 現在文件結構如下:
src--| handlers--| test--| test.go logger--| logger.go | main.go
這個文件代碼有點長,所以放附錄了。 要使用,只需要在main.go里調用:
logger.InitLogging("8080", logger.DEBUG) logger.Error("%s %s", "hi", "my boy")
然後,在bin文件的同級,手工創建logs文件夾。運行程序,日誌功能就開始執行了。 測試了一下效率,在mac pro上。10萬行日誌大概400毫秒。湊合著用還行。
附錄logger.go代碼
// Package logger 是系統日誌的封裝,主要在之上封裝了Error,Info兩個函數。並提供了跨日期 // 自動分割日誌文件的功能。 // 可以在InitLogging 後直接使用logger.Error, logger.Info操作默認的日誌對象。 // 也可以用logger.New 創建一個自己的日誌對象。 package logger import ( "bytes" "fmt" "io" "log" "os" "path/filepath" "runtime" "strconv" "time" ) //logging 是一個默認的日誌對象,提供全局的Error, Info函數供使用,必須調用InitLogging //函數進行初始化 var logging *Logger var DEBUG = 0 var INFO = 3 var ERROR = 5 //InitLogging 初始化默認的日誌對象,初始化後,就能使用Error,Info函數記錄日誌 func InitLogging(inputfilename string, level int) { logging = New(inputfilename, true, false, DEBUG, 3) } //Error 默認日誌對象方法,記錄一條錯誤日誌,需要先初始化 func Error(format string, v ...interface{}) { logging.Error(format, v...) } //Errorln 默認日誌對象方法,記錄一條消息日誌,需要先初始化 func Errorln(args ...interface{}) { logging.Errorln(args...) } //Info 默認日誌對象方法,記錄一條消息日誌,需要先初始化 func Info(format string, v ...interface{}) { logging.Info(format, v...) } //Infoln 默認日誌對象方法,記錄一條消息日誌,需要先初始化 func Infoln(args ...interface{}) { logging.Infoln(args...) } //Debug 默認日誌對象方法,記錄一條消息日誌,需要先初始化 func Debug(format string, v ...interface{}) { logging.Debug(format, v...) } //Debugln 默認日誌對象方法,記錄一條調試日誌,需要先初始化 func Debugln(args ...interface{}) { logging.Debugln(args...) } type Logger struct { level int // debug 0 info 3 err 5 innerLogger *log.Logger curFile *os.File todaydate string filename string runtimeCaller int logFilePath bool logFunc bool msgQueue chan string // 所有的日誌先到這來 closed bool } //New 創建一個自己的日誌對象。 // filename:在logs文件夾下創建的文件名 // logFilePath: 日誌中記錄文件路徑 // logFunc: 日誌中記錄調用函數 // level: 打印等級。DEBUG, INFO, ERROR // runtimeCaller: 文件路徑深度,設定適當的值,否則文件路徑不正確 func New(filename string, logFilePath bool, logFunc bool, level int, runtimeCaller int) *Logger { // result := newLogger(logFile, flag) result := new(Logger) result.msgQueue = make(chan string, 1000) result.closed = false var multi io.Writer if filename != "" { dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) logFile, err := os.OpenFile(dir+"/logs/"+filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { fmt.Println(err.Error()) } result.curFile = logFile fmt.Println("newLogger use MultiWriter") multi = io.MultiWriter(logFile, os.Stdout) } else { result.curFile = nil fmt.Println("newLogger use stdout") multi = os.Stdout } result.innerLogger = log.New(multi, "", 0) result.filename = filename result.runtimeCaller = runtimeCaller result.logFilePath = logFilePath result.logFunc = logFunc result.level = level result.todaydate = time.Now().Format("2006-01-02") // 啟動日誌切換 go result.logworker() return result } // Close 關閉這一個日誌對象 func (logobj *Logger) Close() error { logobj.closed = true return nil } func (logobj *Logger) getFormat(prefix, format string) string { var buf bytes.Buffer // 增加時間 buf.WriteString(time.Now().Format("2006-01-02 15:04:05 ")) buf.WriteString(prefix) // 增加文件和行號 funcName, file, line, ok := runtime.Caller(logobj.runtimeCaller) if ok { if logobj.logFilePath { buf.WriteString(filepath.Base(file)) buf.WriteString(":") buf.WriteString(strconv.Itoa(line)) buf.WriteString(" ") } if logobj.logFunc { buf.WriteString(runtime.FuncForPC(funcName).Name()) buf.WriteString(" ") } buf.WriteString(format) format = buf.String() } return format } //Error 記錄一條錯誤日誌 func (logobj *Logger) Error(format string, v ...interface{}) { if logging.level > 5 { return } format = logobj.getFormat("ERROR ", format) logobj.msgQueue <- fmt.Sprintf(format, v...) } //Errorln 打印一行錯誤日誌 func (logobj *Logger) Errorln(args ...interface{}) { if logging.level > 5 { return } prefix := logobj.getFormat("ERROR ", "") logobj.msgQueue <- fmt.Sprintln(append([]interface{}{prefix}, args...)...) } //Info 記錄一條消息日誌 func (logobj *Logger) Info(format string, v ...interface{}) { if logging.level > 3 { return } format = logobj.getFormat("INFO ", format) logobj.msgQueue <- fmt.Sprintf(format, v...) } //Infoln 打印一行消息日誌 func (logobj *Logger) Infoln(args ...interface{}) { if logging.level > 3 { return } prefix := logobj.getFormat("INFO ", "") logobj.msgQueue <- fmt.Sprintln(append([]interface{}{prefix}, args...)...) } //Debug 記錄一條消息日誌 func (logobj *Logger) Debug(format string, v ...interface{}) { if logging.level > 0 { return } format = logobj.getFormat("DEBUG ", format) logobj.msgQueue <- fmt.Sprintf(format, v...) } //Debugln 打印一行調試日誌 func (logobj *Logger) Debugln(args ...interface{}) { if logging.level > 0 { return } prefix := logobj.getFormat("DEBUG ", "") logobj.msgQueue <- fmt.Sprintln(append([]interface{}{prefix}, args...)...) } func (logobj *Logger) logworker() { for logobj.closed == false { msg := <-logobj.msgQueue logobj.innerLogger.Println(msg) //跨日改時間,後台啟動 nowDate := time.Now().Format("2006-01-02") if nowDate != logobj.todaydate { logobj.Debug("doRotate run %v %v", nowDate, logging.todaydate) logobj.doRotate() } } } func (logobj *Logger) doRotate() { // 日誌按天切換文件,日誌對象記錄了程序啟動時的時間,噹噹前時間和程序啟動的時間不一致 // 則會啟動到這個函數來改變文件 // 首先關閉文件句柄,把當前日誌改名為昨天,再創建新的文件句柄,將這個文件句柄賦值給log對象 // 最後嘗試刪除5天前的日誌 fmt.Println("doRotate run") defer func() { rec := recover() if rec != nil { fmt.Printf("doRotate %v", rec) } }() if logobj.curFile == nil { fmt.Println("doRotate curfile nil, return") return } dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) prefile := logobj.curFile _, err := prefile.Stat() if err == nil { filePath := dir + "/logs/" + logobj.filename err := prefile.Close() fmt.Printf("doRotate close err %v", err) nowTime := time.Now() time1dAgo := nowTime.Add(-1 * time.Hour * 24) err = os.Rename(filePath, filePath+"."+time1dAgo.Format("2006-01-02")) fmt.Printf("doRotate rename err %v", err) } if logobj.filename != "" { nextfile, err := os.OpenFile(dir+"/logs/"+logobj.filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { fmt.Println(err.Error()) } logobj.curFile = nextfile fmt.Println("newLogger use MultiWriter") multi := io.MultiWriter(nextfile, os.Stdout) logobj.innerLogger.SetOutput(multi) } fmt.Println("doRotate ending") // 更新標記,這個標記決定是否會啟動文件切換 nowDate := time.Now().Format("2006-01-02") logobj.todaydate = nowDate logobj.deleteHistory() } func (logobj *Logger) deleteHistory() { // 嘗試刪除5天前的日誌 fmt.Println("deleteHistory run") nowTime := time.Now() time5dAgo := nowTime.Add(-1 * time.Hour * 24 * 5) dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) filePath := dir + "/logs/" + logobj.filename + "." + time5dAgo.Format("2006-01-02") _, err := os.Stat(filePath) if err == nil { os.Remove(filePath) } }