log
fmtlog

1、快速使用

log
package main

import (
  "log"
)

type User struct {
  Name string
  Age  int
}

func main() {
  u := User{
    Name: "test",
    Age:  18,
  }

  log.Printf("%s login, age:%d", u.Name, u.Age)
  log.Panicf("Oh, system error when %s login", u.Name)
  log.Fatalf("Danger! hacker %s login", u.Name)
}
logstderrlog
log
Print/Printf/PrintlnPanic/Panicf/PaniclnpanicFatal/Fatalf/Fatallnos.Exit(1)
fln
log.Panicfpaniclog.Fatalf

2、自定义选项

选项

Ldate2020/02/07Ltime11:45:45LmicrosecondsLtime11:45:45.123123Llongfilegithub.com/darjun/go-daily-lib/log/flag/main.go:50Lshortfilemain.go:50LUTCLdateLtime
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)
log.SetPrefix("Debug: ")

3、输出到文件

file := "./" + "message" + ".txt"
logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
if err != nil {
	panic(err)
}
log.SetOutput(logFile) // 将文件设置为log输出的文件
log.SetPrefix("[qcpz]")
log.SetFlags(log.LstdFlags | log.Lshortfile | log.Ldate | log.Ltime)

4、自定义输出

logLoggerstdlogstd
// src/log/log.go
var std = New(os.Stderr, "", LstdFlags)

func Printf(format string, v ...interface{}) {
  std.Output(2, fmt.Sprintf(format, v...))
}

func Fatalf(format string, v ...interface{}) {
  std.Output(2, fmt.Sprintf(format, v...))
  os.Exit(1)
}

func Panicf(format string, v ...interface{}) {
  s := fmt.Sprintf(format, v...)
  std.Output(2, s)
  panic(s)
}
log.New
io.WriterWriterprefixlogger.SetPrefixflaglogger.SetFlag
io.MultiWriter
package main

import (
	"bytes"
	"io"
	"log"
	"os"
)

type User struct {
	Name string
	Age  int
}

func main() {
	u := User{
		Name: "test",
		Age:  18,
	}

	writer1 := &bytes.Buffer{}
	writer2 := os.Stdout
	writer3, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE, 0755)
	if err != nil {
		log.Fatalf("create file log.txt failed: %v", err)
	}

	logger := log.New(io.MultiWriter(writer1, writer2, writer3), "", log.Lshortfile|log.LstdFlags)
	logger.Printf("%s login, age:%d", u.Name, u.Age)
}

二、logrus的使用

1、golang日志库

golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持。所以催生了很多第三方的日志库,但是在golang的世界里,没有一个日志库像slf4j那样在Java中具有绝对统治地位。golang中,流行的日志框架包括logrus、zap、zerolog、seelog等。

logrus是目前Github上star数量最多的日志库,目前(2018.08,下同)star数量为8119,fork数为1031。logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。很多开源项目,如docker,prometheus等,都是用了logrus来记录其日志。

zap是Uber推出的一个快速、结构化的分级日志库。具有强大的ad-hoc分析功能,并且具有灵活的仪表盘。zap目前在GitHub上的star数量约为4.3k。

seelog提供了灵活的异步调度、格式化和过滤功能。

2、logrus特性

logrus具有以下特性:

  • 完全兼容golang标准库日志模块:logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集。如果您的项目使用标准库日志模块,完全可以以最低的代价迁移到logrus上。
  • 可扩展的Hook机制:允许使用者通过hook的方式将日志分发到任意地方,如本地文件系统、标准输出、logstash、elasticsearch或者mq等,或者通过hook定义日志内容和格式等。
  • 可选的日志输出格式:logrus内置了两种日志格式,JSONFormatter和TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式。
  • Field机制:logrus鼓励通过Field机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
  • logrus是一个可插拔的、结构化的日志框架。

尽管 logrus有诸多优点,但是为了灵活性和可扩展性,官方也削减了很多实用的功能,例如:

  • 没有提供行号和文件名的支持
  • 输出到本地文件系统没有提供日志分割功能
  • 官方没有提供输出到ELK等日志处理中心的功能

但是这些功能都可以通过自定义hook来实现。

3、日志格式

比如,我们约定日志格式为 Text,包含字段如下:

请求时间日志级别状态码执行时间请求IP请求方式请求路由

4、简单的示例

package main
 
import (
  "os"
  log "github.com/sirupsen/logrus"
)
 
func init() {
  // 设置日志格式为json格式
  log.SetFormatter(&log.JSONFormatter{})
 
  // 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
  // 日志消息输出可以是任意的io.writer类型
  log.SetOutput(os.Stdout)
 
  // 设置日志级别为warn以上
  log.SetLevel(log.WarnLevel)
}
 
func main() {
  log.WithFields(log.Fields{
    "animal": "walrus",
    "size":  10,
  }).Info("A group of walrus emerges from the ocean")
 
  log.WithFields(log.Fields{
    "omg":  true,
    "number": 122,
  }).Warn("The group's number increased tremendously!")
 
  log.WithFields(log.Fields{
    "omg":  true,
    "number": 100,
  }).Fatal("The ice breaks!")
}

