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)      }  }