Log顧名思義就是紀錄,通常在Backend當中會將可預期或非預期的錯誤記錄在此,除此之外也會記錄些測試用資料以及警訊資料用以幫忙我們除錯/紀錄/開發。
那Backend Log的產生都是發生在Server端,目前大部分的Backend設計也都透過API, Application,並採用Client/Broswer與Server的架構完成,也因此可將Backend Log分為三大種類
定義使用者在介面上的操作,像是新增資料、上傳檔案、發送訊息等。,通常表示一個業務邏輯的執行與實現,那裡頭每次的動作可能會包含多次的API呼叫,而每次的呼叫都屬於一項操作。Event Log的目的就是紀錄使用者的操作記錄,以此可以用來檢測、除錯與統計資料等。
由Server執行過程中所產生的Log,用來追蹤系統實際運行狀況,與資料的流動情形。而且在Exception發生時就是紀錄在此,好讓後續可以追蹤分析與解決。
人如其名,就是Request在傳遞時所記錄的訊息,但由於Requests繁多導致紀錄成本龐大,因此大部分情況只會紀錄HTTP Header甚至是不紀錄Request,只有在發生Error時才會紀錄完整Request資料。
TRACE
<DEBUG
<INFO
<WARN
<ERROR
<FATAL
<PANIC
Log除了類型以外也有優先序的差別,當log訊息等級高於你所設定的日誌等級,
則該訊息不會紀錄輸出。
當你設定越高的層級,你的紀錄成本也會越大,那通常在production environment只會預設為ERROR
或是WARN
而已。那下面就依照不同的Level依序介紹
最高等級的log,主要用來追蹤某些小事。
紀錄有用的debug資訊。
當有些有用的事件發生時,會紀錄的資訊。
警示訊息,雖然不會對系統有重大影響,但需要注意一下該訊息。
發生了錯誤,但系統依舊在運作。
最緊急的錯誤,一但發生需要立即hotflix。
最低等級的log,當調用Panic時發生。
這邊我們選用github.com/sirupsen/logrus 這個相當多人使用的log package將Log實作在Gin Backend Server當中。那logrus也有著以下的幾個優點:
go get -u github.com/sirupsen/logrus
/app/middleware/log.go
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"os"
"path"
"time"
)
func Logger() *logrus.Logger {
now := time.Now()
logFilePath := ""
if dir, err := os.Getwd(); err == nil {
logFilePath = dir + "/logs/"
}
if err := os.MkdirAll(logFilePath, 0777); err != nil {
fmt.Println(err.Error())
}
logFileName := now.Format("2006-01-02") + ".log"
fileName := path.Join(logFilePath, logFileName)
if _, err := os.Stat(fileName); err != nil {
if _, err := os.Create(fileName); err != nil {
fmt.Println(err.Error())
}
}
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("err", err)
}
logger := logrus.New()
logger.Out = src
logger.SetLevel(logrus.DebugLevel)
logger.SetFormatter(&logrus.TextFormatter{
TimestampFormat: "2021-01-01 11:11:11",
})
return logger
}
func LoggerToFile() gin.HandlerFunc {
logger := Logger()
return func(c *gin.Context) {
startTime := time.Now()
c.Next()
endTime := time.Now()
latencyTime := endTime.Sub(startTime)
reqMethod := c.Request.Method
reqUri := c.Request.RequestURI
statusCode := c.Writer.Status()
clientIP := c.ClientIP()
logger.Infof("| %3d | %13v | %15s | %s | %s |",
statusCode,
latencyTime,
clientIP,
reqMethod,
reqUri,
)
}
}
Logger()
: 主要是設定輸出檔案的位置、輸出格式、檔案命名等設定,在init好LogFile之後藉由logrus.New()
實體化一個logrus物件,並設定所需的log level與TIme Format,並將log寫入LogFile之中。
LoggerToFile()
: Gin的log middleware,我們會將時間相關資訊與gin.Context
中的Request Method, URL, StatusCode與IP等資訊也一併寫入log之中。main.go
server := gin.Default()
server.Use(middleware.LoggerToFile())
server.Use(middleware.CORSMiddleware())
server.GET("/hc", func(c *gin.Context) {
c.String(http.StatusOK, fmt.Sprintf("Health Check"))
middleware.Logger().WithFields(logrus.Fields{
"name": "Flynn Sun",
}).Info("Health Check", "Info")
})
我們讓Gin使用LoggerToFile()
這個Middleware,並在health check endpoint當中寫入INFO Level的log加以測試。
docker-compose.yaml
ironman-2021:
container_name: ironman-2021
ports:
- "8080:8080"
build:
context: ./
dockerfile: Dockerfile
command: ./main
restart: always
networks:
- backend-bridge
depends_on:
- postgresql
- redis
volumes:
- ./logs/:/usr/local/go/src/ironman-2021/logs
我們在Gin的Container中將logs folder的log file都mount出來,讓我們比較容易去觀察與紀錄log。
最後我們則是來啟動docker-compose —build 並 GET http://localhost:8080/hc 進行測試。
然後會在/logs/2021-10-09.log當中得到以下內容
time="90910-10-10 1010:1010:1010" level=info msg="Health CheckInfo" name="Flynn Sun"
time="90910-10-10 1010:1010:1010" level=info msg="| 200 | 6.7669ms | 192.168.160.1 | GET | /hc |"
這也證實我們的logger實裝完成!
Logger在Backend Server當中是必不可缺的一環,因為再怎麼完美的系統也一定會有著漏洞,如果屆時沒有logger的話,會相當難找到問題所在!
這次的程式碼我也會放在下方連結,歡迎大家享用。
https://github.com/Neskem/Ironman-2021/tree/Day-26