­

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