【原创】go语言学习(十七)接口应用实战--日志库

目前

  • 日志库需求分析
  • 日志库接口设计
  • 文件日志库开发
  • Console日志开发
  • 日志使用以及测试

日志库需求分析

1、日志库需求分析

A. 程序运行是个黑盒
B. 而日志是程序运行的外在表现
C. 通过日志,可以知道程序的健康状态

2、日志库需求分析

A. Debug级别:用来调试程序,日志最详细。对程序性能影响比较大。
B. Trace级别:用来追踪问题。
C. Info级别:打印程序运行过程中比较重要的信息,比如访问日志
D. Warn级别:警告日志,说明程序运行出现了潜在的问题
E. Error级别:错误日志,程序运行发生错误,但不影响程序运行。
F. Fatal级别:严重错误日志,发生的错误会导致程序退出

3、日志库需求分析

A. 直接输出到控制台
B. 打印到文件里
C. 直接打印到网络中,比如kafka

 

日志库接口设计

1、为什么使用接口?

A. 定义日志库的规范或者标准
B. 易于可扩展性
C. 利于程序的可维护性

2、 日志库设计

A. 打印各个level的日志
B. 设置级别
C. 构造函数

文件日志库开发

1、关键代码

package logger

import (
	"fmt"
	"os"
	"strconv"
	"time"
)

type FileLogger struct {
	level    int
	logPath  string
	logName  string
	file     *os.File
	warnFile *os.File
	// 异步通道
	LogDataChan chan *LogData
	// 日志切分
	logSplitType  int
	logSplitSize  int64
	lastSplitHour int
}

func NewFileLogger(config map[string]string) (log LogInterface, err error) {
	logPath, ok := config["log_path"]
	if !ok {
		err = fmt.Errorf("not found log_path")
		return
	}
	logName, ok := config["log_name"]
	if !ok {
		err = fmt.Errorf("not found log_name")
		return
	}
	logLevel, ok := config["log_level"]
	if !ok {
		err = fmt.Errorf("not found log_level")
		return
	}
	level := getLogLevel(logLevel)

	// 异步管道初始化
	logChanSize, ok := config["log_chan_size"]
	if !ok {
		logChanSize = "50000"
	}
	//检查logChanSize转数字
	chanSize, err := strconv.Atoi(logChanSize)
	if err != nil {
		chanSize = 50000
	}

	//日志切片
	var logSplitType int = LogSplitTypeHour
	var logSplitSize int64
	logSplitStr, ok := config["log_plit_type"]
	if !ok {
		logSplitStr = "hour"
	} else {
		if logSplitStr == "size" {
			logSplitSizeStr, ok := config["log_split_size"]
			if !ok {
				logSplitSizeStr = "104857600"
			}

			logSplitSize, err = strconv.ParseInt(logSplitSizeStr, 10, 64)
			if err != nil {
				logSplitSize = 104857600
			}

			logSplitType = LogSplitTypeSize
		} else {
			logSplitType = LogSplitTypeHour
		}
	}

	log = &FileLogger{
		level:   level,
		logPath: logPath,
		logName: logName,
		// 异步写日志
		LogDataChan: make(chan *LogData, chanSize),
		// 日志切分
		logSplitSize:  logSplitSize,
		logSplitType:  logSplitType,
		lastSplitHour: time.Now().Hour(),
	}

	// 调试日志分割
	// fmt.Println(logSplitSize, logSplitType, logSplitStr)

	log.Init()
	return
}

func (f *FileLogger) SetLevel(level int) {
	if level < LogLevelDebug || level > LogLevelFatal {
		level = LogLevelDebug
	}
	f.level = level
}

func (f *FileLogger) Init() {
	filename := fmt.Sprintf("%s/%s.log", f.logPath, f.logName)
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	if err != nil {
		panic(fmt.Sprintf("open faile %s failed, err:%v", filename, err))
	}

	f.file = file

	// 写错误日志和fatal日志文件
	filename = fmt.Sprintf("%s/%s.log.wf", f.logPath, f.logName)
	file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	if err != nil {
		panic(fmt.Sprintf("open faile %s failed, err:%v", filename, err))
	}
	f.warnFile = file
	go f.writeLogBackground()
}

