日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Go?Web編程添加服務器錯誤和訪問日志_Golang

作者:KevinYan11 ? 更新時間: 2022-08-18 編程語言

前言

錯誤日志和訪問日志是一個服務器必須支持的功能,我們教程里使用的服務器到目前為止還沒有這兩個功能。正好前兩天也寫了篇介紹logrus日志庫的文章,那么今天的文章里就給我們自己寫的服務器加上錯誤日志和訪問日志的功能。在介紹添加訪問日志的時候會介紹一種通過編寫中間件獲取HTTP響應的StausCodeBody的方法。

Go Web 編程系列的每篇文章的源代碼都打了對應版本的軟件包,供大家參考。公眾號中回復gohttp11獲取本文源代碼

初始化日志記錄器

我們先來做一下初始化工作,在項目里初始化記錄錯誤日志和訪問日志的記錄器Logger

//?./utils/vlog
package?vlog
import?(
????"github.com/sirupsen/logrus"
????"os"
)
var?ErrorLog?*logrus.Logger
var?AccessLog?*logrus.Logger
var?errorLogFile?=?"./tmp/log/error.log"
var?accessLogFile?=?"./tmp/log/access.log"
func?init?()?{
????initErrorLog()
????initAccessLog()
}
func?initErrorLog()?{
????ErrorLog?=?logrus.New()
????ErrorLog.SetFormatter(&logrus.JSONFormatter{})
????file?,?err?:=?os.OpenFile(errorLogFile,?os.O_RDWR?|?os.O_CREATE?|?os.O_APPEND,?0755)
????if?err?!=?nil?{
????????panic(err)
????}
????ErrorLog.SetOutput(file)
}
func?initAccessLog()?{
????AccessLog?=?logrus.New()
????AccessLog.SetFormatter(&logrus.JSONFormatter{})
????file?,?err?:=?os.OpenFile(accessLogFile,?os.O_RDWR?|?os.O_CREATE?|?os.O_APPEND,?0755)
????if?err?!=?nil?{
????????panic(err)
????}
????AccessLog.SetOutput(file)
}
  • 我們新定義一個packageinit函數中來初始化記錄器,這樣服務器成功啟動前就會初始化好記錄器。
  • /tmp/log這個目錄要提前創建好,執行init函數時會自動創建好access.logerror.log

添加錯誤日志

我們創建服務器使用的net/http包的Server類型中,有一個ErrorLog字段供開發者設置記錄錯誤日志用的記錄器Logger,默認使用的是log包默認的記錄器(應該是系統的標準錯誤):

type?Server?struct?{
???Addr????string??//?TCP?address?to?listen?on,?":http"?if?empty
???Handler?Handler?//?handler?to?invoke,?http.DefaultServeMux?if?nil
???...
???//?ErrorLog?specifies?an?optional?logger?for?errors?accepting
???//?connections,?unexpected?behavior?from?handlers,?and
???//?underlying?FileSystem?errors.
???//?If?nil,?logging?is?done?via?the?log?package's?standard?logger.
???ErrorLog?*log.Logger
?????...
}

我們之前在創建服務器的時候自己實現了Server類型的對象,那么現在要做的就是將上面初始化好的錯誤日志的記錄器指定給ServerErrorLog字段。

func?main()?{
??...
? ??//?將logrus的Logger轉換為io.Writer
????errorWriter?:=?vlog.ErrorLog.Writer()
? ??//?記得關閉io.Writer
????defer?errorWriter.Close()
????server?:=?&http.Server{
????????Addr:????":8080",
????????Handler:?muxRouter,
? ? ? ??//?用記錄器轉換成的io.Writer創建log.Logger
????????ErrorLog:?log.New(vlog.ErrorLog.Writer(),?"",?0),
????}
????...
}

添加好錯誤日志的記錄器后,我們找個路由處理函數,在里面故意制造運行時錯誤驗證一下是否能記錄到錯誤。

func?(*HelloHandler)?ServeHTTP(w?http.ResponseWriter,?r?*http.Request)?{
???ints?:=?[]int{0,?1,?2}
???fmt.Fprintf(w,?"%v",?ints[0:5])
}

在上面處理函數中,通過切片表達式越界故意制造了一個運行時錯誤,打開error.log后能看到文件里已經記錄到這個運行時錯誤及其Stack trace

添加訪問日志

