Go语言系列之自定义实现日志库

Posted zhangyafei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言系列之自定义实现日志库相关的知识,希望对你有一定的参考价值。

日志库需求分析

1. 支持往不同的地方输出日志

2. 日志分级别

  • Debug
  • Trace
  • Info
  • Warning
  • Error
  • Fatal

3. 日志要支持开关控制

4. 完整的日志记录要包含时间、行号、文件名、日志级别、日志信息

5. 日志文件要切割

  • 按文件大小切割
  • 按riq切割

具体实现

目录结构

├─mylogger
│      console.go
│      file.go
│      myLogger.go

 

myLogger.go

package mylogger

import (
	"errors"
	"fmt"
	"runtime"
	"strings"
)

type logLevel uint8

type Logger interface {
	Debug(format string, a ...interface{})
	Trace(format string, a ...interface{})
	Info(format string, a ...interface{})
	Warning(format string, a ...interface{})
	Error(format string, a ...interface{})
	Fatal(format string, a ...interface{})
	SetPrefix(prefix string)
}

const (
	UnKnow logLevel = iota
	DebugLevel
	TraceLevel
	InfoLevel
	WarningLevel
	ErrorLevel
	FatalLevel
)

var (
	levels = map[logLevel]string{
		DebugLevel: "Debug",
		TraceLevel: "Trace",
		InfoLevel:  "Info",
		WarningLevel:  "Warning",
		ErrorLevel: "Error",
		FatalLevel: "Fatal",
	}
)

func (ll logLevel) String () string {
	return levels[ll]
}

func parseLogLevel(s string) (logLevel, error)  {
	s = strings.ToLower(s)
	switch s {
	case "debug":
		return DebugLevel, nil
	case "trace":
		return TraceLevel, nil
	case "info":
		return InfoLevel, nil
	case "warning":
		return WarningLevel, nil
	case "error":
		return ErrorLevel, nil
	case "fatal":
		return FatalLevel, nil
	default:
		return UnKnow, errors.New("无效的日志级别")
	}
}

