在使用Go语言的时候,发现标准库的日志log功能很初级。
log.Println(i)
log.Printf(format, i...)
log.Fatalln(i)
log.Panic()
...
基本也就围绕上面几个方法来做日志处理。于是我上Github查找第三方库。在看了几个log库之后,觉得不是满意。我打算自己尝试实现log日志组件。
最后实现log的使用如下:
// 标准日志
l := logs.NewLogger()
l.SetLogLevel(logs.WARN) //设置日志输出等级
l.Debug("Debug")
l.Info("Info")
l.Notice("Notice")
l.Warn(100)
l.Error("Error")
l.Critical("Critical")
l.Fatal("Fatal")
// 保存到本地log文件的日志
l := logs.NewRotateFileLogger("/Users/xxx/logs") // 设置log日志文件存放路径
l.SetLogLevel(logs.INFO) //设置日志输出等级
l.SetNewFileGapTime(time.Hour) // 设置每个小时创建新的log文件
l.Notice("Notice")
l.Warn(100)
l.Error("Error")
l.Critical("Critical")
l.Fatal("Fatal")
性能上通过Benchmark分析输出一个Hello
平均每次执行时间:4177ns/p
内存使用情况:168 B/op 和 7 allocs/op
完全可以应对每秒上万的输出。性能达到要求了。
设计思考
我对组件一直有这样的思路,
组件要易于上手,减少学习成本。尽量减少不必要的概念和抽象。
初始的组件能满足最常用的使用场景,开箱即可用。满足80%的情况。同时又易于拓展,特别需求可以简单通过组件的拓展来封装。
组件API精简,设计上更符合人们对组件直观的认知。
开发前
对日志组件的需求,大部分情况无非一几点
- 带有Info、Warn、Error等等的不同等级的输出。
- 可以设置输出等级。
- 输出内容格式可以自定义。
- 支持log文件的写入。
- 最好也支持过滤Filter输出的功能。
设计API
初定的API
l.SetLogLevel(level) //设置日志输出等级
l.Debug(...)
l.Info(...)
l.Notice(...)
l.Warn(...)
...
l.SetFormat(...) //设置输出格式
前面的几个API比较好理解,但是在设计输出格式SetFormat的时候,我查看了几个第三方库,特别容易在这里引入概念和抽象层。设置还有封装出格式化结构的struct来定义格式的。这样的设计明显不符合我之前说的设计思考的几点,于是我查看了官方的log。官方的log很简朴但是很好理解,一个format字符串+对应format字符串格式的内容。例如格式%s-%s 对应2017/11/26 和Hello。这种方式开发者都熟悉,比较适合。所以我现在了官方的方式来自定义格式。
可是用一个SetFormat来传入参数定义格式其实很难封装得好,因为格式和前面的Info(data)有点不好封装,格式和data可以对应,但是扩展很弱。
我想输出是2017/11/26 Hello,定义好格式后,data不能简单的是Hello,Info(data) 这里的data就要自己再加入时间2017/11/26。
于是我选择了用传入函数来定义格式输出。
logFormatFunc func(logType LogType, i interface{}) (string, []interface{}, bool)
logType标记输出的等级,i表示输出的日志,
函数返回(string, []interface{},bool)
string是格式 例如%s-%s。 []interface{}表示返回格式的内容。
bool主要是满足做Filter过滤器
好了 先看看Logger默认的格式定义函数
func (l *Logger) DefaultLogFormatFunc(logType LogType, i interface{}) (string, []interface{}, bool){
format := "\033["+ logTypesColors[logType] + "m%s [%s] %s \033[0m\n"
layout :="2006-01-02 15:04:05.999"
formatTime := time.Now().Format(layout)
if len(formatTime) != len(layout) {
// 可能出现结尾是0被省略 如 2006-01-02 15:04:05.9 2006-01-02 15:04:05.99,补上
formatTime += ".000"[4 - (len(layout) - len(formatTime)):4]
}
values := make([]interface{}, 3)
values[0] = logTypeStrings[logType]
values[1] = formatTime
values[2] = fmt.Sprint(i)
return format, values, true
}
其实就是格式定义是%s [%s] %s
返回[]interface{} 对应格式的 3个%s。
编写Logger
提供Info(...) Warn(...) Error(...) 和 SetLogLevel(level) 再提供了SetLoggerFormat设置自定义格式输出等等功能。
接口少但易于拓展。
通过SetLoggerFormat 我拓展实现了log文件的RotateFileLogger
RotateFileLogger的格式函数如下:
func (l *RotateFileLogger) DefaultLogFormatFunc(logType LogType, i interface{}) (string, []interface{}, bool){
format := "%s [%s] %s\n"
now := time.Now()
gapTime := now.Sub(l.lastFileTime)
if gapTime > l.newFileGapTime && l.newFileGapTime > 0{
l.file.Close()
rate := int(int64(gapTime) / int64(l.newFileGapTime))
l.lastFileTime = l.lastFileTime.Add(l.newFileGapTime * time.Duration(rate))
file, err := l.createLogFile(l.fileNameFormatFunc(l.lastFileTime))
if err != nil{
file.Close()
panic(err)
}
l.file = file
l.out = file
}
layout :="2006-01-02 15:04:05.999"
formatTime := now.Format(layout)
defer func() {
e := recover()
if e!=nil{
panic(debug.Stack())
}
}()
if len(formatTime) != len(layout) {
// 对于如果是 出现2006-01-02 15:04:05.99 适配处理 成2006-01-02 15:04:05.990
formatTime += ".000"[4 - (len(layout) - len(formatTime)):4]
}
values := make([]interface{}, 3)
values[0] = GetLogTypeString(logType)
values[1] = formatTime
values[2] = fmt.Sprint(i)
return format, values, true
}
源码 (2个文件)
logger.go
package logs
import (
"sync"
"io"
"fmt"
"time"
"os"
"strings"
)
type LogType int
const (
DEBUG = LogType(0)
INFO = LogType(1)
NOTICE = LogType(2)
WARN = LogType(3)
ERROR = LogType(4)
CRITICAL = LogType(5)
FATAL = LogType(6)
)
var logTypeStrings = func() []string{
// log 类型对应的 名称字符串,用于输出,所以统一了长度,故 DEBUG 为 "DEBUG..." 和 "CRITICAL"等长
types :=[]string{"DEBUG", "INFO", "NOTICE", "WARN", "ERROR", "CRITICAL", "FATAL"}
maxTypeLen := 0
for _, t := range types{
if len(t) > maxTypeLen {
maxTypeLen = len(t)
}
}
for index, t := range types{
typeLen := len(t)
if typeLen < maxTypeLen{
types[index] += strings.Repeat(".", maxTypeLen - typeLen)
}
}
return types
}()
var logTypesColors = []string{"0;35", "1;36", "1;37", "0;33", "1;31", "1;31", "1;31"}
/*
创建 Logger
*/
func NewLogger() *Logger{
logger := &Logger{}
logger.Init()
return logger
}
func GetLogTypeString(t LogType) string{
return logTypeStrings[t]
}
/*
Logger 日志
l := NewLogger()
l.Info("hello")
l.Warn(1)
输入格式可以通过 SetLoggerFormat 设置。默认输出格式定义在 见Logger的DefaultLogFormatFunc
可以通过 SetLogLevel 设置输出等级。
*/
type Logger struct {
mu sync.Mutex
out io.Writer
logFormatFunc func(logType LogType, i interface{}) (string, []interface{}, bool)
logLevel LogType
}
func (l *Logger) Init() {
l.mu.Lock()
defer l.mu.Unlock()
l.logFormatFunc = l.DefaultLogFormatFunc
l.out = os.Stdout
l.logLevel = DEBUG
}
func (l* Logger) SetLogLevel(logType LogType) {
l.mu.Lock()
defer l.mu.Unlock()
l.logLevel = logType
}
func (l* Logger) GetLogLevel() LogType{
l.mu.Lock()
defer l.mu.Unlock()
return l.logLevel
}
// 设置格式化log输出函数
// 函数返回 format 和 对应格式 []interface{}
func (l *Logger) SetLoggerFormat(formatFunc func(logType LogType, i interface{}) (string, []interface{}, bool)) {
l.mu.Lock()
defer l.mu.Unlock()
l.logFormatFunc = formatFunc
}
// 输出信息
func (l *Logger) Debug(i interface{}) {
l.log(DEBUG, i)
}
func (l *Logger) Info(i interface{}){
l.log(INFO, i)
}
func (l *Logger) Notice(i interface{}){
l.log(NOTICE, i)
}
func (l *Logger) Warn(i interface{}) {
l.log(WARN, i)
}
func (l *Logger) Error(i interface{}) {
l.log(ERROR, i)
}
func (l *Logger) Critical(i interface{}) {
l.log(CRITICAL, i)
}
func (l *Logger) Fatal(i interface{}) {
l.log(FATAL, i)
}
func (l *Logger) DefaultLogFormatFunc(logType LogType, i interface{}) (string, []interface{}, bool){
format := "\033["+ logTypesColors[logType] + "m%s [%s] %s \033[0m\n"
layout :="2006-01-02 15:04:05.999"
formatTime := time.Now().Format(layout)
if len(formatTime) != len(layout) {
// 可能出现结尾是0被省略 如 2006-01-02 15:04:05.9 2006-01-02 15:04:05.99,补上
formatTime += ".000"[4 - (len(layout) - len(formatTime)):4]
}
values := make([]interface{}, 3)
values[0] = logTypeStrings[logType]
values[1] = formatTime
values[2] = fmt.Sprint(i)
return format, values, true
}
func (l *Logger) log(logType LogType, i interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.logLevel > logType{
return
}
format, data, isLog := l.logFormatFunc(logType, i)
if !isLog{ return}
_, err := fmt.Fprintf(l.out, format, data...)
if err !=nil{
panic(err)
}
}
file_logger.go
package logs
import (
"os"
"time"
"fmt"
"runtime/debug"
)
/*
*
*/
func NewRotateFileLogger(dir string) *RotateFileLogger{
logger := &RotateFileLogger{}
logger.Init(dir)
return logger
}
// 一段时间自动创建新的log文件
// 如果在间隔时间无日志,此间隔不会创建log文件
type RotateFileLogger struct {
Logger
file *os.File // 正在操作文件
dirPath string // logs 文件所在文件夹
fileNameFormatFunc func(t time.Time) string // 获取文件名称格式
newFileGapTime time.Duration // 创建新log的间隔时间
lastFileTime time.Time // 上次创建文件,文件对应时间(依据间隔时间,不是真实创建时间)
}
func (l * RotateFileLogger) Init(dir string) {
l.Logger.Init()
l.mu.Lock()
defer l.mu.Unlock()
l.fileNameFormatFunc = l.DefaultFileNameFormat
l.logFormatFunc = l.DefaultLogFormatFunc
l.newFileGapTime = 0
l.lastFileTime = time.Now()
l.dirPath = dir
file, err := l.createLogFile(l.fileNameFormatFunc(l.lastFileTime))
if err != nil{
panic(err)
return
}
l.file = file
l.out = file
}
func (l * RotateFileLogger) SetNewFileGapTime(gapTime time.Duration) {
l.mu.Lock()
defer l.mu.Unlock()
l.newFileGapTime = gapTime
}
// 默认 创建格式化的文件名. fileTime 不一定是创建文件的时间
func (l * RotateFileLogger) DefaultFileNameFormat(fileTime time.Time) string{
// tips: linux 不支持 2006-01-02 15:04:05.999 ":"名称
// tips: os x 2006/01-02 15-04-05-999 带有"/"报no such file or directory
layout :="2006-01-02 15-04-05-999"
formatTime := fileTime.Format(layout)
if len(formatTime) != len(layout) {
// 对于如果是 出现2006-01-02 15:04:05.99 适配处理 成2006-01-02 15:04:05.990
formatTime += ".000"[4 - (len(layout) - len(formatTime)):4]
}
return formatTime + ".log"
}
func (l * RotateFileLogger) createLogFile(filename string) (*os.File, error){
if len(l.dirPath) != 0{
filename = l.dirPath + "/" + filename
err:=os.MkdirAll(l.dirPath, 0777)
if err != nil{
panic(err)
}
}
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil{
return nil, err
}
return file, nil
}
func (l *RotateFileLogger) DefaultLogFormatFunc(logType LogType, i interface{}) (string, []interface{}, bool){
format := "%s [%s] %s\n"
now := time.Now()
gapTime := now.Sub(l.lastFileTime)
if gapTime > l.newFileGapTime && l.newFileGapTime > 0{
l.file.Close()
rate := int(int64(gapTime) / int64(l.newFileGapTime))
l.lastFileTime = l.lastFileTime.Add(l.newFileGapTime * time.Duration(rate))
file, err := l.createLogFile(l.fileNameFormatFunc(l.lastFileTime))
if err != nil{
file.Close()
panic(err)
}
l.file = file
l.out = file
}
layout :="2006-01-02 15:04:05.999"
formatTime := now.Format(layout)
defer func() {
e := recover()
if e!=nil{
panic(debug.Stack())
}
}()
if len(formatTime) != len(layout) {
// 对于如果是 出现2006-01-02 15:04:05.99 适配处理 成2006-01-02 15:04:05.990
formatTime += ".000"[4 - (len(layout) - len(formatTime)):4]
}
values := make([]interface{}, 3)
values[0] = GetLogTypeString(logType)
values[1] = formatTime
values[2] = fmt.Sprint(i)
return format, values, true
}