Golang log工具

log - The Go Programming Language (golang.org)

import (
    "log"
)

func main() {
    log.Print("Logging in Go!")
}

log模块的Fatal与Panic开头的function都会直接终止程序运行(os.Exit(1)),这点需要注意。

log模块只有Print与Fatal、Panic三个level对应的函数,可以通过SetOutput函数定义输出目的地,如下为输出至文件的示例。

package main

import (
	"log"
	"os"
)

func main() {
	file, _ := os.OpenFile("my.log", os.O_CREATE|os.O_RDWR,0644)
	defer file.Close()
	logger := &log.Logger{}
	logger.SetOutput(file)
	for i:=0; i<50; i++{
		logger.SetFlags(i)  // 经测试,有效的flag值有32个,表示各种不同的输出格式,平时我们选取比较顺眼的flag即可,可以通过一些const参数指定,见下例
		logger.Print("------")
	}
}

go标准库log包虽然可以用,但比较原始。无法便捷的实现像java,python那样快速的格式化输出。

许多开源的第三方的log包也标榜自己多快多好(没错说的就是你zap),但实际上对我来说并不能即插即用,因为结构化输出居多,必须要经过详实的了解和配置才能组合出自己需要的格式。可能这些log包适合直接集成logstash或者到kafka,毕竟直接输出的都是json格式的模块化日志,但是未经可视化处理前对于人眼并不友好。

因此这里自己写一个简单的log包,适用于日常工具类场景。这里的很多参数都写死了,如有自定义lumberkjack参数的需求可以再写其他的new构造函数。lumberkjack是一个用于维护日志文件进行日志切换的包,例如控制文件大小、保存日期、保留个数等等,类似linux上的logrotate功能。

相比于一些成熟的log工具,这里写的这个还很简陋,不过能用就行。

2020-6-29新增:

下述代码我直接新建了一个github repo,这样日常项目里我就可以直接go get使用了~~

详见:https://github.com/realcp1018/tinylog

package log

import (
	"fmt"
	"gopkg.in/natefinch/lumberjack.v2"
	"log"
	"os"
	"runtime/debug"
	"sync"
)

const (
	DEBUG = iota
	INFO
	WARN
	ERROR
	FATAL
)

type Logger struct {
	*log.Logger
	mu       sync.Mutex
	logLevel uint8
}

func NewFileLogger(fileName string, level uint8) *Logger {
	logger := new(log.Logger)
	logger.SetOutput(&lumberjack.Logger{
		Filename:   fileName,
		MaxSize:    500,
		MaxBackups: 30,
		MaxAge:     7,
		Compress:   true,
	})
	logger.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile | log.Lmsgprefix)
	return &Logger{Logger: logger, logLevel: level}
}

func NewStreamLogger(level uint8) *Logger {
	logger := new(log.Logger)
	logger.SetOutput(os.Stdout)
	logger.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile | log.Lmsgprefix)
	return &Logger{Logger: logger, logLevel: level}
}

func (l *Logger) SetLevel(level uint8) {
	l.logLevel = level
}

func (l *Logger) GetLevel() uint8 {
	return l.logLevel
}

func (l *Logger) Debug(logStr string, v ...interface{}) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.logLevel == DEBUG {
		l.SetPrefix(fmt.Sprintf("[DEBUG]: "))
		_ = l.Output(2, fmt.Sprintf(logStr, v...))
	}
}

func (l *Logger) Info(logStr string, v ...interface{}) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.logLevel <= INFO {
		l.SetPrefix("[INFO]: ")
		_ = l.Output(2, fmt.Sprintf(logStr, v...))
	}
}

func (l *Logger) Warn(logStr string, v ...interface{}) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.logLevel <= WARN {
		l.SetPrefix("[WARN]: ")
		_ = l.Output(2, fmt.Sprintf(logStr, v...))
	}
}

func (l *Logger) Error(logStr string, v ...interface{}) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.logLevel <= ERROR {
		l.SetPrefix("[ERROR]: ")
		_ = l.Output(2, fmt.Sprintf(logStr, v...))
	}
}

func (l *Logger) Fatal(logStr string, v ...interface{}) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.logLevel <= FATAL {
		l.SetPrefix("[FATAL]: ")
		_ = l.Output(2, fmt.Sprintf("%s [stacktrace]:\n%s", fmt.Sprintf(logStr, v...), string(debug.Stack())))
		os.Exit(1)
	}
}

使用:

如果是单个包内使用,直接调用相关New构造函数即可。

如果是在包含多个包的项目内使用,只需要在任意包内定义一个GlobalLogger即可,为方便项目内其他包调用,可以直接放在log package内,这样其他包直接使用log.GlobalLogger就可以将日志原子性的写入同一个日志内了。

logutil.go:

package log  
// 如有从配置文件中读取参数的需求,也可一并写在这里
var GlobalLogger = NewFileLogger("log/myproject.log", INFO)

测试下:

package test // 任意包内都可测试

import "testing"
import "myproject/log"

func TestAll(t *testing.T) {
	log.GlobalLogger.Info("This is info log, %s", "name=leo")
}

输出的log格式如下:

2021/08/17 15:19:55.281819 log_test.go:8 [Info]: This is info log, name=leo

可以看到这个格式稍微好看了那么一丢丢,但是没有熟悉的[]方框还是不美,鉴于加方框需要修改写标准log库的代码,保险起见直接拷贝标准log包改名为baselog,然后把里边Logger的formatHeader方法修改下就可以了。

我们在标准log包的log.go文件里添加如下几行:

if l.flag&Ldate != 0部分添加一行:*buf = append(*buf, '[')
if l.flag&(Ltime|Lmicroseconds) != 0部分添加一行:*buf = append(*buf, ']')
if l.flag&(Lshortfile|Llongfile) != 0添加两行:*buf = append(*buf, '[')  *buf = append(*buf, "] "...)
//总之核心目的就是为了加上方框......low是low了点,能用就完事了

示例输出:

[2021/08/17 15:19:55.281819] [log_test.go:8] [Info]: This is info log, name=leo

补充一:

突然意识到setPrefix时需要加锁,不然并发打印日志时可能出现goroutines交叉修改prefix,导致打印出来的prefix有问题的情况。只需要给Logger额外加个mu sync.Mutex然后每个level打印时用mu.Lock/Unlock包裹起来即可,保证两个操作是atomic的。

 

posted @ 2021-09-03 11:18  realcp1018  阅读(312)  评论(0编辑  收藏  举报