APP下载

GoLang -- 日志系统

消息来源:baojiabao.com 作者: 发布时间:2026-05-20

报价宝综合消息GoLang -- 日志系统

一:log日志包

Golang的log包短小精悍,可以非常轻松的实现日志打印转存功能。不用多说,log支援并发操作(即协程安全-相对于JAVA中的执行绪安全而言),其结构定义如下:

type Logger struct {

mu sync.Mutex // ensures atomic writes; protects the following fields

prefix string // prefix to write at beginning of each line // 日志行字首

flag int // properties // 日志打印格式标志,用于指定每行日志的打印格式

out io.Writer // destination for output // 用于指定日志输出位置,理论上可以是任务地方,只要实现了io.Writer界面就行

buf []byte // for accumulating text to write // 日志内容

}

log包定义了一些日志格式标志:

Ldate = 1 << iota // 形如 2009/01/23 的日期

Ltime // 形如 01:23:23 的时间

Lmicroseconds // 形如 01:23:23.123123 的时间

Llongfile // 全路径档名和行号: /a/b/c/d.go:23

Lshortfile // 档名和行号: d.go:23

LstdFlags = Ldate | Ltime // 日期和时间

上述这些标志可以在建立Logger物件时指定(通过下面的New函式建立),也可以通过Logger.setFlat()方法动态指定。

Logger物件通过函式New建立

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

return &Logger{out: out, prefix: prefix, flag: flag}

}

log包已预设提供了一个日志物件,并封装了包级别的常用函式,该物件将日志资讯输出到标准输出装置中(开箱即用)。

如果只是想输出到终端而不储存到档案等其它地方时,可以直接通过log.Xxxx()方式直接呼叫,因为这些包级别的函式只是对std物件相关方法的简单封装,如println函式定义如下:

func Println(v ...interface{}) {

std.Output(2, fmt.Sprintln(v...))

}

Golang\'s log模组主要提供了3类界面。分别是 “Print Panic Fatal ”,对每一类界面其提供了3中呼叫方式,分别是 "Xxxx 、 Xxxxln 、Xxxxf",基本和fmt中的相关函式类似,下面是一个Print的示例:

程式码示例:

package main

import (

"log"

)

func main(){

arr := []int {2,3}

log.Print("Print array ",arr," ")

log.Println("Println array",arr)

log.Printf("Printf array with item [%d,%d] ",arr[0],arr[1])

}

• 对于 log.Fatal 界面,会先将日志内容打印到标准输出,接着呼叫系统的 os.exit(1) 界面,退出程式并返回状态 1 。但是有一点需要注意,由于是直接呼叫系统界面退出,defer函式不会被呼叫,下面是一个Fatal的示例:

package main

import (

"fmt"

"log"

)

func test_deferfatal() {

defer func() {

fmt.Println("--first--")

}()

log.Fatalln("test for defer Fatal")

}

func main() {

test_deferfatal()

}

• 对于log.Panic界面,该函式把日志内容刷到标准错误后呼叫 panic 函式,下面是一个Panic的示例:

package main

import (

"fmt"

"log"

)

func test_deferpanic() {

defer func() {

fmt.Println("--first--")

if err := recover(); err != nil {

fmt.Println(err)

}

}()

log.Panicln("test for defer Panic")

defer func() {

fmt.Println("--second--")

}()

}

func main() {

test_deferpanic()

}

你也可以自定义Logger型别, log.Logger提供了一个New方法用来建立物件:

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

该函式一共有三个引数:

(1)输出位置out,是一个io.Writer物件,该物件可以是一个档案也可以是实现了该界面的物件。通常我们可以用这个来指定日志输出到哪个档案。

(2)prefix 我们在前面已经看到,就是在日志内容前面的东西。我们可以将其置为 "[Info]" 、 "[Warning]"等来帮助区分日志级别。

(3) flags 是一个选项,显示日志开头的东西,可选的值见前面所述

package main

import (

"log"

"os"

)

func main() {

fileName := "Info_First.log"

logFile, err := os.Create(fileName)

defer logFile.Close()

if err != nil {

log.Fatalln("open file error")

}

debugLog := log.New(logFile, "[Info]", log.Llongfile)

debugLog.Println("A Info message here")

debugLog.SetPrefix("[Debug]")

debugLog.Println("A Debug Message here ")

}

综合案例分析:

package main

import (

"fmt"

"log"

"os"

)

func main() {

fmt.Println("begin TestLog ...")

file, err := os.Create("test.log")

if err != nil {

log.Fatalln("fail to create test.log file!")

}

logger := log.New(file, "", log.LstdFlags|log.Llongfile)

log.Println("111.Println log with log.LstdFlags ...")

logger.Println("1.Println log with log.LstdFlags ...")

logger.SetFlags(log.LstdFlags)

log.Println("222.Println log without log.LstdFlags ...")

logger.Println("2.Println log without log.LstdFlags ...")

fmt.Println("3 Will this statement be execute ?")

logger.Panicln("3.Panicln log without log.LstdFlags ...")

// log.Fatal("555.std Fatal log without log.LstdFlags ...")

// fmt.Println("5 Will this statement be execute ?")

// logger.Fatal("5.Fatal log without log.LstdFlags ...")

}

