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

2020-06-06  本文已影响0人  pyihe

前言

一个完整的日志库不仅仅涵盖日志记录功能,还要包括日志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,欢迎指正!

上一篇下一篇

猜你喜欢

热点阅读