zoukankan      html  css  js  c++  java
  • 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工具,这里写的这个还很简陋,不过能用就行。

    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

    最后这里推荐下使用PingCAP家的log模块,格式够详细使用够简单,产品也够成熟,常用的使用Demo如下: 

    package main
    
    import (
    	"github.com/pingcap/log"
    )
    
    func main() {
    	conf := &log.Config{
    		Level: "info", 
    		File: log.FileLogConfig{
    			Filename: "main.log", 
    			MaxSize: 1024 * 1024 * 1024, 
    			MaxDays: 30, 
    			MaxBackups: 60}}
    	filelogger, _, _ := log.InitLogger(conf)
    	filelogger.Error("This is a Error Msg!")  // 文件中打印日志
    	log.Error("This is a Error Msg!")  // 标准输出中打印日志
    }
    

    补充一:

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

     

    想建一个数据库技术和编程技术的交流群,用于磨炼提升技术能力,目前主要专注于Golang和Python以及TiDB,MySQL数据库,群号:231338927,建群日期:2019.04.26,截止2021.02.01人数:300人 ... 如发现博客错误,可直接留言指正,感谢。
  • 相关阅读:
    c++ stl string char* 向 string 转换的问题
    不要在疲惫中工作
    今天
    悠然自得
    忙与闲
    <转>LuaTinker的bug和缺陷
    匿名管道
    SetWindowHookEx 做消息响应
    最近工作
    实现网页页面跳转的几种方法(meta标签、js实现、php实现)
  • 原文地址:https://www.cnblogs.com/realcp1018/p/15222286.html
Copyright © 2011-2022 走看看