18.日志

18.日志

18.1 自帶log包

? ? 在日常項目,在出現(xiàn)問題之后需要排查,一種比較主要的排查方式是通過日志。所以在代碼的關鍵地方,需要打印相應的日志。在Go語言中log包提供了簡單的日志功能,其輸出格式如下所示:

打印 格式化打印 換行打印 備注
log.Print() log.Printf() log.Println() 類似于fmt.Print*
log.Fatal() log.Fatalf() log.Fatalln() 類似于fmt.Print* + os.Exit(1)
log.Panic() log.Panicf() log.Panicln() 類似于fmt.Print*+ panic()

? ? 一般在打印日志時,我們會定義一個Logger進行日志的打印,默認的日志定義如下所示:

type Logger struct {
    outMu sync.Mutex
    out   io.Writer // destination for output

    prefix    atomic.Pointer[string] // prefix on each line to identify the logger (but see Lmsgprefix)
    flag      atomic.Int32           // properties
    isDiscard atomic.Bool
}

const (
    Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
    Ltime                         // the time in the local time zone: 01:23:23
    Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
    Llongfile                     // full file name and line number: /a/b/c/d.go:23
    Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
    LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
    Lmsgprefix                    // move the "prefix" from the beginning of the line to before the message
    LstdFlags     = Ldate | Ltime // initial values for the standard logger
)

var std = New(os.Stderr, "", LstdFlags)

// Default returns the standard logger used by the package-level output functions.
func Default() *Logger { return std }

? ? 上面都是使用std.Output打印日志。而std本質(zhì)上是使用標準錯誤輸出、無前綴、LstdFlags標準標記的Logger

18.1.1 使用Std打印日志

? ? 示例代碼如下所示:

package main

import (
    "log"
    "os"
)

func main() {
    // 使用默認配置打印日志
    log.Print("使用log.Print打印日志\n")
    log.Printf("使用log.Printf打印日志,%v", "Surpass")
    log.Println("使用log.Println打印日志")

    // 使用自定義Logger
    lInfo := log.New(os.Stdout, "[INFO]", log.LstdFlags|log.Lmsgprefix)
    lInfo.Println("使用自定義Logger打印日志,日志級別 info")
    lError := log.New(os.Stderr, "[ERROR]", log.LstdFlags|log.Lmsgprefix|log.Lshortfile)
    lError.Fatalln("使用自定義Logger打印日志,日志級別 error")
}

? ? 代碼運行結果如下所示:

2024/12/30 23:48:43 使用log.Print打印日志
2024/12/30 23:48:43 使用log.Printf打印日志,Surpass
2024/12/30 23:48:43 使用log.Println打印日志
2024/12/30 23:48:43 [INFO]使用自定義Logger打印日志,日志級別 info
2024/12/30 23:48:43 main.go:18: [ERROR]使用自定義Logger打印日志,日志級別 error
exit status 1

18.1.2 日志寫入文件

? ? 示例代碼如下所示:

package main

import (
    "log"
    "os"
    "path/filepath"
)

func main() {
    curpath, _ := os.Getwd()
    flag := os.O_CREATE | os.O_APPEND | os.O_WRONLY
    filename := filepath.Join(curpath, "surpass.log")
    f, err := os.OpenFile(filename, flag, 0)
    if err != nil {
        log.Panicf("打開文件%v出錯:%v\n", filename, err)
    }
    defer f.Close()

    lInfo := log.New(f, "[INFO]", log.Lshortfile|log.LstdFlags)
    lInfo.Println("這是一個寫入文件的日志,級別-INFO")

    lError := log.New(f, "[WARNING]", log.Lshortfile|log.LstdFlags)
    lError.Fatalln("這是一個寫入文件的日志,級別-WARNING")

    lPanic := log.New(f, "[ERROR]", log.Lshortfile|log.LstdFlags)
    lPanic.Panicln("這是一個寫入文件的日志,級別-ERROR")
}

? ? 代碼運行結果如下所示:

