【Go语言学习】——简易日志库

日志库


  • 需求分析

    1. 支持往不同的地方输出日志
    2. 日志分多种级别:Debug、Trace、Info、Warning、Error、Fatal
    3. 日志支持控制不同级别的日志开关,支撑开发和上线运行的不同期间支持不同类型的日志(如开发时支持所有日志,上线后只支持INFO级别及以下)
    4. 日志展示时间、行号、文件名、日志级别、 日志信息
    5. 日志文件要切割(按照文件大小/日期切割),按照文件大小只需要每次切割前查看文件大小是否达到最大文件,按照日期则可以再结构体设置标志位记录上一次切割的小时数,每次判断当前的时间的小时数和之前的是否一致。
  • runtime.Caller

    调用runtime.Caller可以查看调用函数的文件地址,函数名,以及行号,注意参数中的数字表示向上返回多少级的意思

    package main
    
    import (
    	"fmt"
    	"path"
    	"runtime"
    )
    
    func f2() {
    	//2表示向上返回两级,表示调用这个函数的函数,然后再向上找这个函数的调用函数
    	pc, file, line, ok := runtime.Caller(2)
    	if !ok {
    		fmt.Println("runtime.Caller() failed")
    		return
    	}
    	funcName := runtime.FuncForPC(pc).Name()
    	fmt.Println(funcName)
    	fmt.Println(file)
    	fmt.Println(path.Base(file)) //只要当前文件的名字
    	fmt.Println(line)
    }
    func f1() {
    	//1表示向上返回一级,返回的是调用这个函数的函数的信息
    	pc, file, line, ok := runtime.Caller(1)
    	if !ok {
    		fmt.Println("runtime.Caller() failed")
    		return
    	}
    	funcName := runtime.FuncForPC(pc).Name()
    	fmt.Println(funcName)
    	fmt.Println(file)
    	fmt.Println(path.Base(file)) //只要当前文件的名字
    	fmt.Println(line)
    	f2()
    }
    
    //runtime.Caller返回调用函数名,所在文件名以及行号,参数则是确认返回的是当前函数还是调用这个函数的函数
    func main() {
    	//0表示就是当前函数
    	pc, file, line, ok := runtime.Caller(0)
    	if !ok {
    		fmt.Println("runtime.Caller() failed")
    		return
    	}
    	funcName := runtime.FuncForPC(pc).Name()
    	fmt.Println(funcName) //返回文件名.函数名
    	fmt.Println(file)
    	fmt.Println(path.Base(file)) //只要当前文件的名字
    	fmt.Println(line)
    	f1()
    }
    
    
  • fileStat

    调用文件类型的Stat()可以查看当前的文件信息,如大小,名称等

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    func main() {
    	fileObj, err := os.Open("./main.go")
    	if err != nil {
    		fmt.Printf("open file failed err:%v", err)
    		return
    	}
    	fileInfo, err := fileObj.Stat()
    	if err != nil {
    		fmt.Printf("get fileInfo failed err:%v", err)
    		return
    	}
    	fmt.Printf("文件大小是:%dB\n", fileInfo.Size())
    	fmt.Printf("文件名是:%s\n", fileInfo.Name())
    }
    
    
  • 日志库公共插件

    建立接口,常量,级别设置,设置格式化输出内容

    package mylogger
    
    import (
    	"errors"
    	"fmt"
    	"path"
    	"runtime"
    	"strings"
    )
    
    type LogLevel uint16
    
    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{})
    }
    
    //设置日志级别
    const (
    	UNKNOWN LogLevel = iota
    	DEBUG
    	TRACE
    	INFO
    	WARNING
    	ERROR
    	FATAL
    )
    
    //将日志级别转换为内置的级别
    func parseLogLevel(s string) (LogLevel, error) {
    	s = strings.ToLower(s)
    	switch s {
    	case "debug":
    		return DEBUG, nil
    	case "trace":
    		return TRACE, nil
    	case "info":
    		return INFO, nil
    	case "warning":
    		return WARNING, nil
    	case "error":
    		return ERROR, nil
    	case "fatal":
    		return FATAL, nil
    	default:
    		err := errors.New("无效的日志级别")
    		return UNKNOWN, err
    	}
    }
    
    func getLogString(lv LogLevel) string {
    	switch lv {
    	case DEBUG:
    		return "DEBUG"
    	case TRACE:
    		return "TRACE"
    	case INFO:
    		return "INFO"
    	case WARNING:
    		return "WARNING"
    	case ERROR:
    		return "ERROR"
    	case FATAL:
    		return "FATAL"
    	default:
    		return "UNKNOWN"
    	}
    }
    
    func getInfo(skip int) (funcName, fileName string, lineNo int) {
    	pc, file, lineNo, ok := runtime.Caller(skip)
    	if !ok {
    		fmt.Println("runtime.Caller() failed")
    		return
    	}
    	funcName = runtime.FuncForPC(pc).Name()
    	//只要函数名,不要上面的文件名
    	funcName = strings.Split(funcName, ".")[1]
    	fileName = path.Base(file)
    	return
    }
    
  • 日志库终端版

    package mylogger
    
    import (
    	"fmt"
    	"time"
    )
    
    // 往终端写日志内容
    
    //ConsoleLogger日志结构体
    
    type ConsoleLogger struct {
    	Level LogLevel
    }
    
    //Logger构造函数
    func NewConsoleLog(levelStr string) ConsoleLogger {
    	level, err := parseLogLevel(levelStr)
    	if err != nil {
    		panic(err)
    	}
    	return ConsoleLogger{
    		Level: level,
    	}
    }
    
    func (c ConsoleLogger) log(lv LogLevel, format string, a ...interface{}) {
    	if c.enable(lv) {
    		msg := fmt.Sprintf(format, a...)
    		now := time.Now()
    		funcName, fileName, lineNo := getInfo(3)
    		fmt.Printf("[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
    	}
    }
    
    func (c ConsoleLogger) enable(loglevel LogLevel) bool {
    	return c.Level <= loglevel
    }
    
    func (c ConsoleLogger) Debug(format string, a ...interface{}) {
    	c.log(DEBUG, format, a...)
    }
    
    func (c ConsoleLogger) Trace(format string, a ...interface{}) {
    	c.log(TRACE, format, a...)
    }
    func (c ConsoleLogger) Info(format string, a ...interface{}) {
    	c.log(INFO, format, a...)
    }
    func (c ConsoleLogger) Warning(format string, a ...interface{}) {
    	c.log(WARNING, format, a...)
    }
    func (c ConsoleLogger) Error(format string, a ...interface{}) {
    	c.log(ERROR, format, a...)
    }
    func (c ConsoleLogger) Fatal(format string, a ...interface{}) {
    	c.log(FATAL, format, a...)
    }
    
    
  • 日志库文件版

    支持按照文件大小/时间进行切割

    package mylogger
    
    import (
    	"fmt"
    	"os"
    	"path"
    	"time"
    )
    
    // 往文件里面写日志
    
    //日志文件结构体
    type FileLogger struct {
    	Level       LogLevel
    	filePath    string //日志文件保存的路径
    	fileName    string //日志文件保存的文件名
    	fileObj     *os.File
    	errFileObj  *os.File
    	maxFileSize int64 //最大文件大小
    	timeFlag    int   //日志时间标志
    	timeErrFlag int   //错误日志时间标志
    }
    
    //FileLogger构造函数
    func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
    	LogLevel, err := parseLogLevel(levelStr)
    	if err != nil {
    		panic(err)
    	}
    	tf := time.Now().Minute()
    	tef := time.Now().Minute()
    	fl := &FileLogger{
    		Level:       LogLevel,
    		filePath:    fp,
    		fileName:    fn,
    		maxFileSize: maxSize,
    		timeFlag:    tf,
    		timeErrFlag: tef,
    	}
    	//按照文件路径和文件名将文件打开
    	err = fl.initFile()
    	if err != nil {
    		panic(err)
    	}
    	return fl
    }
    
    //根据指定的日志文件路径和文件名打开对应日志和错误日志
    func (f *FileLogger) initFile() error {
    	//将文件路径和文件名按照操作系统的格式进行拼接
    	fullFileName := path.Join(f.filePath, f.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\n", 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 errlog file failed,err:%v\n", err)
    		return err
    	}
    	f.fileObj = fileObj
    	f.errFileObj = errfileObj
    	return nil
    }
    
    func (f *FileLogger) Close() {
    	f.fileObj.Close()
    	f.errFileObj.Close()
    }
    
    //根据级别确定是否需要记录该日志
    func (f *FileLogger) enable(loglevel LogLevel) bool {
    	return f.Level <= loglevel
    }
    
    // 判断文件大小
    // func (f *FileLogger) checkSize(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() >= f.maxFileSize
    // }
    
    func (f *FileLogger) checkTime(t int) bool {
    	nowMinute := time.Now().Minute()
    	return nowMinute != t
    }
    
    //切割文件,文件达到最大后就重新生产新的文件
    func (f *FileLogger) splitFile(file *os.File) (*os.File, error) {
    	//获取旧文件信息和生成新文件信息
    	nowStr := time.Now().Format("20060102150405000")
    	fileInfo, err := file.Stat()
    	if err != nil {
    		fmt.Printf("get fileInfo failed,err:%v", err)
    		return nil, err
    	}
    	//不能用fileLogger里的文件名,没有err
    	//使用file查看文件名区分普通日志和错误日志
    	logName := path.Join(f.filePath, fileInfo.Name())
    	newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr)
    	// 1.关闭当前的日志文件,才能重命名
    	file.Close()
    	// 2.将已满的文件重新命名,添加上时间
    	os.Rename(logName, newLogName)
    	// 3.打开一个新的日志文件
    	fileObj, err := os.OpenFile(logName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    	if err != nil {
    		fmt.Printf("open new log file failed,err:%v", err)
    		return nil, err
    	}
    	// 4.将新打开的日志文件对象赋值给 f.fileObj
    	return fileObj, nil
    }
    
    //按照文件大写切割日志,每次记录日志之前都要判断下当前写的文件的大小
    // func (f *FileLogger) log(lv LogLevel, format string, a ...interface{}) {
    // 	if f.enable(lv) {
    // 		msg := fmt.Sprintf(format, a...)
    // 		now := time.Now()
    // 		funcName, fileName, lineNo := getInfo(3)
    // 		//根据文件大小判断结果确认是否切割文件
    // 		if f.checkSize(f.fileObj) {
    // 			newFile, err := f.splitFile(f.fileObj)
    // 			if err != nil {
    // 				return
    // 			}
    // 			f.fileObj = newFile
    // 		}
    // 		fmt.Fprintf(f.fileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
    // 		//记录日志等级大于Error级别,需要再err日志中再记录一遍
    // 		if lv >= ERROR {
    // 			if f.checkSize(f.errFileObj) {
    // 				newFile, err := f.splitFile(f.errFileObj)
    // 				if err != nil {
    // 					return
    // 				}
    // 				f.errFileObj = newFile
    // 			}
    // 			fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
    // 		}
    
    // 	}
    // }
    
    //按照时间切割日志,每次记录日志之前都要当前时间
    func (f *FileLogger) log(lv LogLevel, format string, a ...interface{}) {
    	if f.enable(lv) {
    		msg := fmt.Sprintf(format, a...)
    		now := time.Now()
    		funcName, fileName, lineNo := getInfo(3)
    		//根据时间判断结果确认是否切割文件
    		if f.checkTime(f.timeFlag) {
    			newFile, err := f.splitFile(f.fileObj)
    			if err != nil {
    				return
    			}
    			f.fileObj = newFile
    			f.timeFlag = time.Now().Minute()
    		}
    		fmt.Fprintf(f.fileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
    		//记录日志等级大于Error级别,需要再err日志中再记录一遍
    		if lv >= ERROR {
    			if f.checkTime(f.timeErrFlag) {
    				newFile, err := f.splitFile(f.errFileObj)
    				if err != nil {
    					return
    				}
    				f.errFileObj = newFile
    				f.timeErrFlag = time.Now().Minute()
    			}
    			fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
    		}
    	}
    }
    
    func (f *FileLogger) Debug(format string, a ...interface{}) {
    	f.log(DEBUG, format, a...)
    }
    
    func (f *FileLogger) Trace(format string, a ...interface{}) {
    	f.log(TRACE, format, a...)
    }
    func (f *FileLogger) Info(format string, a ...interface{}) {
    	f.log(INFO, format, a...)
    }
    func (f *FileLogger) Warning(format string, a ...interface{}) {
    	f.log(WARNING, format, a...)
    }
    func (f *FileLogger) Error(format string, a ...interface{}) {
    	f.log(ERROR, format, a...)
    }
    func (f *FileLogger) Fatal(format string, a ...interface{}) {
    	f.log(FATAL, format, a...)
    }
    
    
  • 日志库主函数

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    func main() {
    	fileObj, err := os.Open("./main.go")
    	if err != nil {
    		fmt.Printf("open file failed err:%v", err)
    		return
    	}
    	fileInfo, err := fileObj.Stat()
    	if err != nil {
    		fmt.Printf("get fileInfo failed err:%v", err)
    		return
    	}
    	fmt.Printf("文件大小是:%dB\n", fileInfo.Size())
    	fmt.Printf("文件名是:%s\n", fileInfo.Name())
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值