有一天我在goland里看著輸出日志识埋,很想知道它是哪個文件凡伊,第多少行打出來的,然后就找到了log功能窒舟。
TRACE: 2019/05/24 17:23:38 main.go:22: begin connect
Name: superWang
Phone: 13478808311
看代碼吧:
func init() {
log.SetPrefix("TRACE: ")
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
log.Println("begin connect")
fmt.Println("Name:", result.Name)
fmt.Println("Phone:", result.Phone)
一系忙、官方自帶log
1.如何使用最基本的 log 包
// This sample program demonstrates how to use the base log package.
package main
import (
"log"
)
func init() {
log.SetPrefix("TRACE: ")
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}
func main() {
// Println writes to the standard logger.
log.Println("message")
// Fatalln is Println() followed by a call to os.Exit(1).
log.Fatalln("fatal message")
// Panicln is Println() followed by a call to panic().
log.Panicln("panic message")
}
-------------------------------
TRACE: 2019/04/09 14:24:32.868375 D:/go/TestFile/src/main/TestLog.go:15: message
TRACE: 2019/04/09 14:24:32.962329 D:/go/TestFile/src/main/TestLog.go:18: fatal message
Process finished with exit code 1
log.SetPrefix("TRACE: ")
設(shè)置了一個字符串,作為每個日志項的前綴惠豺。這個字符串應(yīng)該是能讓用戶從一般的程序輸出中分辨出日志的字符串银还。傳統(tǒng)上這個字符串的字符會全部大寫
golang.org/src/log/log.go
const (
// 將下面的位使用或運(yùn)算符連接在一起,可以控制要輸出的信息洁墙。沒有
// 辦法控制這些信息出現(xiàn)的順序(下面會給出順序)或者打印的格式
// (格式在注釋里描述)蛹疯。這些項后面會有一個冒號:
// 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
// 日期: 2009/01/23
Ldate = 1 << iota
// 時間: 01:23:23
Ltime
// 毫秒級時間: 01:23:23.123123。該設(shè)置會覆蓋 Ltime 標(biāo)志
Lmicroseconds
// 完整路徑的文件名和行號: /a/b/c/d.go:23
Llongfile
// 最終的文件名元素和行號: d.go:23
// 覆蓋 Llongfile
Lshortfile
// 標(biāo)準(zhǔn)日志記錄器的初始值
LstdFlags = Ldate | Ltime
)
log 包有一個很方便的地方就是热监,這些日志記錄器是多 goroutine 安全的捺弦。這意味著在多個goroutine 可以同時調(diào)用來自同一個日志記錄器的這些函數(shù),而不會有彼此間的寫沖突狼纬。標(biāo)準(zhǔn)日志記錄器具有這一性質(zhì)羹呵,用戶定制的日志記錄器也應(yīng)該滿足這一性質(zhì)。
2.定制的日志記錄器
// 這個示例程序展示如何創(chuàng)建定制的日志記錄器
package main
import (
"io"
"io/ioutil"
"log"
"os"
)
var (
Trace *log.Logger // 記錄所有日志
Info *log.Logger // 重要的信息
Warning *log.Logger // 需要注意的信息
Error *log.Logger // 非常嚴(yán)重的問題
)
func init() {
file, err := os.OpenFile("errors.txt",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open error log file:", err)
}
Trace = log.New(ioutil.Discard,
"TRACE: ",
log.Ldate|log.Ltime|log.Lshortfile)
Info = log.New(os.Stdout,
"INFO: ",
log.Ldate|log.Ltime|log.Lshortfile)
Warning = log.New(os.Stdout,
"WARNING: ",
log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(io.MultiWriter(file, os.Stderr),
"ERROR: ",
log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
Trace.Println("I have something standard to say")
Info.Println("Special Information")
Warning.Println("There is something you need to know about")
Error.Println("Something has failed")
}
------------------------------------
INFO: 2019/04/09 14:37:11 TestCustomLog.go:44: Special Information
ERROR: 2019/04/09 14:37:11 TestCustomLog.go:46: Something has failed
WARNING: 2019/04/09 14:37:11 TestCustomLog.go:45:
There is something you need to know about
golang.org/src/log/log.go
// New 創(chuàng)建一個新的 Logger疗琉。out 參數(shù)設(shè)置日志數(shù)據(jù)將被寫入的目的地
// 參數(shù) prefix 會在生成的每行日志的最開始出現(xiàn)
// 參數(shù) flag 定義日志記錄包含哪些屬性
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
Discard 是一個 io.Writer 接口冈欢,調(diào)用它的 Write 方法將不做任何事情并且始終成功返回。當(dāng)某個等級的日志不重要時盈简,使用 Discard 變量可以禁用這個等級的日志凑耻。
// devNull 是一個用 int 作為基礎(chǔ)類型的類型
type devNull int
// Discard 是一個 io.Writer太示,所有的 Write 調(diào)用都不會有動作,但是會成功返回
var Discard io.Writer = devNull(0)
// io.Writer 接口的實現(xiàn)
func (devNull) Write(p []byte) (int, error) {
return len(p), nil
}
golang.org/src/os/file.go
// Stdin香浩、Stdout 和 Stderr 是已經(jīng)打開的文件类缤,分別指向標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和
// 標(biāo)準(zhǔn)錯誤的文件描述符
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
os/file_unix.go
// NewFile 用給出的文件描述符和名字返回一個新 File
func NewFile(fd uintptr, name string) *File {
可以看到 3 個變量的聲明邻吭,分別表示所有操作系統(tǒng)里都有的 3 個標(biāo)準(zhǔn)輸入/輸出餐弱,即 Stdin、Stdout 和 Stderr囱晴。這 3 個變量都被聲明為 File 類型的指針膏蚓,這個類型實現(xiàn)了 io.Writer 接口。
MultiWriter 函數(shù)調(diào)用會返回一個 io.Writer接口類型值畸写,這個值包含之前打開的文件file驮瞧,以及 stderr。MultiWriter 函數(shù)是一個變參函數(shù)枯芬,可以接受任意個實現(xiàn)了io.Writer 接口的值论笔。這個函數(shù)會返回一個io.Writer 值,這個值會把所有傳入的 io.Writer 的值綁在一起千所。當(dāng)對這個返回值進(jìn)行寫入時狂魔,會向所有綁在一起的io.Writer 值做寫入。這讓類似log.New 這樣的函數(shù)可以同時向多個Writer 做輸出≌媛現(xiàn)在毅臊,當(dāng)我們使用Error 記錄器記錄日志時,輸出會同時寫到文件和stderr黑界。
每個記錄器變量都包含一組方法管嬉,這組方法與 log 包里實現(xiàn)的那組函數(shù)完全一致
func (l *Logger) Fatal(v ...interface{})
func (l *Logger) Fatalf(format string, v ...interface{})
func (l *Logger) Fatalln(v ...interface{})
func (l *Logger) Flags() int
func (l *Logger) Output(calldepth int, s string) error
func (l *Logger) Panic(v ...interface{})
func (l *Logger) Panicf(format string, v ...interface{})
func (l *Logger) Panicln(v ...interface{})
func (l *Logger) Prefix() string
func (l *Logger) Print(v ...interface{})
func (l *Logger) Printf(format string, v ...interface{})
func (l *Logger) Println(v ...interface{})
func (l *Logger) SetFlags(flag int)
func (l *Logger) SetPrefix(prefix string)
二、第三方log庫
1.簡要匯總
參考
golang中的log庫
Golang的log包哪個好用朗鸠?
在Github中最受歡迎的Go日志庫集合
截止2019.5.24star數(shù)統(tǒng)計
- logrus 11018star 近期還在更新
- zap 6901star 近期還在更新
- oklog 2781star 近期還在更新
- glog 2214star 已經(jīng)很久不更新
- seelog 1312star 已經(jīng)很久不更新
- zerolog 1981star 近期還在更新
2.選擇JSON格式的日志
王津橇谩:最佳日志實踐
最佳日志實踐(v2.0)
達(dá)達(dá) JSON日志文件
舉個例子,在引入JSON格式的日志之后烛占,我很快就用PHP做了一個日志查看頁面胎挎,因為信息是結(jié)構(gòu)化的,這個查看頁面要做過濾篩選或者特殊顯示就變得方便許多忆家,不是先前做不到犹菇,而是做起來很繁瑣。但是JSON日志比起二進(jìn)制文件格式來說芽卿,有個明顯的問題是JSON導(dǎo)致的文件體積膨脹揭芍。
logrus也是支持json格式日志的,可以參考Golang之使用Logrus
3.性能
參考
如何開發(fā)高性能 go 組件卸例?
Go零消耗debug log技巧
玩轉(zhuǎn)高性能日志庫ZAP (1)
玩轉(zhuǎn)高性能日志庫ZAP (2)
玩轉(zhuǎn)高性能日志庫ZAP(3)
看到題目称杨,有人肯定會問肌毅,官方的log模塊不好么? Debug Log一般很長姑原,在生產(chǎn)環(huán)境還輸出的話悬而,也很難找。 再者锭汛,log的消耗是比較大的笨奠,特別是需要打印行號時。如果在代碼中有大量的debug日志唤殴,這個損耗累積起來艰躺,那也是相當(dāng)驚人的了。
我們的業(yè)務(wù)通常會記錄大量的 Debug 日志眨八,但在實際測試過程中,發(fā)現(xiàn)我們使用的日志庫 seelog 性能存在嚴(yán)重的瓶頸左电,在我們的對比結(jié)果中發(fā)現(xiàn):zap 表現(xiàn)非常突出廉侧,單線程 Qps 也是 logrus、seelog 的數(shù)倍篓足。
其實我們最初的實踐中并沒有意識到日志框架的性能的重要性段誊,直到開發(fā)后期進(jìn)行系統(tǒng)的 benchmark 總是不盡人意,而且在不同的日志級別下性能差距明顯栈拖。通過 go profiling 看到日志組件對于計算資源的消耗十分巨大连舍,因此決心將其替換為一個高性能的日志框架,這也是選擇用 zap 的一個重要的考量的點涩哟。
目前我們使用 zap 已有2年多的時間索赏,zap 很好地解決了日志組件的低性能的問題。目前 zap 也從 beta 發(fā)布到了 1.8版本贴彼,對于 zap 我們不僅僅看到它的高性能潜腻,更重要的是理解它的設(shè)計與工程實踐。日志屬于 io 密集型的組件器仗,這類組件如何做到高性能低成本融涣,這也將直接影響到服務(wù)成本。