[INFO]2024/12/31 00:09:37 main.go:20: 這是一個寫入文件的日志,級別-INFO
[WARNING]2024/12/31 00:09:37 main.go:23: 這是一個寫入文件的日志,級別-WARNING

18.2 zerolog

? ? Go自帶的log庫太簡單,在實際開發(fā)過程中,使用并不方便。因此出現(xiàn)很多第三方比較優(yōu)秀的開源日志庫,如下所示:

  • logrus: 有日志級別、Hook機制、日志格式輸出
  • zap是Uber開源的高性能的日志庫
  • zerolog更注重開發(fā)體驗、也是一款高性能的日志庫,有日志級別,鏈式API,JSON格式化的日志記錄

Github地址:https://github.com/rs/zerolog

18.2.1 安裝

? ? 執(zhí)行以下命令即可

go get -u github.com/rs/zerolog/log

18.2.2 默認Logger

? ? 示例代碼如下所示:

package main

import (
    "github.com/rs/zerolog/log"
)

func main() {
    log.Print("Hello,Surpass")
}

? ? 代碼運行結果如下所示:

{"level":"debug","time":"2024-12-31T23:18:14+08:00","message":"Hello,Surpass"}

默認輸出格式為JSON,級別且為Debug模式

18.2.3 消息級別

? ? zerolog對應的日志級別從高到低如下所示:

  • panic (zerolog.PanicLevel, 5)
  • fatal (zerolog.FatalLevel, 4)
  • error (zerolog.ErrorLevel, 3)
  • warn (zerolog.WarnLevel, 2)
  • info (zerolog.InfoLevel, 1)
  • debug (zerolog.DebugLevel, 0)
  • trace (zerolog.TraceLevel, -1)

? ? 查看源碼定義如下所示:

const (
    // DebugLevel defines debug log level.
    DebugLevel Level = iota
    // InfoLevel defines info log level.
    InfoLevel
    // WarnLevel defines warn log level.
    WarnLevel
    // ErrorLevel defines error log level.
    ErrorLevel
    // FatalLevel defines fatal log level.
    FatalLevel
    // PanicLevel defines panic log level.
    PanicLevel
    // NoLevel defines an absent log level.
    NoLevel
    // Disabled disables the logger.
    Disabled

    // TraceLevel defines trace log level.
    TraceLevel Level = -1
    // Values less than TraceLevel are handled as numbers.
)

? ? 在zerolog中,日志級別可以全局設置,也可以局部設置,即為每一個Logger或消息設置不同的消息級別。其中全局級別的使用形式如下所示:

  • 設置級別:zerolog.SetGlobalLevel(zerolog.InfoLevel)
  • 獲取級別:zerolog.GlobalLevel()

? ? 示例代碼如下所示:

package main

import (
    "fmt"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    // 獲取全局的消息級別
    fmt.Printf("獲取全局消息級別:%+v\n", zerolog.GlobalLevel())
    fmt.Printf("默認Logger的消息級別為:%+v\n", log.Logger.GetLevel())

    // 創(chuàng)建一個Logger,并獲取相應的消息級別
    l := log.Level(zerolog.WarnLevel)
    fmt.Printf("新建Logger的級別為:%+v", l.GetLevel())

    // 使用默認Logger 自定義每一個消息的級別
    log.Info().Msg("默認Logger消息級別-INFO")
    log.Error().Msg("默認Logger消息級別-ERROR")
    log.Warn().Msg("默認Logger消息級別-WARNING")
    log.Debug().Msg("默認Logger消息級別-DEBUG")
    log.Trace().Msg("默認Logger消息級別-TRACE")

    // 使用自定義Logger 自定義每一個消息的級別
    l.Debug().Msg("自定義Logger消息級別-DEBUG")  //不輸出
    l.Info().Msg("自定義Logger消息級別-INFO")    // 不輸出
    l.Error().Msg("自定義Logger消息級別-ERROR")  //輸出
    l.Warn().Msg("自定義Logger消息級別-WARNING") //輸出
}

? ? 代碼運行結果如下所示:

