zoukankan      html  css  js  c++  java
  • Go 自定义日志库

    日志库

       自定义一个日志库。

    知识储备

    runtime.Caller()

       该方法能够获取到打印的位置,文件的信息,行数等。

       以下是该方法的使用,不必纠结太多,照着用就行。

       唯一注意的是caller()中值的放入,该值会影响行数的显示,多测试几遍你就大概明白了。

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"path/filepath"
    )
    
    func f1() {
    	pc, file, line, ok := runtime.Caller(1) // 当被其他函数调用,则设置为1. 当无其他函数调用则设置为0
    	if !ok {
    		fmt.Println("获取信息时出错")
    		return
    	}
    	funcName := runtime.FuncForPC(pc).Name() // 获取调用该函数的函数名字
    	fmt.Println(funcName) // 打印调用者姓名
    	fmt.Println(filepath.Base(file)) // 打印文件
    	fmt.Println(line) // 打印行
    }
    
    func main() {
    	f1()
    }
    
    

    errors.New()

       该方法用于在函数中返回一个error对象时,添加一个新错误。

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    func f1() (string, error) {
    	return "哈哈哈", errors.New("新错误")
    }
    
    func main() {
    	str, err := f1()
    	fmt.Println(err)  // 新错误
    	fmt.Println(str) // 哈哈哈
    }
    
    

    具体代码

    myLogger.go

      

    package mylogger
    
    import (
    	"errors"
    	"fmt"
    	"path/filepath"
    	"runtime"
    	"strings"
    )
    
    // LogLevel 日志级别 (自类型)
    type LogLevel uint16
    
    // Logger 接口
    type Logger interface {
    	Debug(format string, args ...interface{})
    	Trace(format string, args ...interface{})
    	Info(format string, args ...interface{})
    	Warning(format string, args ...interface{})
    	Error(format string, args ...interface{})
    	Fatal(format string, args ...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 "DEBUG"
    	}
    }
    
    func getInfo(skip int) (funcName, fileName string, lineNo int) {
    	pc, file, line, ok := runtime.Caller(skip)
    	if !ok {
    		fmt.Printf("runtime.Caller() failed
    ")
    		return
    	}
    	funcName = runtime.FuncForPC(pc).Name()
    	funcName = strings.Split(funcName, ".")[1]
    	fileName = filepath.Base(file)
    	lineNo = line
    	return funcName, fileName, lineNo
    }
    
    

    file.go

    package mylogger
    
    import (
    	"fmt"
    	"os"
    	"path/filepath"
    	"time"
    )
    
    // FileLogger 往文件里面写日志相关代码
    type FileLogger struct {
    	level       LogLevel
    	filePath    string // 日志文件保存路径
    	fileName    string // 日志文件名
    	maxFileSize int64  // 最大的文件大小
    	fileObj     *os.File
    	errFileObj  *os.File
    }
    
    // NewFileLogger ...
    func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
    	logLevel, err := parseLogLevel(levelStr)
    	if err != nil {
    		panic(err)
    	}
    
    	fl := &FileLogger{
    		level:       logLevel,
    		filePath:    fp,
    		fileName:    fn,
    		maxFileSize: maxSize,
    	}
    
    	err = fl.initFile() // 打开文件,获取文件对象
    	if err != nil {
    		panic(err)
    	}
    	return fl
    }
    
    func (f *FileLogger) initFile() error {
    	// 创建记录正确的日志文件
    	fullFileName := filepath.Join(f.filePath, f.fileName)
    	fileObj, err := os.OpenFile(fullFileName+".log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    	if err != nil {
    		fmt.Printf("open log file faild,err:%v", err)
    		return err
    	}
    	// 创建错误的日志文件
    	errFileObj, err := os.OpenFile(fullFileName+"_err.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    	if err != nil {
    		fmt.Printf("open err log file faild,err:%v", err)
    		return err
    	}
    	f.fileObj = fileObj
    	f.errFileObj = errFileObj
    	return nil
    }
    
    func (f *FileLogger) enable(logLevel LogLevel) bool {
    	return f.level <= logLevel
    }
    
    func (f *FileLogger) log(lv LogLevel, format string, args ...interface{}) {
    	if f.enable(lv) {
    		msg := fmt.Sprintf(format, args...)      // 合并输出
    		funcName, fileName, lineNo := getInfo(3) // 三层调用
    		now := time.Now().Format("2006-01-02 03:04:06")
    		lvStr := getLogString(lv)
    		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 
    ", now, lvStr, fileName, funcName, lineNo, msg)
    		if lv >= ERROR {
    			if f.checkSize(f.errFileObj) {
    				newFile, err := f.splitFile(f.errFileObj)
    				if err != nil {
    					return
    				}
    				f.errFileObj = newFile
    			}
    			// 如果记录日志级别大于或等于ERROR,则再记录一份到LogErr的文件中
    			fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s 
    ", now, lvStr, fileName, funcName, lineNo, msg)
    		}
    	}
    }
    
    // Debug ...
    func (f *FileLogger) Debug(format string, args ...interface{}) {
    	f.log(DEBUG, format, args...)
    }
    
    // Trace ...
    func (f *FileLogger) Trace(format string, args ...interface{}) {
    	f.log(TRACE, format, args...)
    }
    
    // Info ...
    func (f *FileLogger) Info(format string, args ...interface{}) {
    	f.log(INFO, format, args...)
    }
    
    // Warning ...
    func (f *FileLogger) Warning(format string, args ...interface{}) {
    	f.log(WARNING, format, args...)
    }
    
    // Error ...
    func (f *FileLogger) Error(format string, args ...interface{}) {
    	f.log(ERROR, format, args...)
    }
    
    // Fatal ...
    func (f *FileLogger) Fatal(format string, args ...interface{}) {
    	f.log(FATAL, format, args...)
    }
    
    // Close 关闭文件资源
    func (f *FileLogger) Close() {
    	f.fileObj.Close()
    	f.errFileObj.Close()
    }
    
    // 获取文件大小,判断是否要进行切割
    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
    	}
    	// 如果当前文件的size大于设定的size,则返回true,否则返回false
    	return fileInfo.Size() >= f.maxFileSize
    }
    
    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 file info failed,err:%v
    ", err)
    		return nil, err
    	}
    	logName := filepath.Join(f.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
    }
    
    

    consloe.go

    package mylogger
    
    import (
    	"fmt"
    	"time"
    )
    
    // 往终端写日志
    
    // ConsoleLogger ...
    type ConsoleLogger struct {
    	level LogLevel
    }
    
    // NewConsoleLogger 构造函数 ...
    func NewConsoleLogger(levelStr string) ConsoleLogger {
    	level, err := parseLogLevel(levelStr)
    	if err != nil {
    		panic(err)
    	}
    	return ConsoleLogger{
    		level: level,
    	}
    }
    
    func (c ConsoleLogger) enable(logLevel LogLevel) bool {
    	return c.level <= logLevel
    }
    
    func (c ConsoleLogger) log(lv LogLevel, format string, args ...interface{}) {
    	if c.enable(lv) {
    		msg := fmt.Sprintf(format, args...)      // 合并输出
    		funcName, fileName, lineNo := getInfo(3) // 三层调用
    		now := time.Now().Format("2006-01-02 03:04:06")
    		lvStr := getLogString(lv)
    		fmt.Printf("[%s] [%s] [%s:%s:%d] %s 
    ", now, lvStr, fileName, funcName, lineNo, msg)
    	}
    }
    
    // Debug ...
    func (c ConsoleLogger) Debug(format string, args ...interface{}) {
    		c.log(DEBUG, format, args...)
    }
    
    // TRACE ...
    func (c ConsoleLogger) Trace(format string, args ...interface{}) {
    	c.log(TRACE, format, args...)
    }
    
    // Info ...
    func (c ConsoleLogger) Info(format string, args ...interface{}) {
    	c.log(INFO, format, args...)
    }
    
    // Warning ...
    func (c ConsoleLogger) Warning(format string, args ...interface{}) {
    	c.log(WARNING, format, args...)
    }
    
    // Error ...
    func (c ConsoleLogger) Error(format string, args ...interface{}) {
    	c.log(ERROR, format, args...)
    
    }
    
    // Fatal ...
    func (c ConsoleLogger) Fatal(format string, args ...interface{}) {
    	c.log(FATAL, format, args...)
    }
    
    
    
    

    使用案例

       日志库分为往屏幕打印与往文件写入两种。

       通过构造函数实例化不同的对象然后进行操作,支持格式化。

       支持文件切割,可指定每个日志文件的大小。

    package main
    
    import (
    	mylogger "yunya.com/module"
    )
    
    
    func main() {
    	fileLog := mylogger.NewFileLogger("debug", "./", "test", 3*1024) // 向文件打印
    	consoleLog := mylogger.NewConsoleLogger("debug")
    	
    	for {
    		fileLog.Debug("Debug%v", "试试")
    		fileLog.Info("Info")
    		fileLog.Warning("Warning")
    		fileLog.Error("Error")
    		fileLog.Fatal("Fatal")
    
    		consoleLog.Debug("Debug")
    	}
    
    }

    异步写入

      以下是对写入文件的代码进行优化。会自动开一个goroutine来写入内容。

    package mylogger
    
    import (
        "fmt"
        "os"
        "path/filepath"
        "time"
    )
    
    // FileLogger 往文件里面写日志相关代码
    type FileLogger struct {
        level       LogLevel
        filePath    string // 日志文件保存路径
        fileName    string // 日志文件名
        maxFileSize int64  // 最大的文件大小
        fileObj     *os.File
        errFileObj  *os.File
        logChan     chan *logMsg
    }
    
    type logMsg struct {
        Level     LogLevel
        msg       string
        funcName  string
        line      int
        fileName  string
        timestamp string // 时间戳
    }
    
    // NewFileLogger ...
    func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
        logLevel, err := parseLogLevel(levelStr)
        if err != nil {
            panic(err)
        }
    
        fl := &FileLogger{
            level:       logLevel,
            filePath:    fp,
            fileName:    fn,
            maxFileSize: maxSize,
            logChan:     make(chan *logMsg, 50000), // 容量大小五万的日志通道
        }
    
        err = fl.initFile() // 打开文件,获取文件对象
        if err != nil {
            panic(err)
        }
        return fl
    }
    
    func (f *FileLogger) initFile() error {
        // 创建记录正确的日志文件
        fullFileName := filepath.Join(f.filePath, f.fileName)
        fileObj, err := os.OpenFile(fullFileName+".log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
            fmt.Printf("open log file faild,err:%v", err)
            return err
        }
        // 创建错误的日志文件
        errFileObj, err := os.OpenFile(fullFileName+"_err.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
            fmt.Printf("open err log file faild,err:%v", err)
            return err
        }
        f.fileObj = fileObj
        f.errFileObj = errFileObj
        // 开启后台goroutine写日志
        go f.writeLogBackground()
        return nil
    }
    
    func (f *FileLogger) enable(logLevel LogLevel) bool {
        return f.level <= logLevel
    }
    
    func (f *FileLogger) writeLogBackground() {
    
        for {
            if f.checkSize(f.fileObj) {
                newFile, err := f.splitFile(f.fileObj)
                if err != nil {
                    return
                }
                f.fileObj = newFile
            }
    
            select {
            case logTmp := <-f.logChan:
                logInfo := fmt.Sprintf("[%s] [%s] [%s:%s:%d] %s 
    ", logTmp.timestamp, getLogString(logTmp.Level), logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.msg)
    
                fmt.Fprintf(f.fileObj, logInfo)
                if logTmp.Level >= ERROR {
                    if f.checkSize(f.errFileObj) {
                        newFile, err := f.splitFile(f.errFileObj)
                        if err != nil {
                            return
                        }
                        f.errFileObj = newFile
                    }
                    // 如果记录日志级别大于或等于ERROR,则再记录一份到LogErr的文件中
                    fmt.Fprintf(f.fileObj, logInfo)
                }
            default:
                // 等五百毫秒
                time.Sleep(time.Millisecond * 500)
            }
    
        }
    }
    
    func (f *FileLogger) log(lv LogLevel, format string, args ...interface{}) {
        if f.enable(lv) {
            msg := fmt.Sprintf(format, args...)      // 合并输出
            funcName, fileName, lineNo := getInfo(3) // 三层调用
            now := time.Now().Format("2006-01-02 03:04:06")
            // 日志发送到通道中
            logTmp := &logMsg{
                Level:     lv,
                msg:       msg,
                funcName:  funcName,
                fileName:  fileName,
                timestamp: now,
                line:      lineNo,
            }
            select {
            case f.logChan <- logTmp:
                // 信息,写入通道
            default:
                // 如果写满了通道就不写了,丢掉写不进去的日志
            }
    
        }
    }
    
    // Debug ...
    func (f *FileLogger) Debug(format string, args ...interface{}) {
        f.log(DEBUG, format, args...)
    }
    
    // Trace ...
    func (f *FileLogger) Trace(format string, args ...interface{}) {
        f.log(TRACE, format, args...)
    }
    
    // Info ...
    func (f *FileLogger) Info(format string, args ...interface{}) {
        f.log(INFO, format, args...)
    }
    
    // Warning ...
    func (f *FileLogger) Warning(format string, args ...interface{}) {
        f.log(WARNING, format, args...)
    }
    
    // Error ...
    func (f *FileLogger) Error(format string, args ...interface{}) {
        f.log(ERROR, format, args...)
    }
    
    // Fatal ...
    func (f *FileLogger) Fatal(format string, args ...interface{}) {
        f.log(FATAL, format, args...)
    }
    
    // Close 关闭文件资源
    func (f *FileLogger) Close() {
        f.fileObj.Close()
        f.errFileObj.Close()
    }
    
    // 获取文件大小,判断是否要进行切割
    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
        }
        // 如果当前文件的size大于设定的size,则返回true,否则返回false
        return fileInfo.Size() >= f.maxFileSize
    }
    
    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 file info failed,err:%v
    ", err)
            return nil, err
        }
        logName := filepath.Join(f.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
    }
  • 相关阅读:
    hdu 1863
    数据结构与算法分析–Minimum Spanning Tree(最小生成树)
    hdu 1856 More is better
    hdu 1272 小希的迷宫
    数据结构与算法分析 – Disjoint Set(并查集)
    数字逻辑电路课程设计报告
    高校成绩管理数据库系统的设计与实现
    PL/0编译器(java version) – SymbolTable.java
    [jquery]添加行内容后根据下拉菜单选择内容对比之前已有选项,若有重置再提示
    bootstrap-datetimepicker 日期控件的开始日期
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13794973.html
Copyright © 2011-2022 走看看