func getInfo(n int) (string, error)  {
	pc, fileName, lineNo, ok := runtime.Caller(n)
	if !ok {
		return "", errors.New("runtime.Caller() failed
")
	}
	funcName := runtime.FuncForPC(pc).Name()
	funcName = strings.Split(funcName, ".")[1]
	return fmt.Sprintf("%s:%s:%d", fileName, funcName, lineNo), nil
}

console.go

package mylogger

import (
	"fmt"
	"time"
)

type ConsoleLogger struct {
	Level logLevel
	Prefix string
}

func (cl *ConsoleLogger) Print(level logLevel, format string, a ...interface{})  {
	if cl.Level <= level {
		msg := fmt.Sprintf(format, a...)
		now := time.Now()
		info, err := getInfo(3)
		if err != nil {
			fmt.Println(err.Error())
		}else if cl.Prefix == "" {
			fmt.Printf("%s ? %s %s %s
", now.Format("2006/01/02 - 15:04:05"), level, info, msg)
		}else {
			fmt.Printf("%s %s ? %s %s %s
", cl.Prefix, now.Format("2006/01/02 - 15:04:05"), level, info, msg)
		}
	}
}

func (cl *ConsoleLogger) SetPrefix(prefix string)  {
	cl.Prefix = prefix
}

// ConsoleLogger 构造函数
func NewConsoleLog(Level string) *ConsoleLogger {
	level, err := parseLogLevel(Level)
	if err != nil {
		panic(err)
	}
	return &ConsoleLogger{Level:level}
}

func (cl *ConsoleLogger) Debug(format string, a ...interface{}) {
	cl.Print(DebugLevel, format, a...)
}

func (cl *ConsoleLogger) Trace(format string, a ...interface{}) {
	cl.Print(TraceLevel, format, a...)
}

func (cl *ConsoleLogger) Info(format string, a ...interface{}) {
	cl.Print(InfoLevel, format, a...)
}

func (cl *ConsoleLogger) Warning(format string, a ...interface{}) {
	cl.Print(WarningLevel, format, a...)
}

func (cl *ConsoleLogger) Error(format string, a ...interface{}) {
	cl.Print(ErrorLevel, format, a...)
}

func (cl *ConsoleLogger) Fatal(format string, a ...interface{}) {
	cl.Print(FatalLevel, format, a...)
}

file.go

package mylogger

import (
	"fmt"
	"os"
	"path"
	"time"
)

type FileLogger struct {
	// 日志级别 比该级别高的打印的时候会显示
	Level logLevel `json:"level"`
	// 前缀 打印日志信息的前缀
	Prefix string `json:"prefix"`
	// 日志文件夹路径
	FilePath string `json:"file_path"`
	// 日志文件名
	FileName string `json:"file_name"`
	// 最大文件大小 分割日志文件的时候会用到
	MaxFileSize int64 `json:"max_file_size"`
	// 最大时间间隔 按时间分割文件的时候会用到
	MaxAge int `json:"max_age"`
	// 记录上次切割文件的时间
	LastSplitTime time.Time
	// 日志文件对象
	FileObj *os.File
	// Error日志文件对象
	ErrFileObj *os.File
}

// FileLogger 构造函数
func NewFileLog(Level string, fp, fn string, maxSize int64) *FileLogger {
	level, err := parseLogLevel(Level)
	if err != nil {
		panic(err)
	}
	fl := &FileLogger{
		Level:       level,
		FilePath:    fp,
		FileName:    fn,
		MaxFileSize: maxSize,
	}
	err = fl.initFile()
	if err != nil {
		panic(err)
	}
	return fl
}

// FileLogger 构造函数
func NewFileLogWithMaxAge(Level string, fp, fn string, maxSize int64, maxAge int) *FileLogger {
	level, err := parseLogLevel(Level)
	if err != nil {
		panic(err)
	}
	fl := &FileLogger{
		Level:       level,
		FilePath:    fp,
		FileName:    fn,
		MaxFileSize: maxSize,
		MaxAge:maxAge,
		LastSplitTime:time.Now(),
	}
	err = fl.initFile()
	if err != nil {
		panic(err)
	}
	return fl
}

func (fl *FileLogger) CheckFileSize (file *os.File) bool {
	fileInfo, err := file.Stat()
	if err != nil {
		fmt.Printf("get file info failed,err:%v
", err)
		return false
	}
	return fileInfo.Size() >= fl.MaxFileSize
}

func (fl *FileLogger) CheckFileLastAge (now time.Time) bool {
	if fl.LastSplitTime == (time.Time{}){
		return false
	}
	return int(now.Sub(fl.LastSplitTime).Hours()) > fl.MaxAge
}

func (fl *FileLogger) SetPrefix(prefix string)  {
	fl.Prefix = prefix
}

func (fl *FileLogger) Print(level logLevel, format string, a ...interface{})  {
	if fl.Level <= level {
		msg := fmt.Sprintf(format, a...)
		now := time.Now()
		info, err := getInfo(3)
		if err != nil {
			fmt.Println(err.Error())
		}else {
			if fl.CheckFileSize(fl.FileObj) || fl.CheckFileLastAge(now) {
				newFileObje, err := fl.splitFile(fl.FileObj)
				if err != nil {
					return
				}
				fl.FileObj = newFileObje
			}
			if fl.Prefix == "" {
				fmt.Fprintf(fl.FileObj, "%s ? %s %s %s
", now.Format("2006/01/02 - 15:04:05"), level, info, msg)
			}else {
				fmt.Fprintf(fl.FileObj, "%s %s ? %s %s %s
", fl.Prefix, now.Format("2006/01/02 - 15:04:05"), level, info, msg)
			}
			if level >= ErrorLevel {
				if fl.CheckFileSize(fl.ErrFileObj) || fl.CheckFileLastAge(now){
					newFileObje, err := fl.splitFile(fl.ErrFileObj)
					if err != nil {
						return
					}
					fl.ErrFileObj = newFileObje
				}
				if fl.Prefix == "" {
					fmt.Fprintf(fl.ErrFileObj, "%s ? %s %s %s
", now.Format("2006/01/02 - 15:04:05"), level, info, msg)
				} else {
					fmt.Fprintf(fl.ErrFileObj, "%s %s ? %s %s %s
", fl.Prefix, now.Format("2006/01/02 - 15:04:05"), level, info, msg)
				}
			}
		}
	}
}

func (fl *FileLogger) initFile() error {
	fullFileName := path.Join(fl.FilePath, fl.FileName)
	fileObj, err := os.OpenFile(fullFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open log file failed, err:%v", err)
		return err
	}

	errFileObj, err := os.OpenFile(fullFileName+".err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open err log file failed, err:%v", err)
		return err
	}
	fl.FileObj = fileObj
	fl.ErrFileObj = errFileObj
	return nil
}

func (fl *FileLogger) splitFile(file *os.File) (*os.File, error) {
	nowStr := time.Now().Format("20060102150405000")
	fileInfo, err := file.Stat()
	if err != nil {
		fmt.Printf("get file info failed,err:%v
", err)
		return nil, err
	}
	logName := path.Join(fl.FilePath, fileInfo.Name())
	newlogName := fmt.Sprintf("%s.%s.bak", logName, nowStr)
	// 1. 关闭当前文件
	file.Close()
	// 2. 备份一个 rename
	os.Rename(logName, newlogName)
	// 3. 打开一个新的日志文件
	fileObj, err := os.OpenFile(logName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open log file failed, err:%v", err)
		return nil, err
	}
	// 4. 将打开的文件赋值给 fl.FileObj
	return fileObj, nil
}

func (fl *FileLogger) Debug(format string, a ...interface{}) {
	fl.Print(DebugLevel, format, a...)
}

func (fl *FileLogger) Trace(format string, a ...interface{}) {
	fl.Print(TraceLevel, format, a...)
}

func (fl *FileLogger) Info(format string, a ...interface{}) {
	fl.Print(InfoLevel, format, a...)
}

func (fl *FileLogger) Warning(format string, a ...interface{}) {
	fl.Print(WarningLevel, format, a...)
}

func (fl *FileLogger) Error(format string, a ...interface{}) {
	fl.Print(ErrorLevel, format, a...)
}

func (fl *FileLogger) Fatal(format string, a ...interface{}) {
	fl.Print(FatalLevel, format, a...)
}

使用示例

1. console打印

package main

import "go_Logger/mylogger"

var myLog mylogger.Logger

func main() {
	myLog := mylogger.NewConsoleLog("debug")
	myLog.SetPrefix("[MY-LOG]")
	
	myLog.Debug("这是一条Debug日志")
	myLog.Trace("这是一条Trace日志")
	myLog.Info("这是一条Info日志")
	myLog.Warning("这是一条Warning日志")
	name := "zhangyafei"
	myLog.Error("这是一条Error日志, name:%s", name)
	myLog.Fatal("这是一条Fatal日志")
}

终端

E:goprojectsrcgo_Logger>go_Logger.exe
[MY-LOG] 2020/06/11 - 12:22:04 ? Debug E:/go/project/src/go_Logger/main.go:main:25 这是一条Debug日志
[MY-LOG] 2020/06/11 - 12:22:04 ? Trace E:/go/project/src/go_Logger/main.go:main:26 这是一条Trace日志
[MY-LOG] 2020/06/11 - 12:22:04 ? Info E:/go/project/src/go_Logger/main.go:main:27 这是一条Info日志
[MY-LOG] 2020/06/11 - 12:22:04 ? Warning E:/go/project/src/go_Logger/main.go:main:28 这是一条Warning日志
[MY-LOG] 2020/06/11 - 12:22:04 ? Error E:/go/project/src/go_Logger/main.go:main:30 这是一条Error日志, name:zhangyafei
[MY-LOG] 2020/06/11 - 12:22:04 ? Fatal E:/go/project/src/go_Logger/main.go:main:31 这是一条Fatal日志

2. 写入文件-按文件大小切割

package main

import "go_Logger/mylogger"

var myLog mylogger.Logger

func main() {
	myLog = mylogger.NewFileLog("info", "log", "mylog.log", 10*1024*1024)
	myLog.SetPrefix("[MY-LOG]")
	
	myLog.Debug("这是一条Debug日志")
	myLog.Trace("这是一条Trace日志")
	myLog.Info("这是一条Info日志")
	myLog.Warning("这是一条Warning日志")
	name := "zhangyafei"
	myLog.Error("这是一条Error日志, name:%s", name)
	myLog.Fatal("这是一条Fatal日志")
}

log/mylog.log

[MY-LOG] 2020/06/11 - 12:24:20 ? Info E:/go/project/src/go_Logger/main.go:main:27 这是一条Info日志
[MY-LOG] 2020/06/11 - 12:24:20 ? Warning E:/go/project/src/go_Logger/main.go:main:28 这是一条Warning日志
[MY-LOG] 2020/06/11 - 12:24:20 ? Error E:/go/project/src/go_Logger/main.go:main:30 这是一条Error日志, name:zhangyafei
[MY-LOG] 2020/06/11 - 12:24:20 ? Fatal E:/go/project/src/go_Logger/main.go:main:31 这是一条Fatal日志

log/mylog.log.err

[MY-LOG] 2020/06/11 - 12:24:20 ? Error E:/go/project/src/go_Logger/main.go:main:30 这是一条Error日志, name:zhangyafei
[MY-LOG] 2020/06/11 - 12:24:20 ? Fatal E:/go/project/src/go_Logger/main.go:main:31 这是一条Fatal日志

文件大小切割

package main

import "go_Logger/mylogger"

var myLog mylogger.Logger

func main() {
	myLog = mylogger.NewFileLog("info", "log", "mylog.log", 10*1024*1024)
	myLog.SetPrefix("[MY-LOG]")
	for {
		myLog.Debug("这是一条Debug日志")
		myLog.Trace("这是一条Trace日志")
		myLog.Info("这是一条Info日志")
		myLog.Warning("这是一条Warning日志")
		name := "zhangyafei"
		myLog.Error("这是一条Error日志, name:%s", name)
		myLog.Fatal("这是一条Fatal日志")
	}
}  

技术图片

3. 写入文件-按文件大小和时间切割

package main

import "go_Logger/mylogger"

var myLog mylogger.Logger

func main() {
	myLog = mylogger.NewFileLogWithMaxAge("Debug", "log", "mylog.log", 10*1024*1024, 24)
	myLog.SetPrefix("[MY-LOG]")
	for {
		myLog.Debug("这是一条Debug日志")
		myLog.Trace("这是一条Trace日志")
		myLog.Info("这是一条Info日志")
		myLog.Warning("这是一条Warning日志")
		name := "zhangyafei"
		myLog.Error("这是一条Error日志, name:%s", name)
		myLog.Fatal("这是一条Fatal日志")
	}
}

 

以上是关于Go语言系列之自定义实现日志库的主要内容,如果未能解决你的问题,请参考以下文章

Go语言系列之标准库log

Go语言标准库之log

go语言标准库之log

go语言碎片整理之标准库log

go语言碎片整理之标准库log

Go语言系列之日志库zap