) 去除了对标准库中log的依赖 -) 日志支持按天翻转 -) 日志不再支持按固定大小翻转 -) 增加 ShortFileFlag 可配置是否打印源码文件及行号的信息 -) 添加一个fatal日志级别 -) 增加 Out 接口 -) 日志级别从1开始 -) 当同时打印至控制台和文件时,打印至文件中的level字段也带颜色属性

pull/2/head
q191201771 6 years ago
parent 754f9e8c3e
commit d7c7d6a553

2
.gitignore vendored

@ -1,10 +1,12 @@
coverage.txt coverage.txt
coverage.html coverage.html
profile.out profile.out
tmp
/.idea /.idea
/.trash /.trash
/bin /bin
/pre-commit.sh /pre-commit.sh
/TODO.md
profile.out* profile.out*

@ -0,0 +1,82 @@
package log
import (
"fmt"
"os"
)
var global Logger
func Outputf(level Level, calldepth int, format string, v ...interface{}) {
global.Out(level, 3, fmt.Sprintf(format, v...))
}
func Debugf(format string, v ...interface{}) {
global.Out(LevelDebug, 3, fmt.Sprintf(format, v...))
}
func Infof(format string, v ...interface{}) {
global.Out(LevelInfo, 3, fmt.Sprintf(format, v...))
}
func Warnf(format string, v ...interface{}) {
global.Out(LevelWarn, 3, fmt.Sprintf(format, v...))
}
func Errorf(format string, v ...interface{}) {
global.Out(LevelError, 3, fmt.Sprintf(format, v...))
}
func Fatalf(format string, v ...interface{}) {
global.Out(LevelFatal, 3, fmt.Sprintf(format, v...))
}
func Output(level Level, calldepth int, v ...interface{}) {
global.Out(level, 3, fmt.Sprint(v...))
}
func Debug(v ...interface{}) {
global.Out(LevelDebug, 3, fmt.Sprint(v...))
}
func Info(v ...interface{}) {
global.Out(LevelInfo, 3, fmt.Sprint(v...))
}
func Warn(v ...interface{}) {
global.Out(LevelWarn, 3, fmt.Sprint(v...))
}
func Error(v ...interface{}) {
global.Out(LevelError, 3, fmt.Sprint(v...))
}
func Fatal(v ...interface{}) {
global.Out(LevelFatal, 3, fmt.Sprint(v...))
}
func FatalIfErrorNotNil(err error) {
if err != nil {
global.Out(LevelError, 3, fmt.Sprintf("fatal since error not nil. err=%+v", err))
os.Exit(1)
}
}
func Out(level Level, calldepth int, s string) {
global.Out(level, calldepth, s)
}
// 这里不加锁保护如果要调用Init函数初始化全局的Logger那么由调用方保证调用Init函数时不会并发调用全局Logger的其他方法
func Init(c Config) error {
var err error
global, err = New(c)
return err
}
func init() {
global, _ = New(Config{
Level: LevelDebug,
IsToStdout: true,
ShortFileFlag:true,
})
}

