理解底层— —Golang的log库,二开实现自定义Logger

理解底层— —Golang的log库,实现自定义Logger

1 分析实现思路

基于golang中自带的log库实现:对日志实现设置日志级别,每天生成一个文件,同时添加上前缀以及展示文件名等

  • 日志级别,通过添加prefix:[INFO]、[DEBUG]等来实现
  • 每天生成一个日志文件:写日志之前判断当前时间是否为新的一天
  • 日志文件命名:获取每天的时间来实现命名,同时添加读写锁保证并发安全
  • 获取调用日志文件代码行数及文件名:runtime.Caller获取函数调用栈

2 实战

2.1 server.go

package main

import "myTest/inter/log_pro/logger"

func main() {
	logger.SetFile("/Users/xsky/GolandProjects/MyTest/inter/log_pro/log/demo.log")
	logger.SetLevel(0)

	logger.Debug("hello %s", "ziyi")
	logger.Info("hello %s", "ziyi")
}

2.2 logger.go

package logger

import (
	"log"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

//基于log库自定义实现logger
var (
	infoLogger  *log.Logger
	debugLogger *log.Logger

	logOut     *os.File
	logLevel   int
	currentDay int //每天生成一个日志文件
	logFile    string
	fileLock   sync.RWMutex //读写锁,保证同一时间只有一个协程重命名文件
)

const (
	DebugLevel = iota //0
	InfoLevel         //1
)

func SetLevel(level int) {
	logLevel = level
}

func init() {
	fileLock = sync.RWMutex{}
}

func SetFile(file string) {
	var err error
	logOut, err = os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
	if err != nil {
		panic(err)
	} else {
		//初始化自定义的Logger(基于golang中的log库)
		//log.LstdFlags表示时间格式等
		//log.Llongfile表示文件名及调用代码的位置,log.Llongfile=》改为通过getCallTrace获取前缀
		currentDay = time.Now().YearDay()
		infoLogger = log.New(logOut, "[INFO] ", log.LstdFlags)
		debugLogger = log.New(logOut, "[DEBUG] ", log.LstdFlags)
		logFile = file
	}
}

func checkIfDayChange() {
	fileLock.Lock()
	defer fileLock.Unlock()
	day := time.Now().YearDay()
	if day == currentDay {
		return
	} else {
		//关闭之前的文件,重命名,并生成新的文件
		logOut.Close()
		postFix := time.Now().Add(-24 * time.Hour).Format("20060102")
		err := os.Rename(logFile, logFile+"."+postFix)
		if err != nil {
			//TODO 重命名日志文件失败,根据自身情况做处理
		}
		logOut, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
		if err != nil {
			//TODO 打开新的日志文件失败,根据自己业务需求做处理
		}
		infoLogger = log.New(logOut, "[INFO] ", log.LstdFlags)
		debugLogger = log.New(logOut, "[DEBUG] ", log.LstdFlags)
		currentDay = day
	}
}

//golang中的any相当于interface{}空接口
func Debug(format string, v ...any) {
	if logLevel <= DebugLevel {
		checkIfDayChange()
		debugLogger.Printf(getPrefix()+format, v)
	}
}

func Info(format string, v ...any) {
	if logLevel <= InfoLevel {
		checkIfDayChange()
		infoLogger.Printf(getPrefix()+format, v)
	}
}

//获取函数调用栈关系:拿到调用Info或者Debug所在的文件名及代码行数(runtime包)
func getCallTrace() (string, int) {
	_, file, lineNo, ok := runtime.Caller(3)
	if ok {
		return file, lineNo
	} else {
		return "", 0
	}
}

//获取调用Info、Debug代码所在行数,文件名只获取最后三级
func getPrefix() string {
	file, lineNo := getCallTrace()
	path := strings.Split(file, "/")
	if len(path) > 3 {
		file = strings.Join(path[len(path)-3:], "/")
	}
	return file + ":" + strconv.Itoa(lineNo) + " "
}

3 效果

运行server.go查看效果:

理解底层— —Golang的log库,二开实现自定义Logger_第1张图片

目录结构:
理解底层— —Golang的log库,二开实现自定义Logger_第2张图片

你可能感兴趣的:(go,demo,golang,开发语言,后端,log,自定义日志,框架二次封装)