go web: 2 封装日志包log

在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.Errorln("%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,
        level, 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)
    }
}

猜你喜欢

转载自blog.csdn.net/yzh900927/article/details/77807256
今日推荐