獲取全局消息級別:trace
默認Logger的消息級別為:trace
新建Logger的級別為:warn
{"level":"info","time":"2024-12-31T23:41:34+08:00","message":"默認Logger消息級別-INFO"}
{"level":"error","time":"2024-12-31T23:41:34+08:00","message":"默認Logger消息級別-ERROR"}
{"level":"warn","time":"2024-12-31T23:41:34+08:00","message":"默認Logger消息級別-WARNING"}
{"level":"debug","time":"2024-12-31T23:41:34+08:00","message":"默認Logger消息級別-DEBUG"}
{"level":"trace","time":"2024-12-31T23:41:34+08:00","message":"默認Logger消息級別-TRACE"}

{"level":"error","time":"2024-12-31T23:41:34+08:00","message":"自定義Logger消息級別-ERROR"}
{"level":"warn","time":"2024-12-31T23:41:34+08:00","message":"自定義Logger消息級別-WARNING"}

? ? 從代碼運行結果可以總結如下所示:

  • 使用默認Logger,可以輸出所有日志消息
  • 使用自定義Logger,只能輸出部分日志消息,是因為在zerolog中存在消息級別Logger級別兩個概念

? ? 在上述代碼中自定義的Logger中,其Logger級別被設置為WarnLevel,其級別為2,當輸出的消息級別低于其Logger級別時,則不被輸出。因此想要消息成功輸出,則需要滿足以下條件

消息級別 >= max(全局組別,當前Logger級別)

其中 zerolog.SetGlobalLevel() 為全局級別,會影響所有Logger

package main

import (
    "fmt"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    // 設置全局消息級別
    zerolog.SetGlobalLevel(zerolog.ErrorLevel)
    // 獲取全局的消息級別
    fmt.Printf("獲取全局消息級別:%+v\n", zerolog.GlobalLevel())
    fmt.Printf("默認Logger的消息級別為:%+v\n", log.Logger.GetLevel())

    // 創(chuàng)建一個Logger,并獲取相應的消息級別
    l := log.Level(zerolog.WarnLevel)
    fmt.Printf("新建Logger的級別為:%+v", l.GetLevel())

    // 使用默認Logger 自定義每一個消息的級別
    log.Info().Msg("默認Logger消息級別-INFO")
    log.Error().Msg("默認Logger消息級別-ERROR")
    log.Warn().Msg("默認Logger消息級別-WARNING")
    log.Debug().Msg("默認Logger消息級別-DEBUG")
    log.Trace().Msg("默認Logger消息級別-TRACE")

    // 使用自定義Logger 自定義每一個消息的級別
    l.Debug().Msg("自定義Logger消息級別-DEBUG")  //不輸出
    l.Info().Msg("自定義Logger消息級別-INFO")    // 不輸出
    l.Error().Msg("自定義Logger消息級別-ERROR")  //輸出
    l.Warn().Msg("自定義Logger消息級別-WARNING") //輸出

    // 禁用日志輸出
    zerolog.SetGlobalLevel(zerolog.Disabled)
    fmt.Printf("獲取全局消息級別:%+v\n", zerolog.GlobalLevel())
    log.Info().Msg("默認Logger消息級別-PANIC")
    l.Error().Msg("自定義Logger消息級別-PANIC")
}

? ? 代碼運行結果如下所示:

獲取全局消息級別:error
默認Logger的消息級別為:trace
新建Logger的級別為:warn{"level":"error","time":"2025-01-01T00:06:15+08:00","message":"默認Logger消息級別-ERROR"}
{"level":"error","time":"2025-01-01T00:06:15+08:00","message":"自定義Logger消息級別-ERROR"}
獲取全局消息級別:disabled

18.2.4 上下文

? ? zerolog 的默認輸出格式為JSON,但也可以自定義一些鍵值對字段,并在上下文中進行輸出,示例代碼如下所示:

package main