func (f *FileLogger) splitFileHour(warnFile bool) {
	now := time.Now()
	hour := now.Hour()
	if hour == f.lastSplitHour {
		return
	}

	f.lastSplitHour = hour
	var backupFilename string
	var filename string

	if warnFile {
		backupFilename = fmt.Sprintf("%s/%s.log.wf_%4d%02d%02d%02d",
			f.logPath, f.logName, now.Year(), now.Month(), now.Day(), f.lastSplitHour)

		filename = fmt.Sprintf("%s/%s.log.wf", f.logPath, f.logName)
	} else {
		backupFilename = fmt.Sprintf("%s/%s.log_%4d%02d%02d%02d",
			f.logPath, f.logName, now.Year(), now.Month(), now.Day(), f.lastSplitHour)
		filename = fmt.Sprintf("%s/%s.log", f.logPath, f.logName)
	}

	file := f.file
	if warnFile {
		file = f.warnFile
	}

	file.Close()
	os.Rename(filename, backupFilename)

	file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	if err != nil {
		return
	}

	if warnFile {
		f.warnFile = file
	} else {
		f.file = file
	}
}

func (f *FileLogger) splitFileSize(warnFile bool) {

	file := f.file
	if warnFile {
		file = f.warnFile
	}

	statInfo, err := file.Stat()
	if err != nil {
		return
	}

	fileSize := statInfo.Size()
	if fileSize <= f.logSplitSize {
		return
	}

	var backupFilename string
	var filename string

	now := time.Now()
	if warnFile {
		backupFilename = fmt.Sprintf("%s/%s.log.wf_%4d%02d%02d%02d%02d%02d",
			f.logPath, f.logName, now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

		filename = fmt.Sprintf("%s/%s.log.wf", f.logPath, f.logName)
	} else {
		backupFilename = fmt.Sprintf("%s/%s.log_%4d%02d%02d%02d",
			f.logPath, f.logName, now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
		filename = fmt.Sprintf("%s/%s.log", f.logPath, f.logName)
	}

	file.Close()
	os.Rename(filename, backupFilename)

	file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	if err != nil {
		return
	}

	if warnFile {
		f.warnFile = file
	} else {
		f.file = file
	}
}

func (f *FileLogger) checkSplitFile(warnFile bool) {
	if f.logSplitType == LogSplitTypeHour {
		f.splitFileHour(warnFile)
		return
	}

	f.splitFileSize(warnFile)
}

func (f *FileLogger) writeLogBackground() {
	for data := range f.LogDataChan {
		var file *os.File = f.file
		if data.WarnAndFatal {
			file = f.warnFile
		}

		f.checkSplitFile(data.WarnAndFatal)

		fmt.Fprintf(file, "%s %s (%s:%s:%d) %s\n",
			data.LevelStr, data.FileName, data.FuncName, data.LineNo,
			data.Message)
	}
}

func (f *FileLogger) Debug(format string, args ...interface{}) {
	if f.level > LogLevelDebug {
		return
	}
	logData := writeLog(LogLevelDebug, format, args...)
	// 判断队列有没有满
	select {
	case f.LogDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Trace(format string, args ...interface{}) {
	if f.level > LogLevelTrace {
		return
	}
	logData := writeLog(LogLevelTrace, format, args...)
	// 判断队列有没有满
	select {
	case f.LogDataChan <- logData:
	default:
	}

}

func (f *FileLogger) Info(format string, args ...interface{}) {
	if f.level > LogLevelInfo {
		return
	}
	logData := writeLog(LogLevelInfo, format, args...)
	// 判断队列有没有满
	select {
	case f.LogDataChan <- logData:
	default:
	}

}

func (f *FileLogger) Warn(format string, args ...interface{}) {
	if f.level > LogLevelWarn {
		return
	}
	logData := writeLog(LogLevelWarn, format, args...)
	// 判断队列有没有满
	select {
	case f.LogDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Fatal(format string, args ...interface{}) {
	if f.level > LogLevelFatal {
		return
	}
	logData := writeLog(LogLevelFatal, format, args...)
	// 判断队列有没有满
	select {
	case f.LogDataChan <- logData:
	default:
	}

}

func (f *FileLogger) Error(format string, args ...interface{}) {
	if f.level > LogLevelError {
		return
	}
	logData := writeLog(LogLevelError, format, args...)
	// 判断队列有没有满
	select {
	case f.LogDataChan <- logData:
	default:
	}

}

func (f *FileLogger) Close() {
	f.file.Close()
	f.warnFile.Close()
}

 

Console日志开发

1、关键代码

package logger

import (
	"fmt"
	"os"
)

type ConsoleLogger struct {
	level int
}

func NewConsoleLogger(config map[string]string) (log LogInterface, err error) {
	logLevel, ok := config["log_level"]
	if !ok {
		err = fmt.Errorf("not found log_level")
		return
	}
	level := getLogLevel(logLevel)
	log = &ConsoleLogger{
		level: level,
	}
	return
}

func (c *ConsoleLogger) Init() {

}

func (c *ConsoleLogger) SetLevel(level int) {
	if level < LogLevelDebug || level > LogLevelFatal {
		level = LogLevelDebug
	}
	c.level = level
}

func (c *ConsoleLogger) Debug(format string, args ...interface{}) {
	if c.level > LogLevelDebug {
		return
	}
	logData := writeLog(LogLevelDebug, format, args...)
	fmt.Fprintf(os.Stdout, "%s %s (%s:%s:%d) %s\n",
		logData.LevelStr, logData.FileName, logData.FuncName, logData.LineNo,
		logData.Message)
}

func (c *ConsoleLogger) Trace(format string, args ...interface{}) {
	if c.level > LogLevelTrace {
		return
	}
	logData := writeLog(LogLevelTrace, format, args...)
	fmt.Fprintf(os.Stdout, "%s %s (%s:%s:%d) %s\n",
		logData.LevelStr, logData.FileName, logData.FuncName, logData.LineNo,
		logData.Message)

}

func (c *ConsoleLogger) Info(format string, args ...interface{}) {
	if c.level > LogLevelInfo {
		return
	}
	logData := writeLog(LogLevelInfo, format, args...)
	fmt.Fprintf(os.Stdout, "%s %s (%s:%s:%d) %s\n",
		logData.LevelStr, logData.FileName, logData.FuncName, logData.LineNo,
		logData.Message)

}

func (c *ConsoleLogger) Warn(format string, args ...interface{}) {
	if c.level > LogLevelWarn {
		return
	}
	logData := writeLog(LogLevelWarn, format, args...)
	fmt.Fprintf(os.Stdout, "%s %s (%s:%s:%d) %s\n",
		logData.LevelStr, logData.FileName, logData.FuncName, logData.LineNo,
		logData.Message)

}

func (c *ConsoleLogger) Fatal(format string, args ...interface{}) {
	if c.level > LogLevelFatal {
		return
	}
	logData := writeLog(LogLevelFatal, format, args...)
	fmt.Fprintf(os.Stdout, "%s %s (%s:%s:%d) %s\n",
		logData.LevelStr, logData.FileName, logData.FuncName, logData.LineNo,
		logData.Message)

}

func (c *ConsoleLogger) Error(format string, args ...interface{}) {
	if c.level > LogLevelError {
		return
	}
	logData := writeLog(LogLevelError, format, args...)
	fmt.Fprintf(os.Stdout, "%s %s (%s:%s:%d) %s\n",
		logData.LevelStr, logData.FileName, logData.FuncName, logData.LineNo,
		logData.Message)

}

func (c *ConsoleLogger) Close() {
}

  

日志使用以及测试

1、关键代码

package logger

import "testing"

// run test| debug test
func TestFileLogger(t *testing.T) {
	logger := NewFileLogger(LogLevelDebug, "d:/logs/", "test")
	logger.Debug("user id[%d] is come from chaina", 3242342)
	logger.Warn("test warn log")
	logger.Fatal("test fatal log")
	logger.Close()
}

// run test| debug test
func TestConsoleLogger(t *testing.T) {
	logger := NewConsoleLogger(LogLevelDebug)
	logger.Debug("user id[%d] is come from chaina", 3242342)
	logger.Warn("test warn log")
	logger.Fatal("test fatal log")
	logger.Close()
}

  

 

你可能感兴趣的:(【原创】go语言学习(十七)接口应用实战--日志库)