@ -2,9 +2,10 @@
package log package log
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"log" "io"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -12,59 +13,67 @@ import (
"time" "time"
) )
// TODO chef: // 1. 带日志级别
// - 性能优化目前是基于系统库log实现的 // 2. 可选输出至控制台或文件,也可以同时输出
// - 和系统库中的log跑个benchmark对比 // 3. 日志文件支持按天翻转
//
// 目前性能和标准库log相当
var logErr = errors.New("log:fxxk") var LogErr = errors.New("log:fxxk")
type Logger interface { type Logger interface {
Debugf(format string, v ...interface{}) Debugf(format string, v ...interface{})
Infof(format string, v ...interface{}) Infof(format string, v ...interface{})
Warnf(format string, v ...interface{}) Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{}) Errorf(format string, v ...interface{})
Fatalf(format string, v ...interface{}) // 打印日志并退出程序
Debug(v ...interface{}) Debug(v ...interface{})
Info(v ...interface{}) Info(v ...interface{})
Warn(v ...interface{}) Warn(v ...interface{})
Error(v ...interface{}) Error(v ...interface{})
Fatal(v ...interface{})
// 打印错误并退出程序,日志级别为 LevelError
FatalIfErrorNotNil(err error) FatalIfErrorNotNil(err error)
Outputf(level Level, calldepth int, format string, v ...interface{}) Outputf(level Level, calldepth int, format string, v ...interface{})
Output(level Level, calldepth int, v ...interface{}) Output(level Level, calldepth int, v ...interface{})
Out(level Level, calldepth int, s string)
} }
type Level uint8
const (
LevelDebug = iota
LevelInfo
LevelWarn
LevelError
)
type Config struct { type Config struct {
Level Level `json:"level"` // 日志级别,大于等于该级别的日志才会被输出 Level Level `json:"level"` // 日志级别,大于等于该级别的日志才会被输出
// 文件输出和控制台输出可同时打开控制台输出主要用做开发时调试支持level彩色输出 // 文件输出和控制台输出可同时打开
// 控制台输出主要用做开发时调试打开后level字段使用彩色输出
Filename string `json:"filename"` // 输出日志文件名,如果为空,则不写日志文件。可包含路径,路径不存在时,将自动创建 Filename string `json:"filename"` // 输出日志文件名,如果为空,则不写日志文件。可包含路径,路径不存在时,将自动创建
IsToStdout bool `json:"is_to_stdout"` // 是否以stdout输出到控制台 IsToStdout bool `json:"is_to_stdout"` // 是否以stdout输出到控制台
RotateMByte int `json:"rotate_mbyte"` // 日志大小达到多少兆后翻滚如果为0则不翻滚 IsRotateDaily bool `json:"rotate_daily"` // 日志按天翻转
ShortFileFlag bool `json:"short_file_flag"` // 是否在每行日志尾部添加源码文件及行号的信息
} }
type Level uint8
const (
_ = iota
LevelDebug
LevelInfo
LevelWarn
LevelError
LevelFatal
)
func New(c Config) (Logger, error) { func New(c Config) (Logger, error) {
var ( var (
fl *log.Logger dir string
sl *log.Logger fp *os.File
dir string console io.Writer
fp *os.File err error
err error
) )
if c.Level < LevelDebug || c.Level > LevelError { if c.Level < LevelDebug || c.Level > LevelFatal {
return nil, logErr return nil, LogErr
} }
if c.Filename != "" { if c.Filename != "" {
dir = filepath.Dir(c.Filename) dir = filepath.Dir(c.Filename)
@ -75,18 +84,17 @@ func New(c Config) (Logger, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
fl = log.New(fp, "", log.Ldate|log.Lmicroseconds)
} }
if c.IsToStdout { if c.IsToStdout {
sl = log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds) console = os.Stdout
} }
l := &logger{ l := &logger{
fileLogger: fl, c: c,
stdoutLogger: sl, dir: dir,
c: c, fp: fp,
dir: dir, console: console,
fp: fp, currRoundTime:time.Now(),
} }
return l, nil return l, nil
} }
@ -96,11 +104,13 @@ const (
levelInfoString = " INFO " levelInfoString = " INFO "
levelWarnString = " WARN " levelWarnString = " WARN "
levelErrorString = "ERROR " levelErrorString = "ERROR "
levelFatalString = "FATAL "
levelDebugColorString = "\033[22;37mDEBUG\033[0m " levelDebugColorString = "\033[22;37mDEBUG\033[0m "
levelInfoColorString = " \033[22;36mINFO\033[0m " levelInfoColorString = "\033[22;36m INFO\033[0m "
levelWarnColorString = " \033[22;33mWARN\033[0m " levelWarnColorString = "\033[22;33m WARN\033[0m "
levelErrorColorString = "\033[22;31mERROR\033[0m " levelErrorColorString = "\033[22;31mERROR\033[0m "
levelFatalColorString = "\033[22;31mFATAL\033[0m " // 颜色和error的一样
) )
var ( var (
@ -109,183 +119,172 @@ var (
LevelInfo: levelInfoString, LevelInfo: levelInfoString,
LevelWarn: levelWarnString, LevelWarn: levelWarnString,
LevelError: levelErrorString, LevelError: levelErrorString,
LevelFatal: levelFatalString,
} }
levelToColorString = map[Level]string{ levelToColorString = map[Level]string{
LevelDebug: levelDebugColorString, LevelDebug: levelDebugColorString,
LevelInfo: levelInfoColorString, LevelInfo: levelInfoColorString,
LevelWarn: levelWarnColorString, LevelWarn: levelWarnColorString,
LevelError: levelErrorColorString, LevelError: levelErrorColorString,
LevelFatal: levelFatalColorString,
} }
) )
type logger struct { type logger struct {
fileLogger *log.Logger c Config
stdoutLogger *log.Logger
c Config
dir string dir string
m sync.Mutex m sync.Mutex
fp *os.File fp *os.File
console io.Writer
buf bytes.Buffer
currRoundTime time.Time
}
func (l *logger) Outputf(level Level, calldepth int, format string, v ...interface{}) {
l.Out(level, 3, fmt.Sprintf(format, v...))
} }
func (l *logger) Debugf(format string, v ...interface{}) { func (l *logger) Debugf(format string, v ...interface{}) {
l.Outputf(LevelDebug, 3, format, v...) l.Out(LevelDebug, 3, fmt.Sprintf(format, v...))
} }
func (l *logger) Infof(format string, v ...interface{}) { func (l *logger) Infof(format string, v ...interface{}) {
l.Outputf(LevelInfo, 3, format, v...) l.Out(LevelInfo, 3, fmt.Sprintf(format, v...))
} }
func (l *logger) Warnf(format string, v ...interface{}) { func (l *logger) Warnf(format string, v ...interface{}) {
l.Outputf(LevelWarn, 3, format, v...) l.Out(LevelWarn, 3, fmt.Sprintf(format, v...))
} }
func (l *logger) Errorf(format string, v ...interface{}) { func (l *logger) Errorf(format string, v ...interface{}) {
l.Outputf(LevelError, 3, format, v...) l.Out(LevelError, 3, fmt.Sprintf(format, v...))
}
func (l *logger) Fatalf(format string, v ...interface{}) {
l.Out(LevelFatal, 3, fmt.Sprintf(format, v...))
os.Exit(1)
}
func (l *logger) Output(level Level, calldepth int, v ...interface{}) {
l.Out(level, 3, fmt.Sprint(v...))
} }
func (l *logger) Debug(v ...interface{}) { func (l *logger) Debug(v ...interface{}) {
l.Output(LevelDebug, 3, v...) l.Out(LevelDebug, 3, fmt.Sprint(v...))
} }
func (l *logger) Info(v ...interface{}) { func (l *logger) Info(v ...interface{}) {
l.Output(LevelInfo, 3, v...) l.Out(LevelInfo, 3, fmt.Sprint(v...))
} }
func (l *logger) Warn(v ...interface{}) { func (l *logger) Warn(v ...interface{}) {
l.Output(LevelWarn, 3, v...) l.Out(LevelWarn, 3, fmt.Sprint(v...))
} }
func (l *logger) Error(v ...interface{}) { func (l *logger) Error(v ...interface{}) {
l.Output(LevelError, 3, v...) l.Out(LevelError, 3, fmt.Sprint(v...))
}
func (l *logger) Fatal(v ...interface{}) {
l.Out(LevelFatal, 3, fmt.Sprint(v...))
os.Exit(1)
} }
func (l *logger) FatalIfErrorNotNil(err error) { func (l *logger) FatalIfErrorNotNil(err error) {
if err != nil { if err != nil {
l.Outputf(LevelError, 3, "fatal since error not nil. err=%+v", err) l.Out(LevelError, 3, fmt.Sprintf("fatal since error not nil. err=%+v", err))
os.Exit(1) os.Exit(1)
} }
} }
// TODO chef: Outputf 和 Output 代码重复 func (l *logger) Out(level Level, calldepth int, s string) {
func (l *logger) Outputf(level Level, calldepth int, format string, v ...interface{}) {
if l.c.Level > level { if l.c.Level > level {
return return
} }
msg := fmt.Sprintf(format, v...) + shortFileSuffix(calldepth) l.m.Lock()
if l.stdoutLogger != nil { defer l.m.Unlock()
_ = l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg)
now := time.Now()
// 格式化日志内容
l.buf.Reset()
writeTime(&l.buf, now)
if l.console != nil {
l.buf.WriteString(levelToColorString[level])
} else {
l.buf.WriteString(levelToString[level])
} }
if l.fileLogger != nil { l.buf.WriteString(s)
if l.c.RotateMByte > 0 { if l.c.ShortFileFlag {
l.m.Lock() writeShortFile(&l.buf, calldepth)
// 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件
// TODO chef: 性能比较差,系统库内部也有锁
defer l.m.Unlock()
if fi, err := os.Stat(l.c.Filename); err == nil {
if fi.Size() > int64(l.c.RotateMByte)*1024*1024 {
newFileName := l.c.Filename + "." + time.Now().Format("20060102150405")
if err := os.Rename(l.c.Filename, newFileName); err == nil {
_ = l.fp.Close()
l.fp, _ = os.Create(l.c.Filename)
l.fileLogger.SetOutput(l.fp)
}
}
}
}
_ = l.fileLogger.Output(calldepth, levelToString[level]+msg)
} }
} if len(s) == 0 || s[len(s)-1] != '\n' {
l.buf.WriteByte('\n')
func (l *logger) Output(level Level, calldepth int, v ...interface{}) {
if l.c.Level > level {
return
} }
msg := fmt.Sprint(v...) + shortFileSuffix(calldepth) // 输出至控制台
if l.stdoutLogger != nil { if l.console != nil {
_ = l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg) _, _ = l.console.Write(l.buf.Bytes())
} }
if l.fileLogger != nil {
if l.c.RotateMByte > 0 { // 输出至日志文件
l.m.Lock() if l.fp != nil {
// 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件 if now.Day() != l.currRoundTime.Day() {
// TODO chef: 性能比较差,系统库内部也有锁 backupName := l.c.Filename + "." + l.currRoundTime.Format("20060102")
defer l.m.Unlock() if err := os.Rename(l.c.Filename, backupName); err == nil {
if fi, err := os.Stat(l.c.Filename); err == nil { _ = l.fp.Close()
if fi.Size() > int64(l.c.RotateMByte)*1024*1024 { l.fp, _ = os.Create(l.c.Filename)
newFileName := l.c.Filename + "." + time.Now().Format("20060102150405")
if err := os.Rename(l.c.Filename, newFileName); err == nil {
_ = l.fp.Close()
l.fp, _ = os.Create(l.c.Filename)
l.fileLogger.SetOutput(l.fp)
}
}
} }
l.currRoundTime = now
} }
_ = l.fileLogger.Output(calldepth, levelToString[level]+msg) _, _ = l.fp.Write(l.buf.Bytes())
} }
} }
var global Logger // @NOTICE 该函数拷贝自 Go 标准库
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
func Debugf(format string, v ...interface{}) { func itoa(buf *bytes.Buffer, i int, wid int) {
global.Outputf(LevelDebug, 3, format, v...) // Assemble decimal in reverse order.
} var b [20]byte
bp := len(b) - 1
func Infof(format string, v ...interface{}) { for i >= 10 || wid > 1 {
global.Outputf(LevelInfo, 3, format, v...) wid--
} q := i / 10
b[bp] = byte('0' + i - q*10)
func Warnf(format string, v ...interface{}) { bp--
global.Outputf(LevelWarn, 3, format, v...) i = q
}
func Errorf(format string, v ...interface{}) {
global.Outputf(LevelError, 3, format, v...)
}
func Debug(v ...interface{}) {
global.Output(LevelDebug, 3, v...)
}
func Info(v ...interface{}) {
global.Output(LevelInfo, 3, v...)
}
func Warn(v ...interface{}) {
global.Output(LevelWarn, 3, v...)
}
func Error(v ...interface{}) {
global.Output(LevelError, 3, v...)
}
func FatalIfErrorNotNil(err error) {
if err != nil {
global.Outputf(LevelError, 3, "fatal since error not nil. err=%+v", err)
os.Exit(1)
} }
// i < 10
b[bp] = byte('0' + i)
buf.Write(b[bp:])
} }
func Outputf(level Level, calldepth int, format string, v ...interface{}) { func writeTime(buf *bytes.Buffer, t time.Time) {
global.Outputf(level, calldepth, format, v...) year, month, day := t.Date()
itoa(buf, year, 4)
buf.WriteByte('/')
itoa(buf, int(month), 2)
buf.WriteByte('/')
itoa(buf, day, 2)
buf.WriteByte(' ')
hour, min, sec := t.Clock()
itoa(buf, hour, 2)
buf.WriteByte(':')
itoa(buf, min, 2)
buf.WriteByte(':')
itoa(buf, sec, 2)
buf.WriteByte('.')
itoa(buf, t.Nanosecond()/1e3, 6)
buf.WriteByte(' ')
} }
func Output(level Level, calldepth int, v ...interface{}) { func writeShortFile(buf *bytes.Buffer, calldepth int) {
global.Output(level, calldepth, v...) buf.Write([]byte{' ', '-', ' '})
}
// 这里不加锁保护如果要调用Init函数初始化全局的Logger那么由调用方保证调用Init函数时不会并发调用全局Logger的其他方法
func Init(c Config) error {
var err error
global, err = New(c)
return err
}
func shortFileSuffix(calldepth int) string {
_, file, line, ok := runtime.Caller(calldepth) _, file, line, ok := runtime.Caller(calldepth)
if !ok { if !ok {
file = "???" file = "???"
@ -299,12 +298,8 @@ func shortFileSuffix(calldepth int) string {
} }
} }
file = short file = short
return fmt.Sprintf(" - %s:%d", file, line)
}
func init() { buf.WriteString(file)
global, _ = New(Config{ buf.WriteByte(':')
Level: LevelDebug, itoa(buf, line, -1)
IsToStdout: true,
})
} }

