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\n")
		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 \n", 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 \n", 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\n", 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\n", 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 \n", 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 \n", 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\n", 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\n", 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
}

 

posted @ 2020-10-11 18:00  云崖先生  阅读(637)  评论(0编辑  收藏  举报