美文网首页
Go语言实现简单的日志记录

Go语言实现简单的日志记录

作者: pyihe | 来源:发表于2020-06-06 14:21 被阅读0次

    前言

    一个完整的日志库不仅仅涵盖日志记录功能,还要包括日志level、行号、文件切分,甚至包含统计与分析等,Go语言中的日志库也是很多,其中知名度比较高的有:

    库名 star
    logrus 14940
    zap 9827
    zerolog 3386
    seelog 1464

    备注:star数获取时间为2020-05-28 23:26:00

    一千个人有一千个需求,不管是哪个开源日志库,用着总有不顺手的时候,没关系,那就自己实现一个吧,相信自己,来,就让咱们先从实现简单的日志记录功能开始吧~「手动狗头」

    思路

    1. 功能设计

      根据自己的需求,我想要的日志记录功能有:

      • 按照level输出日志
      • 能够同时输出到文件和控制台
      • 控制台能够根据level将内容输出为不同颜色
      • 日志文件根据大小进行分割
      • 输出行号
    2. API设计

      一般来说,根据level不同,设计有不同的API,level大概可以分为: trace、warn、error、fatal,
      也就是说对外的API可以概括为: T(...inter), W(...), E(...), F(...)

      type logger interface{
          T(format string, v ...interface{})
          W(format string, v ...interface{})
          E(format string, v ...interface{})
          F(format string, v ...interface{})  
      }
      
    3. 结构设计

      根据需求,日志记录器logger的结构需要包含writers、文件名、文件保存路径、文件分割大小
      完整结构设计如下:

      type myLog struct {
          sync.Once 
          sync.Mutex                     //用于outs并发访问
          outs     map[logType]io.Writer //writer集合
          file     *os.File              //文件句柄
          fileName string                //日志名
          dir      string                //日志存放路径
          size     int64                 //单个日志文件的大小限制
      }
      
    4. 关键方法实现

      • 日志文件大小检测
      func (m *myLog) checkLogSize() {
          if m.file == nil {
              return
          }
          m.Lock()
          defer m.Unlock() //此处必须加锁,否则会出现并发问题
          fileInfo, err := m.file.Stat()
          if err != nil {
              panic(err)
          }
          if m.size > fileInfo.Size() {
              return
          }
          //需要分割,重新打开一个新的文件句柄替换老的,并关闭老的文件句柄,
          newName := path.Join(m.dir, time.Now().Format("2006_01_02_15:04:03")+".log")
          name := path.Join(m.dir, m.fileName)
          
          err = os.Rename(name, newName)
          if err != nil {
              panic(err)
          }
          
          file, err := os.OpenFile(name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
          if err != nil {
              panic(err)
          }
             
          m.file.Close()
          m.file = file
          m.outs[logTypeFile] = file
          return
      } 
      
      • 控制台带颜色输出内容
      func setColor(msg string, text int) string {
          return fmt.Sprintf("%c[%dm%s%c[0m", 0x1B, text, msg, 0x1B)
      }
      
      • 获取行号
      func shortFileName(file string) string {
          short := file
          for i := len(file) - 1; i > 0; i-- {
              if file[i] == '/' {
                  short = file[i+1:]
                  break
              }
          }
          return short
      }
      

    完整代码实现

    package logUtil
    
    import (
        "fmt"
        "io"
        "os"
        "path"
        "runtime"
        "strconv"
        "sync"
        "time"
    )
        
    const (
        colorRed    = 31
        colorYellow = 33
        colorBlue   = 34
       
        levelT = "[T] "
        levelE = "[E] "
        levelW = "[W] "
       
        defaultFileSize = 60 * 1024 * 1024
        minFileSize     = 1 * 1024 * 1024
        defaultLogDir   = "log"
        defaultLogName  = "default.log"
        
        logTypeStd logType = iota + 1
        logTypeFile
    )
        
    type (
        logType int
        
        LogOption func(log *myLog)
        
        myLog struct {
            sync.Once
            sync.Mutex
            outs     map[logType]io.Writer //writer集合
            file     *os.File              //文件句柄
            fileName string                //日志名
            dir      string                //日志存放路径
            size     int64                 //单个日志文件的大小限制
        }
    )
        
    var (
        defaultLogger = &myLog{}
    )
        
    func (m *myLog) init() {
        if m.dir == "" {
            m.dir = defaultLogDir
        }
        if m.fileName == "" {
            m.fileName = defaultLogName
        }
        if m.size == 0 {
            m.size = defaultFileSize
        } else {
            if m.size < minFileSize {
                panic(fmt.Sprintf("invalid size: %d", m.size))
            }
        }
        
        if m.outs == nil {
            m.outs = make(map[logType]io.Writer)
        }
        if !isExist(m.dir) {
            if err := os.Mkdir(m.dir, 0777); err != nil {
                panic(err)
            }
        }
        name := path.Join(m.dir, m.fileName)
        file, err := os.OpenFile(name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
        if err != nil {
            panic(err)
        }    
        
        m.file = file
        m.outs[logTypeStd] = os.Stdout
        m.outs[logTypeFile] = file
    }
        
    func (m *myLog) checkLogSize() {
        if m.file == nil {
            return
        }
        m.Lock()
        defer m.Unlock()
        fileInfo, err := m.file.Stat()
        if err != nil {
            panic(err)
        }
        if m.size > fileInfo.Size() {
            return
        }
        //需要分割
        newName := path.Join(m.dir, time.Now().Format("2006_01_02_15:04:03")+".log")
        name := path.Join(m.dir, m.fileName)
        
        err = os.Rename(name, newName)
        if err != nil {
            panic(err)
        }
        
        file, err := os.OpenFile(name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
        if err != nil {
            panic(err)
        }
        
        m.file.Close()
        m.file = file
        m.outs[logTypeFile] = file
        return
    }
        
    func (m *myLog) write(level string, content string) {
        m.checkLogSize()
        var colorText int
        switch level {
        case levelT:
            colorText = colorBlue
        case levelW:
            colorText = colorYellow
        case levelE:
            colorText = colorRed
        }
       
        for k, wr := range m.outs {
            if k == logTypeStd {
                fmt.Fprintf(wr, setColor(content, colorText))
            } else {
                fmt.Fprintf(wr, content)
            }
        }
    }
        
    func WithSize(size int64) LogOption {
        return func(log *myLog) {
            log.size = size
        }
    }
        
    func WithLogDir(dir string) LogOption {
        return func(log *myLog) {
            log.dir = dir
        }
    }
        
    func WithFileName(name string) LogOption {
        return func(log *myLog) {
            log.fileName = name
        }
    }
        
    func InitLogger(args ...LogOption) {
        defaultLogger.Do(func() {
            for _, af := range args {
                af(defaultLogger)
            }
            defaultLogger.init()
        })
    }
        
    //Info
    func T(format string, v ...interface{}) {
        _, file, line, _ := runtime.Caller(1)
        timeStr := time.Now().Format("2006-01-02 15:04:05.0000") + " "
        codeLine := "[" + timeStr + shortFileName(file) + ":" + strconv.Itoa(line) + "]"
        content := levelT + codeLine + fmt.Sprintf(format, v...) + "\n"
        defaultLogger.write(levelT, content)
    }
        
    //Error
    func E(format string, v ...interface{}) {
        _, file, line, _ := runtime.Caller(1)
        timeStr := time.Now().Format("2006-01-02 15:04:05.0000") + " "
        codeLine := "[" + timeStr + shortFileName(file) + ":" + strconv.Itoa(line) + "]"
        content := levelE + codeLine + fmt.Sprintf(format, v...) + "\n"
        defaultLogger.write(levelE, content)
    }
        
    //Warn
    func W(format string, v ...interface{}) {
        _, file, line, _ := runtime.Caller(1)
        timeStr := time.Now().Format("2006-01-02 15:04:05.0000") + " "
        codeLine := "[" + timeStr + shortFileName(file) + ":" + strconv.Itoa(line) + "]"
        content := levelW + codeLine + fmt.Sprintf(format, v...) + "\n"
        defaultLogger.write(levelW, content)
    }
        
    func isExist(path string) bool {
        _, err := os.Stat(path)
        if err != nil {
            if os.IsExist(err) {
                return true
            }
            return false
        }
        return true
    }
        
    func shortFileName(file string) string {
        short := file
        for i := len(file) - 1; i > 0; i-- {
            if file[i] == '/' {
                short = file[i+1:]
                break
            }
        }
        return short
    }
        
    func setColor(msg string, text int) string {
        return fmt.Sprintf("%c[%dm%s%c[0m", 0x1B, text, msg, 0x1B)
    }
    

    最后

    原文链接
    如有不足,还请不吝指教!
    Thanks!
    附上代码地址:logUtil,欢迎指正!

    相关文章

      网友评论

          本文标题:Go语言实现简单的日志记录

          本文链接:https://www.haomeiwen.com/subject/yypxtktx.html