|
|
|
|
// package nazalog 日志库
|
|
|
|
|
package nazalog
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"runtime"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 1. 带日志级别
|
|
|
|
|
// 2. 可选输出至控制台或文件,也可以同时输出
|
|
|
|
|
// 3. 日志文件支持按天翻转
|
|
|
|
|
//
|
|
|
|
|
// 目前性能和标准库log相当
|
|
|
|
|
|
|
|
|
|
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{})
|
|
|
|
|
Fatalf(format string, v ...interface{}) // 打印日志并退出程序
|
|
|
|
|
Panicf(format string, v ...interface{})
|
|
|
|
|
|
|
|
|
|
Debug(v ...interface{})
|
|
|
|
|
Info(v ...interface{})
|
|
|
|
|
Warn(v ...interface{})
|
|
|
|
|
Error(v ...interface{})
|
|
|
|
|
Fatal(v ...interface{})
|
|
|
|
|
Panic(v ...interface{})
|
|
|
|
|
|
|
|
|
|
FatalIfErrorNotNil(err error)
|
|
|
|
|
PanicIfErrorNotNil(err error)
|
|
|
|
|
|
|
|
|
|
Outputf(level Level, calldepth int, format string, v ...interface{})
|
|
|
|
|
Output(level Level, calldepth int, v ...interface{})
|
|
|
|
|
Out(level Level, calldepth int, s string)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Option struct {
|
|
|
|
|
Level Level `json:"level"` // 日志级别,大于等于该级别的日志才会被输出
|
|
|
|
|
|
|
|
|
|
// 文件输出和控制台输出可同时打开
|
|
|
|
|
// 控制台输出主要用做开发时调试,打开后level字段使用彩色输出
|
|
|
|
|
Filename string `json:"filename"` // 输出日志文件名,如果为空,则不写日志文件。可包含路径,路径不存在时,将自动创建
|
|
|
|
|
IsToStdout bool `json:"is_to_stdout"` // 是否以stdout输出到控制台
|
|
|
|
|
|
|
|
|
|
IsRotateDaily bool `json:"is_rotate_daily"` // 日志按天翻转
|
|
|
|
|
|
|
|
|
|
ShortFileFlag bool `json:"short_file_flag"` // 是否在每行日志尾部添加源码文件及行号的信息
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 没有配置的属性,将按如下配置
|
|
|
|
|
var defaultOption = Option{
|
|
|
|
|
Level: LevelDebug,
|
|
|
|
|
Filename: "",
|
|
|
|
|
IsToStdout: true,
|
|
|
|
|
IsRotateDaily: false,
|
|
|
|
|
ShortFileFlag: true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Level uint8
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
_ Level = iota
|
|
|
|
|
LevelDebug // 1
|
|
|
|
|
LevelInfo
|
|
|
|
|
LevelWarn
|
|
|
|
|
LevelError
|
|
|
|
|
LevelFatal
|
|
|
|
|
LevelPanic
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ModOption func(option *Option)
|
|
|
|
|
|
|
|
|
|
func New(modOptions ...ModOption) (Logger, error) {
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
l := new(logger)
|
|
|
|
|
l.currRoundTime = time.Now()
|
|
|
|
|
l.option = defaultOption
|
|
|
|
|
|
|
|
|
|
for _, fn := range modOptions {
|
|
|
|
|
fn(&l.option)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if l.option.Level < LevelDebug || l.option.Level > LevelPanic {
|
|
|
|
|
return nil, LogErr
|
|
|
|
|
}
|
|
|
|
|
if l.option.Filename != "" {
|
|
|
|
|
l.dir = filepath.Dir(l.option.Filename)
|
|
|
|
|
if err = os.MkdirAll(l.dir, 0777); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if l.fp, err = os.OpenFile(l.option.Filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if l.option.IsToStdout {
|
|
|
|
|
l.console = os.Stdout
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return l, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
levelDebugString = "DEBUG "
|
|
|
|
|
levelInfoString = " INFO "
|
|
|
|
|
levelWarnString = " WARN "
|
|
|
|
|
levelErrorString = "ERROR "
|
|
|
|
|
levelFatalString = "FATAL "
|
|
|
|
|
levelPanicString = "PANIC "
|
|
|
|
|
|
|
|
|
|
levelDebugColorString = "\033[22;37mDEBUG\033[0m "
|
|
|
|
|
levelInfoColorString = "\033[22;36m INFO\033[0m "
|
|
|
|
|
levelWarnColorString = "\033[22;33m WARN\033[0m "
|
|
|
|
|
levelErrorColorString = "\033[22;31mERROR\033[0m "
|
|
|
|
|
levelFatalColorString = "\033[22;31mFATAL\033[0m " // 颜色和 error 级别一样
|
|
|
|
|
levelPanicColorString = "\033[22;31mPANIC\033[0m " // 颜色和 error 级别一样
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
levelToString = map[Level]string{
|
|
|
|
|
LevelDebug: levelDebugString,
|
|
|
|
|
LevelInfo: levelInfoString,
|
|
|
|
|
LevelWarn: levelWarnString,
|
|
|
|
|
LevelError: levelErrorString,
|
|
|
|
|
LevelFatal: levelFatalString,
|
|
|
|
|
LevelPanic: levelPanicString,
|
|
|
|
|
}
|
|
|
|
|
levelToColorString = map[Level]string{
|
|
|
|
|
LevelDebug: levelDebugColorString,
|
|
|
|
|
LevelInfo: levelInfoColorString,
|
|
|
|
|
LevelWarn: levelWarnColorString,
|
|
|
|
|
LevelError: levelErrorColorString,
|
|
|
|
|
LevelFatal: levelFatalColorString,
|
|
|
|
|
LevelPanic: levelPanicColorString,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type logger struct {
|
|
|
|
|
option Option
|
|
|
|
|
|
|
|
|
|
dir string
|
|
|
|
|
|
|
|
|
|
m sync.Mutex
|
|
|
|
|
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{}) {
|
|
|
|
|
l.Out(LevelDebug, 3, fmt.Sprintf(format, v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Infof(format string, v ...interface{}) {
|
|
|
|
|
l.Out(LevelInfo, 3, fmt.Sprintf(format, v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Warnf(format string, v ...interface{}) {
|
|
|
|
|
l.Out(LevelWarn, 3, fmt.Sprintf(format, v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Errorf(format string, v ...interface{}) {
|
|
|
|
|
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) Panicf(format string, v ...interface{}) {
|
|
|
|
|
l.Out(LevelPanic, 3, fmt.Sprintf(format, v...))
|
|
|
|
|
panic(fmt.Sprintf(format, v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Output(level Level, calldepth int, v ...interface{}) {
|
|
|
|
|
l.Out(level, 3, fmt.Sprint(v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Debug(v ...interface{}) {
|
|
|
|
|
l.Out(LevelDebug, 3, fmt.Sprint(v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Info(v ...interface{}) {
|
|
|
|
|
l.Out(LevelInfo, 3, fmt.Sprint(v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Warn(v ...interface{}) {
|
|
|
|
|
l.Out(LevelWarn, 3, fmt.Sprint(v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Error(v ...interface{}) {
|
|
|
|
|
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) Panic(v ...interface{}) {
|
|
|
|
|
l.Out(LevelPanic, 3, fmt.Sprint(v...))
|
|
|
|
|
panic(fmt.Sprint(v...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) FatalIfErrorNotNil(err error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Out(LevelError, 3, fmt.Sprintf("fatal since error not nil. err=%+v", err))
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) PanicIfErrorNotNil(err error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Out(LevelPanic, 3, fmt.Sprintf("panic since error not nil. err=%+v", err))
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *logger) Out(level Level, calldepth int, s string) {
|
|
|
|
|
if l.option.Level > level {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
l.m.Lock()
|
|
|
|
|
defer l.m.Unlock()
|
|
|
|
|
|
|
|
|
|
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])
|
|
|
|
|
}
|
|
|
|
|
l.buf.WriteString(s)
|
|
|
|
|
if l.option.ShortFileFlag {
|
|
|
|
|
writeShortFile(&l.buf, calldepth)
|
|
|
|
|
}
|
|
|
|
|
if l.buf.Len() == 0 || l.buf.Bytes()[l.buf.Len()-1] != '\n' {
|
|
|
|
|
l.buf.WriteByte('\n')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 输出至控制台
|
|
|
|
|
if l.console != nil {
|
|
|
|
|
_, _ = l.console.Write(l.buf.Bytes())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 输出至日志文件
|
|
|
|
|
if l.fp != nil {
|
|
|
|
|
if now.Day() != l.currRoundTime.Day() {
|
|
|
|
|
backupName := l.option.Filename + "." + l.currRoundTime.Format("20060102")
|
|
|
|
|
if err := os.Rename(l.option.Filename, backupName); err == nil {
|
|
|
|
|
_ = l.fp.Close()
|
|
|
|
|
l.fp, _ = os.Create(l.option.Filename)
|
|
|
|
|
}
|
|
|
|
|
l.currRoundTime = now
|
|
|
|
|
}
|
|
|
|
|
_, _ = l.fp.Write(l.buf.Bytes())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @NOTICE 该函数拷贝自 Go 标准库
|
|
|
|
|
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
|
|
|
|
|
func itoa(buf *bytes.Buffer, i int, wid int) {
|
|
|
|
|
// Assemble decimal in reverse order.
|
|
|
|
|
var b [20]byte
|
|
|
|
|
bp := len(b) - 1
|
|
|
|
|
for i >= 10 || wid > 1 {
|
|
|
|
|
wid--
|
|
|
|
|
q := i / 10
|
|
|
|
|
b[bp] = byte('0' + i - q*10)
|
|
|
|
|
bp--
|
|
|
|
|
i = q
|
|
|
|
|
}
|
|
|
|
|
// i < 10
|
|
|
|
|
b[bp] = byte('0' + i)
|
|
|
|
|
buf.Write(b[bp:])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeTime(buf *bytes.Buffer, t time.Time) {
|
|
|
|
|
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 writeShortFile(buf *bytes.Buffer, calldepth int) {
|
|
|
|
|
buf.Write([]byte{' ', '-', ' '})
|
|
|
|
|
|
|
|
|
|
_, file, line, ok := runtime.Caller(calldepth)
|
|
|
|
|
if !ok {
|
|
|
|
|
file = "???"
|
|
|
|
|
line = 0
|
|
|
|
|
}
|
|
|
|
|
short := file
|
|
|
|
|
for i := len(file) - 1; i > 0; i-- {
|
|
|
|
|
if file[i] == '/' {
|
|
|
|
|
short = file[i+1:]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
file = short
|
|
|
|
|
|
|
|
|
|
buf.WriteString(file)
|
|
|
|
|
buf.WriteByte(':')
|
|
|
|
|
itoa(buf, line, -1)
|
|
|
|
|
}
|