当前位置: 首页 > 工具软件 > Go Logger > 使用案例 >

Golang日志

巫马劲
2023-12-01

日志

使用Logger

log包定义了Logger类型,该类型提供了一些格式化输出的方法。本包也提供了一个预定义的“标准”logger,可以通过调用函数Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用,比自行创建一个logger对象更容易使用。

例如,我们可以像下面的代码一样直接通过log包来调用上面提到的方法,默认它们会将日志信息打印到终端界面:

package main

import (
	"log"
)

func main() {
	log.Println("这是一条很普通的日志。")
	v := "很普通的"
	log.Printf("这是一条%s日志。\n", v)
	log.Fatalln("这是一条会触发fatal的日志。")
	log.Panicln("这是一条会触发panic的日志。")
}

编译并执行上面的代码会得到如下输出:

2022/02/14 21:18:42 这是一条很普通的日志。
2022/02/14 21:18:42 这是一条很普通的日志。     
2022/02/14 21:18:42 这是一条会触发fatal的日志。

配置logger

默认情况下的logger只会提供日志的时间信息,但是很多情况下我们希望得到更多信息,比如记录该日志的文件名和行号等。log标准库中为我们提供了定制这些设置的方法。

log标准库中的Flags函数会返回标准logger的输出配置,而SetFlags函数用来设置标准logger的输出配置。

func Flags() int
func SetFlags(flag int)

flag选项

log标准库提供了如下的flag选项,它们是一系列定义好的常量。

const (
    // 控制输出日志信息的细节,不能控制输出的顺序和格式。
    // 输出的日志在每一项后会有一个冒号分隔:例如2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
    Ldate         = 1 << iota     // 日期:2009/01/23
    Ltime                         // 时间:01:23:23
    Lmicroseconds                 // 微秒级别的时间:01:23:23.123123(用于增强Ltime位)
    Llongfile                     // 文件全路径名+行号: /a/b/c/d.go:23
    Lshortfile                    // 文件名+行号:d.go:23(会覆盖掉Llongfile)
    LUTC                          // 使用UTC时间
    LstdFlags     = Ldate | Ltime // 标准logger的初始值
)

下面我们在记录日志之前先设置一下标准logger的输出选项如下:

func main() {
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("这是一条很普通的日志。")
}

编译执行后得到的输出结果如下:

2022/02/14 21:26:42.546952 D:/Golang/src/go_code/project02/log/test/main.go:9: 这是一条很普通的日志。

配置日志前缀

log标准库中还提供了关于日志信息前缀的两个方法:

func Prefix() string
func SetPrefix(prefix string)

其中Prefix函数用来查看标准logger的输出前缀,SetPrefix函数用来设置输出前缀。

func main() {
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("这是一条很普通的日志。")
	log.SetPrefix("[小王子]")
	log.Println("这是一条很普通的日志。")
}

上面的代码输出如下:

[小王子]2022/02/14 21:29:01.392368 D:/Golang/src/go_code/project02/log/test/main.go:11: 这是一条很普通的日志。

这样我们就能够在代码中为我们的日志信息添加指定的前缀,方便之后对日志信息进行检索和处理。

配置日志输出位置

func SetOutput(w io.Writer)

SetOutput函数用来设置标准logger的输出目的地,默认是标准错误输出。

例如,下面的代码会把日志输出到同目录下的xx.log文件中。

func main() {
	logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("open log file failed, err:", err)
		return
	}
	log.SetOutput(logFile)
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("这是一条很普通的日志。")
	log.SetPrefix("[小王子]")
	log.Println("这是一条很普通的日志。")
}

如果你要使用标准的logger,我们通常会把上面的配置操作写到init函数中。

func init() {
	logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("open log file failed, err:", err)
		return
	}
	log.SetOutput(logFile)
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
}

创建logger

log标准库中还提供了一个创建新logger对象的构造函数–New,支持我们创建自己的logger示例。New函数的签名如下:

func New(out io.Writer, prefix string, flag int) *Logger

New创建一个Logger对象。其中,参数out设置日志信息写入的目的地。参数prefix会添加到生成的每一条日志前面。参数flag定义日志的属性(时间、文件等等)。

举个例子:

func main() {
	logger := log.New(os.Stdout, "<New>", log.Lshortfile|log.Ldate|log.Ltime)
	logger.Println("这是自定义的logger记录的日志。")
}

将上面的代码编译执行之后,得到结果如下:

<New>2022/02/14 21:34:36 main.go:11: 这是自定义的logger记录的日志。

日志案例

需求分析

  1. 支持往不同的地方输出日志
  2. 日志分级别
    1. Debug
    2. Trace
    3. Info
    4. Warning
    5. Error
    6. Fatal
  3. 日志要支持开关控制,比如说开发的时候什么级别都能输出,但是上线之后只有INFO级别往下的才能输出
  4. 日志要有时间、行号、文件名、日志级别、日志信息
  5. 日志文件要切割
    1. 按文件大小切割
      1. 每次记录日志之前都判断—下当前写的这个文件的文件大小
    2. 按日期切割
      1. 在日志结构体中设置一个字段记录上一次切割的小时数
      2. 在写日志之前检查一下当前时间的小时数和之前保存的是否一致,不一致就要切割

公共函数:

package mylogger

import (
   "errors"
   "fmt"
   "path"
   "runtime"
   "strings"
)

type LogLevel uint16

//接口
type Logger interface {
   Debug(format string, a ...interface{})
   Info(format string, a ...interface{})
   Trace(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
   WARING
   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 "waring":
      return WARING, 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 WARING:
      return "WARING"
   case ERROR:
      return "ERROR"
   case FATAL:
      return "FATAL"
   }
   return "DEBUG"
}

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()
   fileName = path.Base(file)
   funcName = strings.Split(funcName, ".")[1]
   return
}

