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