You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lal/pkg/util/log/log.go

275 lines
6.6 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package log
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"time"
)
// TODO chef:
// - 性能优化目前是基于系统库log实现的
// - 和系统库中的log跑个benchmark对比
var logErr = errors.New("log:fxxk")
type Logger interface {
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{})
Debug(v ...interface{})
Info(v ...interface{})
Warn(v ...interface{})
Error(v ...interface{})
Outputf(level Level, calldepth int, format string, v ...interface{})
Output(level Level, calldepth int, v ...interface{})
}
type Level uint8
const (
LevelDebug = iota
LevelInfo
LevelWarn
LevelError
)
type Config struct {
Level Level `json:"level"` // 日志级别,大于等于该级别的日志才会被输出
Filename string `json:"filename"` // 输出日志文件名,如果为空,则不写日志文件。可包含路径,路径不存在时,将自动创建
IsToStdout bool `json:"is_to_stdout"` // 是否以stdout输出到控制台
// 文件输出和控制台输出可同时打开控制台输出主要用做开发时调试支持level彩色输出以及源码文件、行号
RotateMByte int `json:"rotate_mbyte"` // 日志大小达到多少兆后翻滚如果为0则不翻滚
}
func New(c Config) (Logger, error) {
var (
fl *log.Logger
sl *log.Logger
dir string
fp *os.File
err error
)
if c.Level < LevelDebug || c.Level > LevelError {
return nil, logErr
}
if c.Filename != "" {
dir = filepath.Dir(c.Filename)
if err := os.MkdirAll(dir, 0777); err != nil {
return nil, err
}
fp, err = os.Create(c.Filename)
if err != nil {
return nil, err
}
fl = log.New(fp, "", log.Ldate|log.Lmicroseconds)
}
if c.IsToStdout {
sl = log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
}
l := &logger{
fileLogger: fl,
stdoutLogger: sl,
c: c,
dir: dir,
fp: fp,
}
return l, nil
}
const (
levelDebugString = "DEBUG "
levelInfoString = "INFO "
levelWarnString = "WARN "
levelErrorString = "ERROR "
levelDebugColorString = "\033[22;37mDEBUG\033[0m "
levelInfoColorString = "\033[22;36mINFO\033[0m "
levelWarnColorString = "\033[22;33mWARN\033[0m "
levelErrorColorString = "\033[22;31mERROR\033[0m "
)
var (
levelToString = map[Level]string{
LevelDebug: levelDebugString,
LevelInfo: levelInfoString,
LevelWarn: levelWarnString,
LevelError: levelErrorString,
}
levelToColorString = map[Level]string{
LevelDebug: levelDebugColorString,
LevelInfo: levelInfoColorString,
LevelWarn: levelWarnColorString,
LevelError: levelErrorColorString,
}
)
type logger struct {
fileLogger *log.Logger
stdoutLogger *log.Logger
c Config
dir string
m sync.Mutex
fp *os.File
}
func (l *logger) Debugf(format string, v ...interface{}) {
l.Outputf(LevelDebug, 3, format, v...)
}
func (l *logger) Infof(format string, v ...interface{}) {
l.Outputf(LevelInfo, 3, format, v...)
}
func (l *logger) Warnf(format string, v ...interface{}) {
l.Outputf(LevelWarn, 3, format, v...)
}
func (l *logger) Errorf(format string, v ...interface{}) {
l.Outputf(LevelError, 3, format, v...)
}
func (l *logger) Debug(v ...interface{}) {
l.Output(LevelDebug, 3, v...)
}
func (l *logger) Info(v ...interface{}) {
l.Output(LevelInfo, 3, v...)
}
func (l *logger) Warn(v ...interface{}) {
l.Output(LevelWarn, 3, v...)
}
func (l *logger) Error(v ...interface{}) {
l.Output(LevelError, 3, v...)
}
// TODO chef: Outputf 和 Output 代码重复
func (l *logger) Outputf(level Level, calldepth int, format string, v ...interface{}) {
if l.c.Level > level {
return
}
msg := fmt.Sprintf(format, v...)
if l.stdoutLogger != nil {
l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg)
}
if l.fileLogger != nil {
if l.c.RotateMByte > 0 {
l.m.Lock()
// 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件
// 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)
}
}
func (l *logger) Output(level Level, calldepth int, v ...interface{}) {
if l.c.Level > level {
return
}
msg := fmt.Sprint(v...)
if l.stdoutLogger != nil {
l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg)
}
if l.fileLogger != nil {
if l.c.RotateMByte > 0 {
l.m.Lock()
// 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件
// 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)
}
}
var global Logger
func Debugf(format string, v ...interface{}) {
global.Outputf(LevelDebug, 3, format, v...)
}
func Infof(format string, v ...interface{}) {
global.Outputf(LevelInfo, 3, format, v...)
}
func Warnf(format string, v ...interface{}) {
global.Outputf(LevelWarn, 3, format, v...)
}
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 Outputf(level Level, calldepth int, format string, v ...interface{}) {
global.Outputf(level, calldepth, format, v...)
}
func Output(level Level, calldepth int, v ...interface{}) {
global.Output(level, calldepth, v...)
}
// 这里不加锁保护如果要调用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,
})
}