Go:log功能的实现

时间:Feb. 15, 2020 分类:

目录:

需求分析

  1. 支持往不同的地方输出
  2. 支持日志级别Debug,Trace,Info,Warning,Error,Fatal
  3. 日志开关功能
  4. 日志信息格式化
  5. 日志文件切割
  6. 异步写入日志

根据日志级别循环打印日志输出

package main

import(
    "fmt"
    "time"
)

// 各个日志级别
func Debug(msg string) {
    now := time.Now()
    fmt.Printf("[%s][DEBUG] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}

func Trace(msg string) {
    now := time.Now()
    fmt.Printf("[%s][TRACE] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}


func Info(msg string) {
    now := time.Now()
    fmt.Printf("[%s][INFO] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}

func Warning(msg string) {
    now := time.Now()
    fmt.Printf("[%s][WARNING] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}

func Error(msg string) {
    now := time.Now()
    fmt.Printf("[%s][ERROR] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}

func Fatal(msg string) {
    now := time.Now()
    fmt.Printf("[%s][FATAL] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}

// 测试数据
func main() {
    for {
        Debug("这是Debug日志")
        Trace("这是Trace日志")
        Info("这是Info日志")
        Warning("这是Warning日志")
        Error("这是Error日志")
        Fatal("这是Fatal日志")
        time.Sleep(time.Second)
    }
}

日志开关

将日志级别转化为数字,用于比较日志级别输出

package main

import(
    "fmt"
    "time"
    "errors"
)

type LogLevel uint16

const (
    UNKNOWN LogLevel = iota
    DEBUG
    TRACE
    INFO
    WARNING
    ERROR
    FATAL
)

type Logger struct {
    Level LogLevel
}

// 构造函数
func NewLogger(levelStr string) Logger {
    level, err := parseLogLevel(levelStr)
    if err != nil {
        panic(err)
    }
    return Logger{
        Level: level,
    }
}

// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
    switch s {
    case "debug":
        return DEBUG, nil
    case "trace":
        return TRACE, nil
    case "info":
        return INFO, nil
    case "warning":
        return WARNING, nil
    case "error":
        return ERROR, nil
    case "fatal":
        return FATAL, nil
    default:
        err := errors.New("无效日志级别")
        return UNKNOWN, err
    }
}

// 开关
func (l Logger) enable(LogLevel LogLevel) bool {
    return LogLevel >= l.Level
}

// 各个日志级别
func (l Logger) Debug(msg string) {
    if l.enable(DEBUG) {
        now := time.Now()
        fmt.Printf("[%s][DEBUG] %s\n", now.Format("2006-01-02 15:04:05"), msg)
    }
}

func (l Logger) Trace(msg string) {
    if l.enable(TRACE) {
        now := time.Now()
        fmt.Printf("[%s][TRACE] %s\n", now.Format("2006-01-02 15:04:05"), msg)
    }
}


func (l Logger) Info(msg string) {
    if l.enable(INFO) {
        now := time.Now()
        fmt.Printf("[%s][INFO] %s\n", now.Format("2006-01-02 15:04:05"), msg)
    }
}

func (l Logger) Warning(msg string) {
    if l.enable(WARNING) {
        now := time.Now()
        fmt.Printf("[%s][WARNING] %s\n", now.Format("2006-01-02 15:04:05"), msg)
    }
}

func (l Logger) Error(msg string) {
    if l.enable(ERROR) {
        now := time.Now()
        fmt.Printf("[%s][ERROR] %s\n", now.Format("2006-01-02 15:04:05"), msg)
    }
}

func (l Logger) Fatal(msg string) {
    if l.enable(FATAL) {
        now := time.Now()
        fmt.Printf("[%s][FATAL] %s\n", now.Format("2006-01-02 15:04:05"), msg)
    }
}

// 测试数据
func main() {
    log := NewLogger("error")
    for {
        log.Debug("这是Debug日志")
        log.Trace("这是Trace日志")
        log.Info("这是Info日志")
        log.Warning("这是Warning日志")
        log.Error("这是Error日志")
        log.Fatal("这是Fatal日志")
        time.Sleep(time.Second)
    }
}

定位错位位置

使用runtime.Caller实现

package main

import(
    "fmt"
    "time"
    "errors"
    "runtime"
    "strings"
    "path"
)

type LogLevel uint16

const (
    UNKNOWN LogLevel = iota
    DEBUG
    TRACE
    INFO
    WARNING
    ERROR
    FATAL
)

type Logger struct {
    Level LogLevel
}

// 构造函数
func NewLogger(levelStr string) Logger {
    level, err := parseLogLevel(levelStr)
    if err != nil {
        panic(err)
    }
    return Logger{
        Level: level,
    }
}

// 定位错误位置
func getInfo(skip int) (fileName, funcName string, lineNo int) {
    pc, file, lineNo, ok := runtime.Caller(skip)
    if !ok {
        fmt.Printf("runtimme.Caller() failed\n")
        return
    }
    funcName = runtime.FuncForPC(pc).Name()
    fileName = path.Base(file)
    funcName = strings.Split(funcName, ".")[1]
    return 
}

// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
    switch s {
    case "debug":
        return DEBUG, nil
    case "trace":
        return TRACE, nil
    case "info":
        return INFO, nil
    case "warning":
        return WARNING, nil
    case "error":
        return ERROR, nil
    case "fatal":
        return FATAL, nil
    default:
        err := errors.New("无效日志级别")
        return UNKNOWN, err
    }
}

// 开关
func (l Logger) enable(LogLevel LogLevel) bool {
    return LogLevel >= l.Level
}

// 各个日志级别
func (l Logger) Debug(msg string) {
    if l.enable(DEBUG) {
        now := time.Now()
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][DEBUG][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo,  msg)
    }
}

func (l Logger) Trace(msg string) {
    if l.enable(TRACE) {
        now := time.Now()
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][TRACE][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo,  msg)
    }
}


func (l Logger) Info(msg string) {
    if l.enable(INFO) {
        now := time.Now()
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][INFO][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo,  msg)
    }
}

func (l Logger) Warning(msg string) {
    if l.enable(WARNING) {
        now := time.Now()
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][WARNING][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo,  msg)
    }
}

func (l Logger) Error(msg string) {
    if l.enable(ERROR) {
        now := time.Now()
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][ERROR][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo,  msg)
    }
}

func (l Logger) Fatal(msg string) {
    if l.enable(FATAL) {
        now := time.Now()
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][FATAL][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo,  msg)
    }
}

// 测试数据
func main() {
    log := NewLogger("error")
    for {
        log.Debug("这是Debug日志")
        log.Trace("这是Trace日志")
        log.Info("这是Info日志")
        log.Warning("这是Warning日志")
        log.Error("这是Error日志")
        log.Fatal("这是Fatal日志")
        time.Sleep(time.Second)
    }
}

重复日志打印整合

package main

import(
    "fmt"
    "time"
    "errors"
    "runtime"
    "strings"
    "path"
)

type LogLevel uint16

const (
    UNKNOWN LogLevel = iota
    DEBUG
    TRACE
    INFO
    WARNING
    ERROR
    FATAL
)

type Logger struct {
    Level LogLevel
}

// 构造函数
func NewLogger(levelStr string) Logger {
    level, err := parseLogLevel(levelStr)
    if err != nil {
        panic(err)
    }
    return Logger{
        Level: level,
    }
}

// 定位错误位置
func getInfo(skip int) (fileName, funcName string, lineNo int) {
    pc, file, lineNo, ok := runtime.Caller(skip)
    if !ok {
        fmt.Printf("runtimme.Caller() failed\n")
        return
    }
    funcName = runtime.FuncForPC(pc).Name()
    fileName = path.Base(file)
    funcName = strings.Split(funcName, ".")[1]
    return 
}

// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
    switch s {
    case "debug":
        return DEBUG, nil
    case "trace":
        return TRACE, nil
    case "info":
        return INFO, nil
    case "warning":
        return WARNING, nil
    case "error":
        return ERROR, nil
    case "fatal":
        return FATAL, nil
    default:
        err := errors.New("无效日志级别")
        return UNKNOWN, err
    }
}
// 日志级别转化为字符串
func getLogString(lv LogLevel) string {
   switch lv {
   case DEBUG: 
       return "DEBUG"
   case TRACE: 
       return "TRACE"
   case INFO: 
       return "INFO"
   case WARNING: 
       return "WARNING"
   case ERROR: 
       return "ERROR"
   case FATAL: 
       return "FATAL"
   default:
       return "UNKNOWN"
   }
}

// 开关
func (l Logger) enable(LogLevel LogLevel) bool {
    return LogLevel >= l.Level
}

// 各个日志级别
func (l Logger) log(lvstring string , msg string) {
    lv, err := parseLogLevel(lvstring)
    if err != nil {
        panic(err)        
    }
    if l.enable(lv) {
        now := time.Now()
        // skip根据实际情况调整
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][%s][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo,  msg)
    }
}

// 测试数据
func main() {
    log := NewLogger("error")
    for {
        log.log("fatal", "这是Fatal日志")
        log.log("error", "这是error日志")
        log.log("warning", "这是warning日志")
        log.log("info", "这是info日志")
        log.log("debug", "这是debug日志")
        time.Sleep(time.Second)
    }
}

自定义日志格式

采用空接口传数据

// 各个日志级别
func (l Logger) log(lvstring string , format string, a ...interface{}) {
    msg := fmt.Sprintf(format, a...)
    lv, err := parseLogLevel(lvstring)

    if err != nil {
        panic(err)        
    }
    if l.enable(lv) {
        now := time.Now()
        // skip根据实际情况调整
        fileName, funcName, lineNo := getInfo(1)
        fmt.Printf("[%s][%s][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo,  msg)
    }
}

// 测试数据
func main() {
    log := NewLogger("error")
    id := 1
    st := "test"
    for {
        log.log("fatal", "这是Fatal日志, id=%d, st=%s", id, st)
        log.log("error", "这是error日志")
        log.log("warning", "这是warning日志")
        log.log("info", "这是info日志")
        log.log("debug", "这是debug日志")
        time.Sleep(time.Second)
    }
}

打印结果

[2020-02-20 18:04:46][FATAL][main.go Logger 107] 这是Fatal日志, id=1, st=test
[2020-02-20 18:04:46][ERROR][main.go Logger 107] 这是error日志
[2020-02-20 18:04:47][FATAL][main.go Logger 107] 这是Fatal日志, id=1, st=test
[2020-02-20 18:04:47][ERROR][main.go Logger 107] 这是error日志
[2020-02-20 18:04:48][FATAL][main.go Logger 107] 这是Fatal日志, id=1, st=test
[2020-02-20 18:04:48][ERROR][main.go Logger 107] 这是error日志

写入文件

package main

import(
    "fmt"
    "time"
    "errors"
    "runtime"
    "strings"
    "path"
    "os"
)

type LogLevel uint16

const (
    UNKNOWN LogLevel = iota
    DEBUG
    TRACE
    INFO
    WARNING
    ERROR
    FATAL
)

type FileLogger struct {
    Level LogLevel
    filePath string   //日志路径
    fileName string   //日志名
    maxFileSize int64 //日志切分大小
    fileObj *os.File
}

// 构造函数
func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
    logLevel, err := parseLogLevel(levelStr)
    if err != nil {
        panic(err)
    }
    f := &FileLogger{
        Level: logLevel,
        filePath: fp,
        fileName: fn,
        maxFileSize: maxSize,
    }
    err = f.initFile()
    if err != nil {
        panic(err)
    }
    return f
}

// 初始化文件
func (f *FileLogger) initFile() error {
    fullFileName := path.Join(f.filePath, f.fileName)
    fileObj, err := os.OpenFile(fullFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Printf("Open log file failed, err:%v\n", err)
        return err
    }
    f.fileObj = fileObj
    return nil
}

// 定位错误位置
func getInfo(skip int) (fileName, funcName string, lineNo int) {
    pc, file, lineNo, ok := runtime.Caller(skip)
    if !ok {
        fmt.Printf("runtimme.Caller() failed\n")
        return
    }
    funcName = runtime.FuncForPC(pc).Name()
    fileName = path.Base(file)
    funcName = strings.Split(funcName, ".")[1]
    return 
}

// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
    switch s {
    case "debug":
        return DEBUG, nil
    case "trace":
        return TRACE, nil
    case "info":
        return INFO, nil
    case "warning":
        return WARNING, nil
    case "error":
        return ERROR, nil
    case "fatal":
        return FATAL, nil
    default:
        err := errors.New("无效日志级别")
        return UNKNOWN, err
    }
}
// 日志级别转化为字符串
func getLogString(lv LogLevel) string {
   switch lv {
   case DEBUG: 
       return "DEBUG"
   case TRACE: 
       return "TRACE"
   case INFO: 
       return "INFO"
   case WARNING: 
       return "WARNING"
   case ERROR: 
       return "ERROR"
   case FATAL: 
       return "FATAL"
   default:
       return "UNKNOWN"
   }
}

// 开关
func (f *FileLogger) enable(LogLevel LogLevel) bool {
    return LogLevel >= f.Level
}

// 切分日志
func (f *FileLogger) splitFile() {
    _, err := f.fileObj.Stat()
    if err != nil {
        fmt.Printf("get file info failed, err:%v\n", err)
    }
    f.fileObj.Close()
    nowStr := time.Now().Format("20060102150405000")
    logName := path.Join(f.filePath, f.fileName)
    newLogName := fmt.Sprintf("%s-%s", logName, nowStr)
    os.Rename(logName, newLogName)
    fileObj, err := os.OpenFile(logName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Printf("Open log file failed, err:%v\n", err)
        return
    }   
    f.fileObj = fileObj
}

// 判断日志大小
func (f *FileLogger) checkSize() bool {
    fileInfo, err := f.fileObj.Stat()
    if err != nil {
        fmt.Printf("get file info failed, err:%v\n", err)
    }
    return fileInfo.Size() > f.maxFileSize
}

// 各个日志级别
func (f *FileLogger) log(lvstring string , format string, a ...interface{}) {
    lv, _ := parseLogLevel(lvstring)
    if f.enable(lv) {
        msg := fmt.Sprintf(format, a...)
        now := time.Now()
        // skip根据实际情况调整
        fileName, funcName, lineNo := getInfo(1)
        if f.checkSize() {
            f.splitFile()
    }
        fmt.Fprintf(f.fileObj, "[%s][%s][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo,  msg)
    }
}

// 测试数据
func main() {
    log := NewFileLogger("info", "./", "why.test", 10*1024)
    id := 1
    st := "test"
    for {
        log.log("fatal", "这是Fatal日志, id=%d, st=%s", id, st)
        log.log("error", "这是error日志")
        log.log("warning", "这是warning日志")
        log.log("info", "这是info日志")
        log.log("debug", "这是debug日志")
        time.Sleep(time.Second)
    }
}

查看一下

$ ll
-rw-r--r-- 1 root root  4202 Feb 20 21:36 main.go
-rw-r--r-- 1 root root  3980 Feb 20 21:33 why.test
-rw-r--r-- 1 root root 10257 Feb 20 21:29 why.test-20200220212908000
-rw-r--r-- 1 root root 10251 Feb 20 21:29 why.test-20200220212941000
-rw-r--r-- 1 root root 10255 Feb 20 21:30 why.test-20200220213014000
-rw-r--r-- 1 root root 10329 Feb 20 21:30 why.test-20200220213047000
-rw-r--r-- 1 root root 10329 Feb 20 21:31 why.test-20200220213120000
-rw-r--r-- 1 root root 10329 Feb 20 21:31 why.test-20200220213153000
-rw-r--r-- 1 root root 10329 Feb 20 21:32 why.test-20200220213226000
-rw-r--r-- 1 root root 10329 Feb 20 21:32 why.test-20200220213259000    
$ cat why.test
[2020-02-20 21:33:02][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:02][INFO][main.go (*FileLogger) 158] 这是info日志
[2020-02-20 21:33:03][FATAL][main.go (*FileLogger) 158] 这是Fatal日志, id=1, st=test
[2020-02-20 21:33:03][ERROR][main.go (*FileLogger) 158] 这是error日志
[2020-02-20 21:33:03][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:03][INFO][main.go (*FileLogger) 158] 这是info日志
[2020-02-20 21:33:04][FATAL][main.go (*FileLogger) 158] 这是Fatal日志, id=1, st=test
[2020-02-20 21:33:04][ERROR][main.go (*FileLogger) 158] 这是error日志
[2020-02-20 21:33:04][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:04][INFO][main.go (*FileLogger) 158] 这是info日志
[2020-02-20 21:33:05][FATAL][main.go (*FileLogger) 158] 这是Fatal日志, id=1, st=test
[2020-02-20 21:33:05][ERROR][main.go (*FileLogger) 158] 这是error日志
[2020-02-20 21:33:05][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:05][INFO][main.go (*FileLogger) 158] 这是info日志

如果是异步写,需要将消息的指针通过channel传递过去