Go語言中l(wèi)og日志庫的介紹
一、標準庫log介紹
- Go語言內(nèi)置的log包實現(xiàn)了簡單的日志服務。
1、使用Logger
- log包定義了Logger類型,該類型提供了一些格式化輸出的方法。
- log包也提供了一個預定義的“標準”logger,可以通過調(diào)用函數(shù)Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)來使用,比自行創(chuàng)建一個logger對象更容易使用。
logger會打印每條日志信息的日期、時間,默認輸出到系統(tǒng)的標準錯誤。
Fatal系列函數(shù)會在寫入日志信息后調(diào)用os.Exit(1)。
Panic系列函數(shù)會在寫入日志信息后panic。
示例:
// 直接調(diào)用print來輸出日志到終端
func main() {
log.Println("這是一條很普通的日志。")
v := "很普通的"
log.Printf("這是一條%s日志。\n", v)
log.Fatalln("這是一條會觸發(fā)fatal的日志。")
log.Panicln("這是一條會觸發(fā)panic的日志。")
}
2、配置logger
2.1、標準logger的配置
- 默認情況下的logger只會提供日志的時間信息,但是很多情況下我們希望得到更多信息,比如記錄該日志的文件名和行號等。
- log標準庫中為我們提供了定制這些設置的方法。log標準庫中的Flags函數(shù)會返回標準logger的輸出配置,而SetFlags函數(shù)用來設置標準logger的輸出配置。
二、自定義日志庫
1、需要滿足的需求
1、支持網(wǎng)不同地方輸出日志
2、日志要支持開關控制
3、完整的日志要包含時間、行號、文件名、日志級別、日志信息
4、日志文件要能切割
5、日志級別
- Debug
- Trace
- Info
- Warning
- Error
- Fatal
2、了解下runtime包
- runtime.Caller() 方法,會返回函數(shù)調(diào)用的信息,在()內(nèi)傳入int類型數(shù)據(jù),代表調(diào)用的層數(shù)(0代表第一層被調(diào)用,1代表第二層,一次類推)。runtime.Caller()返回如下四個參數(shù):
pc: 記錄了調(diào)用的函數(shù)消息,如函數(shù)名等
file: 調(diào)用的文件名
line: 行數(shù)
OK: 返回的bool值
// 獲取代碼行號
func getinfo(n int) {
/*
runtime.Caller()返回四個參數(shù):
pc: 記錄了調(diào)用的函數(shù)消息,如函數(shù)名等
file: 調(diào)用的文件名
line: 行數(shù)
OK: 返回的bool值
*/
pc, file, line, ok := runtime.Caller(n)
if !ok {
fmt.Printf("runtime.Caller() failed\n")
return
}
fmt.Println(pc) // 用于調(diào)用函數(shù)消息
funcName := runtime.FuncForPC(pc).Name() // 利用 runtime.FuncForPC()方法,參數(shù)pc序號,可以調(diào)出函數(shù)名
fmt.Println(funcName)
fmt.Println(file) // 打印出執(zhí)行的文件的絕對路徑
fmt.Println(path.Base(file)) // path.Base()方法可以獲取傳入的絕對路徑的最后一個文件名稱
fmt.Println(line) // 打印出執(zhí)行的行號
}
func main() {
getinfo(0)
}
3、自定義日志庫
1、在 E:\GoProject\src\gitee.com 項目目錄下,新建 mylogger 包目錄,存放日志庫代碼
在mylogger包目錄下console.go文件中,放將日志輸出到終端的代碼
# E:\GoProject\src\gitee.com\mylogger\console.go
package mylogger
import (
"fmt"
"strings"
"time"
)
// 終端輸出的日志內(nèi)容
// Logger 日志結(jié)構(gòu)體
type ConsoleLogger struct {
Level LogLevel
}
// 構(gòu)造函數(shù)
func NewLog(levelStr string) ConsoleLogger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
return ConsoleLogger{
Level: level,
}
}
// 定義日志等級比較
func (c ConsoleLogger) enable(level LogLevel) bool {
return c.Level <= level
}
// 定義一個日志打印方法
func (c ConsoleLogger) outLog(lv LogLevel, format string, a ...interface{}) {
level := parseLogLevelStr(lv)
if c.enable(lv) {
msg := fmt.Sprintf(format, a...) // 支持格式化操作,a...表示對接收的接口進行展開
level = strings.ToUpper(level)
fileName, funcName, linenu := getinfo(3)
fmt.Printf("[%s] [%s] [FileName:%s lineNum:%d:%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), level, fileName, linenu, funcName, msg)
}
}
// Debug日志
func (c ConsoleLogger) Debug(msg string, a ...interface{}) {
c.outLog(DEBUG, msg, a...)
}
// Info日志
func (c ConsoleLogger) Info(msg string, a ...interface{}) {
c.outLog(INFO, msg, a...)
}
// Warning日志
func (c ConsoleLogger) Warning(msg string, a ...interface{}) {
c.outLog(WARNING, msg, a...)
}
// Error日志
func (c ConsoleLogger) Error(msg string, a ...interface{}) {
c.outLog(ERROR, msg, a...)
}
// Fatal日志
func (c ConsoleLogger) Fatal(msg string, a ...interface{}) {
c.outLog(FATAL, msg, a...)
}
在mylogger包目錄下logfile.go文件中,放將日志輸出到文件的代碼
# E:\GoProject\src\gitee.com\mylogger\logfile.go
package mylogger
// 向文件中寫日志的相關操作
import (
"fmt"
"os"
"path"
"strings"
"time"
)
// Logger 日志結(jié)構(gòu)體
type FileLogger struct {
Level LogLevel
logPath string // 日志文件目錄
fileName string // 日志文件名稱
maxFileSize int64 // 日志文件大小
fileObj *os.File
errFileObj *os.File
}
// 構(gòu)造函數(shù)
func NewFileLog(levelStr, fp, fn string, maxSize int64) *FileLogger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
f1 := &FileLogger{
Level: level,
logPath: fp,
fileName: fn,
maxFileSize: maxSize,
}
err = f1.initFile()
if err != nil {
panic(err)
}
return f1
}
// 定義文件初始化方法,打開文件
func (f *FileLogger) initFile() error {
// 打開文件
fullPath := path.Join(f.logPath, f.fileName)
fileObj, err := os.OpenFile(fullPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open logFile failed,Err:%v", err)
return err
}
// 打開錯誤日志文件
errFileObj, err := os.OpenFile("error-"+fullPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open errorLogFile failed,Err:%v", err)
return err
}
// 將文件句柄賦值給結(jié)構(gòu)體中的元素
f.fileObj = fileObj
f.errFileObj = errFileObj
return nil
}
// 關閉文件方法
func (f *FileLogger) closeFile() {
f.fileObj.Close()
f.errFileObj.Close()
}
// 定義日志等級比較
func (f *FileLogger) enable(level LogLevel) bool {
return f.Level <= level
}
// 定義檢查日志大小進行切割的方法
func (f *FileLogger) checkLogSize(file *os.File) bool { // 利用os.Open()方法打開的文件句柄類型都是 *os.File 這種指針類型
fileInfo, err := file.Stat()
if err != nil {
fmt.Printf("get file info failed,err:%v\n", err)
return false
// panic(err)
}
if fileInfo.Size() > f.maxFileSize {
return true
}
return false
}
func (f *FileLogger) cutFile(file *os.File) (*os.File, error) {
// 獲取當前文件名
fileInfo, err := file.Stat()
if err != nil {
fmt.Printf("get file info failed,err:%v\n", err)
return nil, err
}
append := "_" + time.Now().Format("2006-01-02-15-04-05") + ".log" // 時間戳后綴
fileName := fileInfo.Name()
newFileName := fileName + append
oldPath := path.Join(f.logPath, fileName) // 日志文件的全路徑
newPath := path.Join(f.logPath, newFileName)
// 關閉當前文件句柄
file.Close()
// 進行更名備份
os.Rename(oldPath, newPath)
// 再打開一個文件
fileObj, err := os.OpenFile(oldPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open logFile failed,Err:%v\n", err)
return nil, err
}
return fileObj, nil
}
// 定義一個日志輸出的方法
func (f *FileLogger) outLog(lv LogLevel, format string, a ...interface{}) {
level := parseLogLevelStr(lv)
if f.enable(lv) {
msg := fmt.Sprintf(format, a...) // 支持格式化操作,a...表示對接收的接口進行展開
level = strings.ToUpper(level)
fileName, funcName, linenu := getinfo(3)
// 判斷日志是否需要切割
if f.checkLogSize(f.fileObj) {
fileobj, err := f.cutFile(f.fileObj)
if err != nil {
fmt.Printf("log cut failed,err:%v\n", err)
}
f.fileObj = fileobj
}
// 將日志記錄在日志文件中
fmt.Fprintf(f.fileObj, "[%s] [%s] [FileName:%s lineNum:%d:%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), level, fileName, linenu, funcName, msg)
if lv >= ERROR {
// 判斷錯誤日志是否需要切割
if f.checkLogSize(f.errFileObj) {
fileobj, err := f.cutFile(f.errFileObj)
if err != nil {
fmt.Printf("log cut failed,err:%v\n", err)
}
f.errFileObj = fileobj
}
// 將日志等級大于ERROR的在錯誤日志中再記錄一遍
fmt.Fprintf(f.errFileObj, "[%s] [%s] [FileName:%s lineNum:%d:%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), level, fileName, linenu, funcName, msg)
}
}
}
// Debug日志
func (f *FileLogger) Debug(msg string, a ...interface{}) {
f.outLog(DEBUG, msg, a...)
}
// Info日志
func (f *FileLogger) Info(msg string, a ...interface{}) {
f.outLog(INFO, msg, a...)
}
// Warning日志
func (f *FileLogger) Warning(msg string, a ...interface{}) {
f.outLog(WARNING, msg, a...)
}
// Error日志
func (f *FileLogger) Error(msg string, a ...interface{}) {
f.outLog(ERROR, msg, a...)
}
// Fatal日志
func (f *FileLogger) Fatal(msg string, a ...interface{}) {
f.outLog(FATAL, msg, a...)
}
在mylogger包目錄下創(chuàng)建mylogger.go文件,放包內(nèi)共用的代碼
# E:\GoProject\src\gitee.com\mylogger\mylogger.go
package mylogger
import (
"errors"
"fmt"
"path"
"runtime"
"strings"
)
// 設置日志級別
type LogLevel uint16
const (
UNKNOWN LogLevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)
// 解析日志等級,將字符串解析成int
func parseLogLevel(s string) (LogLevel, error) {
s = strings.ToLower(s) // 將字符串全部轉(zhuǎn)為小寫
switch s {
case "debug":
return DEBUG, nil
case "trace":
return TRACE, nil
case "info":
return INFO, nil
case "warning":
return WARNING, nil
case "error":
return ERROR, nil
case "fatal":
return FATAL, nil
default:
err := errors.New("無效日志等級")
return UNKNOWN, err
}
}
// 將日志等級解析成字符串
func parseLogLevelStr(lv LogLevel) (s string) {
s = strings.ToLower(s) // 將字符串全部轉(zhuǎn)為小寫
switch lv {
case DEBUG:
return "DEBUG"
case TRACE:
return "TRACE"
case INFO:
return "INFO"
case WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "DEBUG"
}
}
// 獲取代碼行號
func getinfo(n int) (fileName, funcName string, line int) {
/*
runtime.Caller()返回四個參數(shù):
pc: 記錄了調(diào)用的函數(shù)消息,如函數(shù)名等
file: 調(diào)用的文件名
line: 行數(shù)
OK: 返回的bool值
*/
pc, file, line, ok := runtime.Caller(n)
if !ok {
fmt.Printf("runtime.Caller() failed\n")
return
}
fileName = path.Base(file)
funcName = runtime.FuncForPC(pc).Name()
return fileName, funcName, line
}
2、調(diào)用日志庫代碼測試
需要注意,導入mylogger包,是從$GOPATH路徑開始的(即從src目錄開始的相對路徑)
# E:\GoProject\src\gitee.com\LTP\logging
package main
import (
"time"
mylogger "gitee.com/mylogger"
)
func main() {
id := 10
name := "小明"
flogger := mylogger.NewFileLog("info", "./", "app.log", 1024)
for {
flogger.Warning("這是個Warning日志")
flogger.Error("這是個Error日志,id:%d name:%s", id, name)
time.Sleep(2 * time.Second)
}
}
到此這篇關于Go log日志庫的文章就介紹到這了,更多相關Go log日志庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Go語言基本的語法和內(nèi)置數(shù)據(jù)類型初探
這篇文章主要介紹了Go語言基本的語法和內(nèi)置數(shù)據(jù)類型,是golang入門學習中的基礎知識,需要的朋友可以參考下2015-10-10
Go語言中的流程控制結(jié)構(gòu)和函數(shù)詳解
這篇文章主要介紹了Go語言中的流程控制結(jié)構(gòu)和函數(shù)詳解,本文詳細講解了if、goto、for、switch等控制語句,同時對函數(shù)相關知識做了講解,需要的朋友可以參考下2014-10-10