Console:

package mylogger

import (
   "fmt"
   "time"
)

//往终端上写

//日志结构体
type ConsoleLogger struct {
   Level LogLevel
}

//构造函数
func NewConsoleLogLog(levelStr string) ConsoleLogger {
   level, err := parseLogLevel(levelStr)
   if err != nil {
      panic(err)
   }
   return ConsoleLogger{
      level,
   }
}

func (l ConsoleLogger) enable(loglevel LogLevel) bool {
   return l.Level <= loglevel
}

func (l ConsoleLogger) log(lv LogLevel, format string, a ...interface{}) {
   if l.enable(DEBUG) {
      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 (l ConsoleLogger) Debug(format string, a ...interface{}) {
   l.log(DEBUG, format, a...)
}

func (l ConsoleLogger) Trace(format string, a ...interface{}) {
   l.log(TRACE, format, a...)
}

func (l ConsoleLogger) Info(format string, a ...interface{}) {
   l.log(INFO, format, a...)
}

func (l ConsoleLogger) Warning(format string, a ...interface{}) {
   l.log(WARING, format, a...)
}

func (l ConsoleLogger) Error(format string, a ...interface{}) {
   l.log(ERROR, format, a...)
}

func (l ConsoleLogger) Fatal(format string, a ...interface{}) {
   l.log(FATAL, format, a...)
}

File

package mylogger

import (
   "fmt"
   "os"
   "path"
   "time"
)

//往文件里面写日志相关代码

type FileLogger struct {
   Level       LogLevel
   filePath    string //日志文件保存的路径
   fileName    string //日志文件保存的文件名
   fileObj     *os.File
   errFileObj  *os.File
   maxFileSize int64
}

// 构造函数
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 (l *FileLogger) initFile() error {
   fullFileName := path.Join(l.filePath, l.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 err log file failed ,err:%v\n", err)
      return err
   }
   l.fileObj = fileObj
   l.errFileObj = errFileObj
   return nil
}

func (l *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
   }
   return fileInfo.Size() >= l.maxFileSize
}

func (l *FileLogger) splitFile(file *os.File) (*os.File, error) {
   //需要切割日志文件

   //2.备份一下 rename
   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 := path.Join(l.filePath, fileInfo.Name())      //拿到当前的日志文件完整路径
   newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr) //拼接一个日志文件备份的名字
   //1.关闭当前的日志文件
   file.Close()
   os.Rename(logName, newLogName)
   //3.打开一个新的日志文件
   fileObj, err := os.OpenFile(logName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
   if err != nil {
      fmt.Println("open new log fail err=", err)
      return nil, err
   }
   //4.将打开的日志文件对象赋值给 fileObj
   return fileObj, err
}

func (l *FileLogger) log(lv LogLevel, format string, a ...interface{}) {
   if l.enable(lv) {
      msg := fmt.Sprintf(format, a...)
      now := time.Now()
      funcName, fileName, lineNo := getInfo(3)
      if l.checkSize(l.fileObj) {
         newFile, err := l.splitFile(l.fileObj)
         if err != nil {
            return
         }
         l.fileObj = newFile
      }
      fmt.Fprintf(l.fileObj, "[%s] [%s] [%s : %s : %d] %s \n", now.Format("2006-01-02 15:04:05 "), getLogString(lv), fileName, funcName, lineNo, msg)

      if lv >= ERROR {
         if l.checkSize(l.errFileObj) {
            newFile, err := l.splitFile(l.errFileObj)
            if err != nil {
               return
            }
            l.errFileObj = newFile
         }
         //如果要记录的日志大于等于ERROR级别,我还要在err目志文件中再记录一遍
         fmt.Fprintf(l.errFileObj, "[%s] [%s] [%s : %s : %d] %s \n", now.Format("2006-01-02 15:04:05 "), getLogString(lv), fileName, funcName, lineNo, msg)

      }
   }
}

func (l *FileLogger) enable(loglevel LogLevel) bool {
   return l.Level <= loglevel
}

func (l *FileLogger) Debug(format string, a ...interface{}) {
   if l.enable(DEBUG) {
      l.log(DEBUG, format, a...)
   }

}

func (l *FileLogger) Trace(format string, a ...interface{}) {
   if l.enable(TRACE) {
      l.log(TRACE, format, a...)
   }
}

func (l *FileLogger) Info(format string, a ...interface{}) {
   if l.enable(INFO) {
      l.log(INFO, format, a...)
   }
}

func (l *FileLogger) Warning(format string, a ...interface{}) {
   if l.enable(WARING) {
      l.log(WARING, format, a...)
   }
}

func (l *FileLogger) Error(format string, a ...interface{}) {
   if l.enable(ERROR) {
      l.log(ERROR, format, a...)
   }
}

func (l *FileLogger) Fatal(format string, a ...interface{}) {
   if l.enable(FATAL) {
      l.log(FATAL, format, a...)
   }

}

func (l *FileLogger) Close() {
   l.fileObj.Close()
   l.errFileObj.Close()
}

main:

var log mylogger.Logger

func main() {
   log = mylogger.NewConsoleLogLog("debug")
   log = mylogger.NewFileLogger("info", "./", "1.log", 10*1024)
   for {
      id := 100
      name := "n"
      log.Debug("这是一条 Debug 日志")
      log.Info("这是一条 Info 日志")
      log.Warning("这是一条 Warning 日志")
      log.Error("这是一条 Error  日志 id:%d,name:%s", id, name)
   }
}
 类似资料: