日志

使用Logger

Print系列Fatal系列Panic系列
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

log
logFlagsSetFlags
func Flags() int
func SetFlags(flag int)

flag选项

log

下面我们在记录日志之前先设置一下标准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)
PrefixSetPrefix
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
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("这是一条很普通的日志。")
}
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

logNewNew
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)
   }
}