1、xerrors 異常
xerrors 包是一個非常棒的設計,不同於往常語言如java/php,因為go的errors只是一個string類型的映射,所以內存占用空間很少。這在golang的核心庫和golang大多數開源模塊中使用,簡單,高效,穩定!比如:
var myErr:=errors.New("error msg") func act1()err{ return myErr }
以上代非常高效,如果你經常做go的開發時。這種解決時經常會存在一個很頭疼的問題,就是異常觸發點很難定位,因為你很難去定位到異常發生的代碼文件及行數和調用的堆棧,但出現問題時非常必要知道是什么情況處罰的這個異常,只是一個message不足以讓開發人員去解決問題。
在最新的xerrors包中,引入了一個caller堆地址,通過使用Format接口可以格式化出調用第一行的堆,但這往往很多時候不足以解決去定位問題。這時可以考慮使用第三方包,請繼續往下看:)
2、堆棧異常處理
go.mod
require github.com/pkg/errors latest
go mod download
package main import( "fmt" "github.com/pkg/errors" ) func act1()error{ return errors.New("hello world!") } func main(){ fmt.Printf("%+v\n",act1()) }
Output:
Hello world!
main.act1
/tmp/aa/main.go:9
main.main
/tmp/aa/main.go:13
runtime.main
/usr/local/Cellar/go/1.13.4/libexec/src/runtime/proc.go:203
runtime.goexit
/usr/local/Cellar/go/1.13.4/libexec/src/runtime/asm_amd64.s:1357
以上代碼中,首先引用pkg/errrors堆異常包,然后通過使用%+v格式化error,將堆棧數據打印到屏幕中,通過這種方式,通過調用堆棧你可以快速定位異常的代碼位置,從而快速解決問題。
3、go原生日志框架
glog是Golang log的縮寫,開源於golang核心庫,所以應用比較廣泛實現如下:
package main import "log" func main(){ log.Println("Info","hello world") }
output:
2019/11/29 17:44:56 Info hello world
因為功能過於簡單,導致在golang中的日志很難實現日志的來源和日志級別實現日志的過濾功能。在很多模塊集成后,導致日志繁雜錯亂很難閱讀,甚至到致不同模塊包括的日志格式不同以及日志的級別也不一致,導致日志的分析帶來了不少挑戰!
4、midlog 日志框架
midlog類似於java的slf4j框架,定義了通用的日志級別、日志來源鎖定、日志重定向框架,並且提供日志的重定義組件,可以將所有日志匯總處理,非常方便!
midlog 日志框架示
4.1、midlog 包接取
go.mod
require github.com/lingdor/midlog
or
go get github.com/lingdor/midlog
4.2、midlog通用日志函數
創建日志對象
var Logger Midlog = New("your module name")
建議這個Logger聲明為外部可訪問的,這樣如果想實現日志的分流時,可以通過這個Logger對象判斷日志的來源模塊是哪來的,然后分流不同的日志記錄。
Logger.Trace("hello") Logger.Info("hello") Logger.Warn("hello") Logger.Debug("hello") Logger.Error1("hello") Logger.Error2("hello") Logger.Error3("hello") Logger.Log(LevelTrace, "hello") Logger.Infof("hello %s","world!") Logger.Tracef("hello %s","world!") Logger.Warnf("hello %s","world!") Logger.Debugf("hello %s","world!") Logger.Error1f("hello %s","world!") Logger.Error2f("hello %s","world!") Logger.Error3f("hello %s","world!") Logger.Logf(LevelTrace, "hello %s","world!")
可以按照不同的日志級別,選擇不同的函數,實現日志的記錄!
4.3、對接go原生日志框架
go.mod 增加依賴
require github.com/lingdor/midlog v1.0.0 require github.com/lingdor/glog2midlog v1.0.0
package main import ( _ "github.com/lingdor/glog2midlog" "github.com/lingdor/midlog-examples/library1" "log" ) func main() { log.Println("info", "hello world!") }
output: \
2019-11-29 18:15:51 GLOG INFO hello world!
4.4 對接logrus
go.mod
require github.com/lingdor/midlog v1.0.0 require github.com/lingdor/midlog2logrus v1.4.3
main.go
package main import ( "fmt" "github.com/lingdor/midlog" _ "github.com/lingdor/midlog2logrus" ) var Logger = midlog.New("useLogrus") func main() { Logger.Info("hello world!") Logger.Error1("logger errror log") Logger.Ext(midlog.ExtMap{"123": "456"}).Error1("ext log") fmt.Println("done") }
output:
INFO[0000] hello world!
ERRO[0000] logger errror log
ERRO[0000] ext log 123=456
done
4.5 對接zap
go.mod
require github.com/lingdor/midlog v1.0.0 require github.com/lingdor/midlog2zap v1.13.0
main.go
package main import ( "fmt" "github.com/lingdor/midlog" _ "github.com/lingdor/midlog2zap" ) var Logger = midlog.New("useLogrus") func main() { Logger.Info("hello world!") Logger.Error1("logger errror log") Logger.Ext(midlog.ExtMap{"123": "456"}).Info("ext log") fmt.Println("done") }
output:
{"level":"info","ts":1575022710.237491,"caller":"midlog2zap@v1.13.0/zapWriter.go:36","msg":"hello world!"}
{"level":"error","ts":1575022710.23755,"caller":"midlog2zap@v1.13.0/zapWriter.go:37","msg":"logger errror log","stacktrace":"github.com/lingdor/midlog2zap.ZapWriter.Write\n\t/Users/bobby96333/go/pkg/mod/github.com/lingdor/midlog2zap@v1.13.0/zapWriter.go:37\ngithub.com/lingdor/midlog.(*midlogT).tryWriteLog\n\t/Users/bobby96333/go/pkg/mod/github.com/lingdor/midlog@v1.0.0/logWriter.go:19\ngithub.com/lingdor/midlog.(*midlogT).logWithCaller3\n\t/Users/bobby96333/go/pkg/mod/github.com/lingdor/midlog@v1.0.0/midlog.go:122\ngithub.com/lingdor/midlog.(*midlogT).Error1\n\t/Users/bobby96333/go/pkg/mod/github.com/lingdor/midlog@v1.0.0/midlog.go:69\nmain.main\n\t/Users/bobby96333/go/midlog-examples/useZap/main.go:13\nruntime.main\n\t/usr/local/Cellar/go/1.13.4/libexec/src/runtime/proc.go:203"}
{"level":"info","ts":1575022710.237587,"caller":"midlog2zap@v1.13.0/zapWriter.go:36","msg":"ext log","123":"456"}
done
5、總結
實際上midlog只是一個日志接口層,並沒有實現日志的滾動寫入、異步等日志實際操作,但通過這個中間層,可以靈活對接自己選擇的日志框架,和分流功能。然后通過zap/logrus/seelog/zerolog等模塊組合實現日志的存儲.
不管是go的原生error還是log,都是一個非常棒的設計,很多時候可以解決最基礎的需求,這避免了很多時候因為不需要而避免的額外性能消耗。而還有一部份場景,只是簡單往往不足以解決問題,這導致簡單和復雜的沖突哲學層面的取舍。我們在選擇時,應該清楚了解自己的需求是什么,按照自己的需要去選擇合適的模塊,即可!
6、參考資料
謝謝