import (
    "fmt"
    "time"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    // 設置全局消息級別
    zerolog.SetGlobalLevel(zerolog.InfoLevel)
    // 獲取全局的消息級別
    fmt.Printf("獲取全局消息級別:%+v\n", zerolog.GlobalLevel())
    fmt.Printf("默認Logger的消息級別為:%+v\n", log.Logger.GetLevel())

    log.Warn().Bool("SUCCESS", false).Str("原因", "未滿足相應的條件").Msg("沒有滿足相應條件")
    log.Info().Str("Username", "Surpass").Time("登錄時間", time.Now()).Msg("用戶登錄成功")
}

? ? 代碼運行結果如下所示:

獲取全局消息級別:info
默認Logger的消息級別為:trace
{"level":"warn","SUCCESS":false,"原因":"未滿足相應的條件","time":"2025-01-01T00:19:38+08:00","message":"沒有滿足相應條件"}
{"level":"info","Username":"Surpass","登錄時間":"2025-01-01T00:19:38+08:00","time":"2025-01-01T00:19:38+08:00","message":"用戶登錄成功"}

18.2.5 錯誤日志

? ? 在zerolog中輸出錯誤日志的示例代碼如下所示:

package main

import (
    "errors"
    "fmt"
    "time"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    // 設置全局消息級別
    zerolog.SetGlobalLevel(zerolog.InfoLevel)
    // 獲取全局的消息級別
    fmt.Printf("獲取全局消息級別:%+v\n", zerolog.GlobalLevel())
    fmt.Printf("默認Logger的消息級別為:%+v\n", log.Logger.GetLevel())

    // 設置時間格式
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs

    log.Info().Str("Username", "Surpass").Time("登錄時間", time.Now()).Msg("用戶登錄成功")

    // 自定義錯誤日志
    err := errors.New("Surpass自定義錯誤日志")
    log.Error(). // 錯誤級別消息
            Err(err). // 錯誤消息內(nèi)容,即err字段
            Send()

    log.Fatal().Err(err).Send()
}

? ? 代碼運行結果如下所示:

獲取全局消息級別:info
默認Logger的消息級別為:trace
{"level":"info","Username":"Surpass","登錄時間":1735662443578,"time":1735662443578,"message":"用戶登錄成功"}
{"level":"error","error":"Surpass自定義錯誤日志","time":1735662443578}
{"level":"fatal","error":"Surpass自定義錯誤日志","time":1735662443578}
exit status 1

18.2.6 自定義Logger

? ? 示例代碼如下所示:

package main

import (
    "errors"
    "fmt"
    "os"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    // 自定義Logger方式一:
    loggerA := zerolog.New(os.Stdout).With().Timestamp().Caller().Logger().Level(zerolog.DebugLevel)
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
    fmt.Println("自定義LoggerA的級別為:", loggerA.GetLevel())
    err := errors.New("Surpass自定義錯誤")
    loggerA.Error().Err(err).Send()

    fmt.Println("")

    // 自定義Logger方式二:
    loggerB := log.With().Str("Product", "Surpass").Caller().Logger().Level(zerolog.InfoLevel)
    fmt.Println("自定義LoggerB的級別為:", loggerB.GetLevel())
    loggerB.Info().Msg("自定義的Logger")
}

? ? 代碼運行結果如下所示:

自定義LoggerA的級別為: debug
{"level":"error","error":"Surpass自定義錯誤","time":1735664944449,"caller":"C:/Users/Surpass/Documents/GolangProjets/src/18/1808-zerolog-customer/main.go:18"}

自定義LoggerB的級別為: info
{"level":"info","Product":"Surpass","time":1735664944449,"caller":"C:/Users/Surpass/Documents/GolangProjets/src/18/1808-zerolog-customer/main.go:25","message":"自定義的Logger"}

18.2.7 日志寫入文件

? ? 示例代碼如下所示:

package main

import (
    "os"
    "path/filepath"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs

    curPath, _ := os.Getwd()
    filename := filepath.Join(curPath, "zerolog.surpass.log")
    f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0)
    if err != nil {
        log.Panic().Err(err).Send()
    }
    defer f.Close()

    // 多分支寫
    multi := zerolog.MultiLevelWriter(f, os.Stdout)

    logger := zerolog.New(multi).With().Timestamp().Logger()
    logger.Info().Msg("日志同時寫入文件和控制臺")
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容