@ -12,48 +12,51 @@ func TestLogger(t *testing.T) {
Level: LevelInfo, Level: LevelInfo,
Filename: "/tmp/lallogtest/aaa.log", Filename: "/tmp/lallogtest/aaa.log",
IsToStdout: true, IsToStdout: true,
RotateMByte: 10, IsRotateDaily: true,
} }
l, err := New(c) l, err := New(c)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
l.Debugf("test msg by Debug%s", "f") l.Debugf("l test msg by Debug%s", "f")
l.Infof("test msg by Info%s", "f") l.Infof("l test msg by Info%s", "f")
l.Warnf("test msg by Warn%s", "f") l.Warnf("l test msg by Warn%s", "f")
l.Errorf("test msg by Error%s", "f") l.Errorf("l test msg by Error%s", "f")
l.Debug("test msg by Debug") l.Debug("l test msg by Debug")
l.Info("test msg by Info") l.Info("l test msg by Info")
l.Warn("test msg by Warn") l.Warn("l test msg by Warn")
l.Error("test msg by Error") l.Error("l test msg by Error")
l.Outputf(LevelInfo, 3, "l test msg by Output%s", "f")
l.Output(LevelInfo, 3, "l test msg by Output")
l.Out(LevelInfo, 3, "l test msg by Out")
} }
func TestGlobal(t *testing.T) { func TestGlobal(t *testing.T) {
Debugf("test msg by Debug%s", "f") Debugf("g test msg by Debug%s", "f")
Infof("test msg by Info%s", "f") Infof("g test msg by Info%s", "f")
Warnf("test msg by Warn%s", "f") Warnf("g test msg by Warn%s", "f")
Errorf("test msg by Error%s", "f") Errorf("g test msg by Error%s", "f")
Debug("test msg by Debug") Debug("g test msg by Debug")
Info("test msg by Info") Info("g test msg by Info")
Warn("test msg by Warn") Warn("g test msg by Warn")
Error("test msg by Error") Error("g test msg by Error")
c := Config{ c := Config{
Level: LevelInfo, Level: LevelInfo,
Filename: "/tmp/lallogtest/bbb.log", Filename: "/tmp/lallogtest/bbb.log",
IsToStdout: true, IsToStdout: true,
RotateMByte: 10,
} }
err := Init(c) err := Init(c)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
Debugf("test msg by Debug%s", "f") Debugf("gc test msg by Debug%s", "f")
Infof("test msg by Info%s", "f") Infof("gc test msg by Info%s", "f")
Warnf("test msg by Warn%s", "f") Warnf("gc test msg by Warn%s", "f")
Errorf("test msg by Error%s", "f") Errorf("gc test msg by Error%s", "f")
Debug("test msg by Debug") Debug("gc test msg by Debug")
Info("test msg by Info") Info("gc test msg by Info")
Warn("test msg by Warn") Warn("gc test msg by Warn")
Error("test msg by Error") Error("gc test msg by Error")
Output(LevelInfo, 3, "test msg by Output") Outputf(LevelInfo, 3, "gc test msg by Output%s", "f")
Outputf(LevelInfo, 3, "test msg by Output%s", "f") Output(LevelInfo, 3, "gc test msg by Output")
Out(LevelInfo, 3, "gc test msg by Out")
} }
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
@ -61,9 +64,9 @@ func TestNew(t *testing.T) {
l Logger l Logger
err error err error
) )
l, err = New(Config{Level: LevelError + 1}) l, err = New(Config{Level: LevelFatal + 1})
assert.Equal(t, nil, l) assert.Equal(t, nil, l)
assert.Equal(t, logErr, err) assert.Equal(t, LogErr, err)
l, err = New(Config{Filename: "/tmp"}) l, err = New(Config{Filename: "/tmp"})
assert.Equal(t, nil, l) assert.Equal(t, nil, l)
@ -79,7 +82,7 @@ func TestRotate(t *testing.T) {
Level: LevelInfo, Level: LevelInfo,
Filename: "/tmp/lallogtest/ccc.log", Filename: "/tmp/lallogtest/ccc.log",
IsToStdout: false, IsToStdout: false,
RotateMByte: 1, IsRotateDaily: true,
} }
err := Init(c) err := Init(c)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
@ -93,24 +96,30 @@ func TestRotate(t *testing.T) {
} }
func BenchmarkStdout(b *testing.B) { func BenchmarkStdout(b *testing.B) {
b.ReportAllocs()
c := Config{ c := Config{
Level: LevelInfo, Level: LevelInfo,
Filename: "/tmp/lallogtest/ccc.log", //Filename: "/tmp/lallogtest/ddd.log",
IsToStdout: true, Filename: "/dev/null",
RotateMByte: 10, //IsToStdout: true,
ShortFileFlag:true,
} }
err := Init(c) err := Init(c)
assert.Equal(b, nil, err) assert.Equal(b, nil, err)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Infof("hello %s %d", "world", i) Infof("hello %s %d", "world", i)
Info("Info")
} }
} }
func BenchmarkOriginLog(b *testing.B) { func BenchmarkOriginLog(b *testing.B) {
fp, _ := os.Create("/tmp/origin.log") b.ReportAllocs()
fp, err := os.Create("/dev/null")
assert.Equal(b, nil, err)
originLog.SetOutput(fp) originLog.SetOutput(fp)
originLog.SetFlags(originLog.Ldate | originLog.Lshortfile) originLog.SetFlags(originLog.Ldate | originLog.Ltime | originLog.Lmicroseconds | originLog.Lshortfile)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
originLog.Printf("hello %s %d", "world", i) originLog.Printf("hello %s %d\n", "world", i)
originLog.Println("Info")
} }
} }

Loading…
Cancel
Save