fmt打印不显示 go_程序猿学Go: 日志系统
一:log日志包
Golang的log包短小精悍,可以非常輕松的實現日志打印轉存功能。不用多說,log支持并發操作(即協程安全-相對于JAVA中的線程安全而言),其結構定義如下:
type Logger struct {mu sync.Mutex // ensures atomic writes; protects the following fieldsprefix 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創建
// New creates a new Logger. The out variable sets the // destination to which log data will be written. // The prefix appears at the beginning of each generated log line. // The flag argument defines the logging properties. func New(out io.Writer, prefix string, flag int) *Logger {return &Logger{out: out, prefix: prefix, flag: flag} }log包已默認提供了一個日志對象,并封裝了包級別的常用函數,該對象將日志信息輸出到標準輸出設備中(開箱即用)。
如果只是想輸出到終端而不保存到文件等其它地方時,可以直接通過log.Xxxx()方式直接調用,因為這些包級別的函數只是對std對象相關方法的簡單封裝,如println函數定義如下:
// Println calls Output to print to the standard logger. // Arguments are handled in the manner of fmt.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 mainimport ("log" )func main(){arr := []int {2,3}log.Print("Print array ",arr,"n")log.Println("Println array",arr)log.Printf("Printf array with item [%d,%d]n",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 mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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 = alevellogger, 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 mainimport ("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, // megabytesMaxBackups: 3, // 最多保留3個備份MaxAge: 7, //daysCompress: true, // 是否壓縮 disabled by default}w := zapcore.AddSync(&hook)var level zapcore.Levelswitch loglevel {case "debug":level = zap.DebugLevelcase "info":level = zap.InfoLevelcase "error":level = zap.ErrorLeveldefault:level = zap.InfoLevel}encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncodercore := 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查閱對應的資料即可。
三:依賴管理govendor
長期以來,golang 對外部依賴都沒有很好的管理方式,只能從 $GOPATH 下查找依賴。這就造成不同用戶在安裝同一個項目適合可能從外部獲取到不同的依賴庫版本,同時當無法聯網時,無法編譯依賴缺失的項目。
自 1.5 版本開始引入 govendor 工具,該工具將項目依賴的外部包放到項目下的 vendor 目錄下(對比 nodejs 的 node_modules 目錄),并通過 vendor.json 文件來記錄依賴包的版本,方便用戶使用相對穩定的依賴。
對于 govendor 來說,主要存在三種位置的包:項目自身的包組織為本地(local)包;傳統的存放在 $GOPATH 下的依賴包為外部(external)依賴包;被 govendor 管理的放在 vendor 目錄下的依賴包則為 vendor 包。
通過指定包類型,可以過濾僅對指定包進行操作。
命令 功能
| 命令 | 含義 |
| init | 初始化 vendor 目錄 |
| list | 列出所有的依賴包 |
| add | 添加包到 vendor 目錄,如 govendor add +external 添加所有外部包 |
| add PKG_PATH | 添加指定的依賴包到 vendor 目錄 |
| update | 從 $GOPATH 更新依賴包到 vendor 目錄 |
| remove | 從 vendor 管理中刪除依賴 |
| status | 列出所有缺失、過期和修改過的包 |
| fetch | 添加或更新包到本地 vendor 目錄 |
| sync | 本地存在 vendor.json 時候拉去依賴包,匹配所記錄的版本 |
| get | 類似 go get 目錄,拉取依賴包到 vendor 目錄 |
具體使用流程:
1 下載govendor
go get -u github.com/kardianos/govendor2 初始化
govendor init進入src/projectname,生成vendor文件,里面包含一個vendor.json
3 將GOPATH中本工程使用到的依賴包自動移動到vendor目錄中
#說明:如果本地GOPATH沒有依賴包,先go get相應的依賴包
govendor add +external4 #Go 1.6以上版本默認開啟 GO15VENDOREXPERIMENT 環境變量,可忽略該步驟。
#通過設置環境變量 GO15VENDOREXPERIMENT=1 使用vendor文件夾構建文件。
#可以選擇 export GO15VENDOREXPERIMENT=1 或 GO15VENDOREXPERIMENT=1 go build 執行編譯
export GO15VENDOREXPERIMENT=15 govendor —help
6: 從遠程拉取包到vendor下并記錄進vendor.json,gopath目錄下不會有拉下來的包
govendor fetch [包鏈接,如:github.com/BurntSushi/toml]7:根據已有的vendor.json從遠程拉取包到vendor目錄下,gopath目錄下不會有拉下來的包 可以把現有的json文件copy過來
govendor sync8 :添加包
添加包會同時在json文件中記錄
把gopath下的包(只會是被項目引用到的包)添加到vendor目錄下 必須vendor目錄下沒有,且vendor.json中沒有記錄這個包的時候才會添加,其中一個存在則命令無效亦不報錯。
govendor add +external(+e)9:把項目中的包添加到vendor目錄下 必須vendor目錄下沒有,且vendor.json中沒有記錄這個包的時候才會添加,其中一個存在則命令無效亦不報錯
govendor add +local(+l)10:把標準庫的包添加到vendor目錄下 必須vendor目錄下沒有,且vendor.json中沒有記錄這個包的時候才會添加,其中一個存在則命令無效亦不報錯。
govendor add +std(+s)11 把主程序包(main包)添加到vendor目錄下 必須vendor目錄下沒有,且vendor.json中沒有記錄這個包的時候才會添加,其中一個存在則命令無效亦不報錯
govendor add +program(+p)12:把指定包添加進vendor目錄 vendor目錄下已存在會報錯,vendor.json存在該包記錄不會報錯會直接覆蓋該記錄。
govendor add [包鏈接,如:http://github.com/BurntSushi/toml]
13:添加所有的包,包括gopath、go標準庫、項目中的包 gopath和標準庫下的包必須是被項目或者項目中引用到的包引用的才會添加
govendor add +all(+a)14:移除包的時候會把vendor目錄和json文件的記錄一起移除,只有vendor或者只有json中有仍然會移除而不提示
govendor remove +local(+l)15:移除未被項目引用的包
govendor remove +unused(+u)16 :移除指定包
govendor remove [包鏈接,如:github.com/BurntSushi/toml]17 :移除vendor下所有的包
govendor remove +vendor關于’+'后面的包類型的說明
| 狀態 | 縮寫狀態 | 含義 |
| +local | l | 本地包,即項目自身的包組織 |
| +external | e | 外部包,即被 $GOPATH 管理,但不在 vendor 目錄下 |
| +vendor | v | 已被 govendor 管理,即在 vendor 目錄下 |
| +std | s | 標準庫中的包 |
| +unused | u | 未使用的包,即包在 vendor 目錄下,但項目并沒有用到 |
| +missing | m | 代碼引用了依賴包,但該包并沒有找到 |
| +program | p | 主程序包,意味著可以編譯為執行文件 |
| +outside | o | 外部包和缺失的包 |
| +all | a | 所有的包 |
? 說明
govendor只是用來管理項目的依賴包,如果GOPATH中本身沒有項目的依賴包,則需要通過go get先下載到GOPATH中,再通過govendor add +external拷貝到vendor目錄中。Go 1.6以上版本默認開啟GO15VENDOREXPERIMENT環境變量。
? 其他常見命令
# View your work. govendor list# Look at what is using a package govendor list -v fmt# Specify a specific version or revision to fetch govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55 govendor fetch golang.org/x/net/context@v1 # Get latest v1.*.* tag or branch. govendor fetch golang.org/x/net/context@=v1 # Get the tag or branch named "v1".# Update a package to latest, given any prior version constraint govendor fetch golang.org/x/net/context# Format your repository only govendor fmt +local總結
以上是生活随笔為你收集整理的fmt打印不显示 go_程序猿学Go: 日志系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大学物理实验试卷1到8_物理实验在绝对的
- 下一篇: go 判断元素是否在slice_Go内置