二:Zap日志包使用

日志作为整个程式码行为的记录,是程式执行逻辑和异常最直接的反馈。对于整个系统来说,日志是至关重要的组成部分。通过分析日志我们不仅可以发现系统的问题,同时日志中也蕴含了大量有价值可以被挖掘的资讯,因此合理地记录日志是十分必要的。

绝大多数的程式码中的写日志通常通过各式各样的日志库来实现。日志库提供了丰富的功能,对于 Go 开发者来说大家常用的日志元件通常会有以下几种,下面简单的总结了常用的日志元件的特点:

• seelog: 最早的日志元件之一,功能强大但是效能不佳,不过给社群后来的日志库在设计上提供了很多的启发。

• logrus: 程式码清晰简单,同时提供结构化的日志,效能较好。

• zap: uber 开源的高效能日志库,面向高效能并且也确实做到了高效能。

Zap 程式码并不是很多,不到 5000 行,比 seelog 少多了( 8000 行左右), 但比logrus(不到 2000 行)要多很多。

下面我们具体使用Zap来进行我们的学习。

package main

import (

"go.uber.org/zap"

_ "go.uber.org/zap/zapcore"

"time"

)

func main() {

var url string = "Hello"

logger, _ := zap.NewProduction()

//logger, _ := zap.NewDevelopment()

defer logger.Sync()

logger.Info("failed to fetch URL",

// Structured context as strongly typed Field values.

zap.String("url", url),

zap.Int("attempt", 3),

zap.Duration("backoff", time.Second),

)

logger.Warn("debug log", zap.String("level", url))

logger.Error("Error Message", zap.String("error", url))

logger.Panic("Panic log", zap.String("level", url))

}

下面我们看下,如何通过HTTP界面动态的改变日志级别

package main

import (

"fmt"

"go.uber.org/zap"

"net/http"

"time"

)

func main() {

alevel := zap.NewAtomicLevel()

http.HandleFunc("/handle/level", alevel.ServeHTTP)

go func() {

if err := http.ListenAndServe(":9090", nil); err != nil {

panic(err)

}

}()

// 预设是Info级别

logcfg := zap.NewProductionConfig()

logcfg.Level = alevel

logger, err := logcfg.Build()

if err != nil {

fmt.Println("err", err)

}

defer logger.Sync()

for i := 0; i < 1000; i++ {

time.Sleep(1 * time.Second)

logger.Debug("debug log", zap.String("level", alevel.String()))

logger.Info("Info log", zap.String("level", alevel.String()))

}

}

检视日志级别

curl http://localhost:9090/handle/level

输出

{"level":"info"}

调整日志级别(可选值 “debug” “info” “warn” “error” 等)

curl -XPUT --data \'{"level":"debug"}\' http://localhost:9090/handle/level

输出

{“level":"debug"}

• 下面我们将日志进行序列化档案

uber开源的高效能日志库zap, 除了效能远超logrus之外,还有很多诱人的功能,比如支援日志取样、支援通过HTTP服务动态调整日志级别。不过他原生不支援档案归档,如果要支援档案按大小或者时间归档,必须要使用第三方库, 根据官方资料参考资料1,官方推荐的是 natefinch/lumberjack

package main

import (

"go.uber.org/zap"

"go.uber.org/zap/zapcore"

"gopkg.in/natefinch/lumberjack.v2"

)

// logpath 日志档案路径

// loglevel 日志级别

func initLogger(logpath string, loglevel string) *zap.Logger {

hook := lumberjack.Logger{

Filename: logpath, // 日志档案路径

MaxSize: 1024, // megabytes

MaxBackups: 3, // 最多保留3个备份

MaxAge: 7, //days

Compress: true, // 是否压缩 disabled by default

}

w := zapcore.AddSync(&hook)

var level zapcore.Level

switch loglevel {

case "debug":

level = zap.DebugLevel

case "info":

level = zap.InfoLevel

case "error":

level = zap.ErrorLevel

default:

level = zap.InfoLevel

}

encoderConfig := zap.NewProductionEncoderConfig()

encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder

core := zapcore.NewCore(

zapcore.NewConsoleEncoder(encoderConfig),

w,

level,

)

logger := zap.New(core)

logger.Info("DefaultLogger init success")

return logger

}

func main() {

logger := initLogger("all.log", "info")

logger.Info("test log", zap.Int("line", 47))

logger.Warn("testlog", zap.Int("line", 47))

}

内容相当多,大家去github查阅对应的资料即可。

2019-12-29 04:48:00

相关文章