Server對象可以設置錯誤日志的記錄器不一樣,訪問日志只能是我們通過自己編寫中間件的方式來實現了。在記錄訪問日志的中間件里我們會記錄ipmethodpathqueryrequest_bodystatusresponse_body這些個字段的內容。

statusresponse_body兩個字段來自請求對應的響應。響應在net/http包里是用http.ResponseWriter接口表示的

type?ResponseWriter?interface?{
????Header()?Header
????Write([]byte)?(int,?error)
????WriteHeader(statusCode?int)
}

接口本身以及net/http提供的實現都沒有讓我們進行讀取的方法,所以在編寫的用于記錄訪問日志的中間件里需要對net/http庫本身實現的ResponseWriter做一層包裝。

利用Go語言結構體類型嵌套匿名類型后,結構體擁有了被嵌套類型的所有導出字段和方法的特性,我們可以很方便地對原來的ResponseWriter做一層包裝,然后只重新實現需要更改的方法即可:

type?ResponseWithRecorder?struct?{
???http.ResponseWriter
???statusCode?int
???body?bytes.Buffer
}
func?(rec?*ResponseWithRecorder)?WriteHeader(statusCode?int)?{
???rec.ResponseWriter.WriteHeader(statusCode)
???rec.statusCode?=?statusCode
}
func?(rec?*ResponseWithRecorder)?Write(d?[]byte)?(n?int,?err?error)?{
???n,?err?=?rec.ResponseWriter.Write(d)
???if?err?!=?nil?{
??????return
???}
???rec.body.Write(d)
???return
}

定義好新的類型后我們重新實現了WriteHeaderWrite方法,在向原來的ReponseWriter中寫入后也會向ResponseWriteRecoder.statusCode和ResponseWriteRecoder.body寫入對應的數據。這樣我們就可以在中間件里通過這兩個字段訪問響應碼和響應數據了。

記錄訪問日志的中間件定義如下:

func?AccessLogging?(f?http.Handler)?http.Handler?{
????//?創建一個新的handler包裝http.HandlerFunc
????return?http.HandlerFunc(func(w?http.ResponseWriter,?r?*http.Request)?{
????????buf?:=?new(bytes.Buffer)
????????buf.ReadFrom(r.Body)
????????logEntry?:=?vlog.AccessLog.WithFields(logrus.Fields{
????????????"ip":?r.RemoteAddr,
????????????"method":?r.Method,
????????????"path":?r.RequestURI,
????????????"query":?r.URL.RawQuery,
????????????"request_body":?buf.String(),
????????})
????????wc?:=?&ResponseWithRecorder{
????????????ResponseWriter:?w,
????????????statusCode:?http.StatusOK,
????????????body:?bytes.Buffer{},
????????}
????????//?調用下一個中間件或者最終的handler處理程序
????????f.ServeHTTP(wc,?r)
????????defer?logEntry.WithFields(logrus.Fields{
????????????"status":?wc.statusCode,
????????????"response_body":?wc.body.String(),
????????}).Info()
????})
}

Router上應用創建好的AccessLogging中間件后,就可以正常的記錄服務器的訪問日志了。

//?router/router.go
func?RegisterRoutes(r?*mux.Router)?{
????...
????//?apply?Logging?middleware
????r.Use(middleware.Logging(),?middleware.AccessLogging)
????...
}

不過有兩點需要注意一下

  • 這里為了演示獲取響應數據記錄了response_body字段,如果是接口響應內容記錄下還可以,但是如果是HTML還是不記錄的為好。
  • 初始化ResponseWithRecorder時默認設置了statusCode是因為,服務器正確返回響應時不會顯式調用WriteHeader方法,只有在返回NOT_FOUND之類的錯誤的時候才會調用WriteHeader方法,針對這種情況需要在初始化的時候把statusCode的默認值設置為200

現在再訪問服務器后打開access.log會看到剛剛的訪問日志,就能看到剛剛請求的urlmethod,客戶端IP等信息了。

{"ip":"......","level":"info","method":"GET","msg":"","path":"/index/","query":"","request_body":"","response_body":"Hello?World1","status":200,"time":"2020-03-26T04:21:46Z"}

注意:文章只為說明演示方便,獲取IP的方法無法獲取代理后的真實IP,請悉知。

原文鏈接:http://170e.cn/864o

欄目分類
最近更新