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.
naza/pkg/nazalog/log.go

338 lines
7.8 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 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)
}