5、Logger

logger
package main
 
import (
  "github.com/sirupsen/logrus"
  "os"
)
 
// logrus提供了New()函数来创建一个logrus的实例。
// 项目中,可以创建任意数量的logrus实例。
var log = logrus.New()
 
func main() {
  // 为当前logrus实例设置消息的输出,同样地,
  // 可以设置logrus实例的输出到任意io.writer
  log.Out = os.Stdout
 
  // 为当前logrus实例设置消息输出格式为json格式。
  // 同样地,也可以单独为某个logrus实例设置日志级别和hook,这里不详细叙述。
  log.Formatter = &logrus.JSONFormatter{}
 
  log.WithFields(logrus.Fields{
    "animal": "walrus",
    "size":  10,
  }).Info("A group of walrus emerges from the ocean")
}

6、Fields

Fields

例如下面的记录日志的方式:

log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)

//替代方案

log.WithFields(log.Fields{
 "event": event,
 "topic": topic,
 "key": key,
}).Fatal("Failed to send event")

7、gin框架日志中间件使用

package middleware

import (
    "fmt"
    "ginDemo/config"
    "github.com/gin-gonic/gin"
    rotatelogs "github.com/lestrrat-go/file-rotatelogs"
    "github.com/rifflock/lfshook"
    "github.com/sirupsen/logrus"
    "os"
    "path"
    "time"
)

// 日志记录到文件
func LoggerToFile() gin.HandlerFunc {

    logFilePath := config.Log_FILE_PATH
    logFileName := config.LOG_FILE_NAME

    // 日志文件
    fileName := path.Join(logFilePath, logFileName)

    // 写入文件
    src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
    if err != nil {
        fmt.Println("err", err)
    }

    // 实例化
    logger := logrus.New()

    // 设置输出
    logger.Out = src

    // 设置日志级别
    logger.SetLevel(logrus.DebugLevel)

    // 设置 rotatelogs
    logWriter, err := rotatelogs.New(
        // 分割后的文件名称
        fileName + ".%Y%m%d.log",

        // 生成软链,指向最新日志文件
        rotatelogs.WithLinkName(fileName),

        // 设置最大保存时间(7天)
        rotatelogs.WithMaxAge(7*24*time.Hour),

        // 设置日志切割时间间隔(1天)
        rotatelogs.WithRotationTime(24*time.Hour),
    )

    writeMap := lfshook.WriterMap{
        logrus.InfoLevel:  logWriter,
        logrus.FatalLevel: logWriter,
        logrus.DebugLevel: logWriter,
        logrus.WarnLevel:  logWriter,
        logrus.ErrorLevel: logWriter,
        logrus.PanicLevel: logWriter,
    }

    lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{
        TimestampFormat:"2006-01-02 15:04:05",
    })

    // 新增 Hook
    logger.AddHook(lfHook)

    return func(c *gin.Context) {
        // 开始时间
        startTime := time.Now()

        // 处理请求
        c.Next()

        // 结束时间
        endTime := time.Now()

        // 执行时间
        latencyTime := endTime.Sub(startTime)

        // 请求方式
        reqMethod := c.Request.Method

        // 请求路由
        reqUri := c.Request.RequestURI

        // 状态码
        statusCode := c.Writer.Status()

        // 请求IP
        clientIP := c.ClientIP()

        // 日志格式
        logger.WithFields(logrus.Fields{
            "status_code"  : statusCode,
            "latency_time" : latencyTime,
            "client_ip"    : clientIP,
            "req_method"   : reqMethod,
            "req_uri"      : reqUri,
        }).Info()
    }
}

// 日志记录到 MongoDB
func LoggerToMongo() gin.HandlerFunc {
    return func(c *gin.Context) {

    }
}

// 日志记录到 ES
func LoggerToES() gin.HandlerFunc {
    return func(c *gin.Context) {

    }
}

// 日志记录到 MQ
func LoggerToMQ() gin.HandlerFunc {
    return func(c *gin.Context) {

    }
}

8、简单的日志切割

需要引入外部组件

package main

import (
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	log "github.com/sirupsen/logrus"
)

func init() {
	path := "message.log"
	/* 日志轮转相关函数
	`WithLinkName` 为最新的日志建立软连接
	`WithRotationTime` 设置日志分割的时间,隔多久分割一次
	WithMaxAge 和 WithRotationCount二者只能设置一个
	 `WithMaxAge` 设置文件清理前的最长保存时间
	 `WithRotationCount` 设置文件清理前最多保存的个数
	*/
	// 下面配置日志每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉。
	writer, _ := rotatelogs.New(
		path+".%Y%m%d%H%M",
		rotatelogs.WithLinkName(path),
		rotatelogs.WithMaxAge(time.Duration(180)*time.Second),
		rotatelogs.WithRotationTime(time.Duration(60)*time.Second),
	)
	log.SetOutput(writer)
	//log.SetFormatter(&log.JSONFormatter{})
}

func main() {
	for {
		log.Info("hello, world!")
		time.Sleep(time.Duration(2) * time.Second)
	}
}