這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
API's - logging
在上一節中,我們解決了API's可以任意訪問的問題,那麼我們現在還有一個問題。
就是我們的日誌,都是輸出到控制台上的,這顯然對於一個項目來說是不合理的,因此我們這一節簡單封裝log庫,使其支援簡單的檔案日誌!
建立logging包
我們在pkg下建立logging目錄,建立file.go和log.go檔案,寫入內容:
- file.go:
package loggingimport ( "os" "time" "fmt" "log")var ( LogSavePath = "runtime/logs/" LogSaveName = "log" LogFileExt = "log" TimeFormat = "20060102")func getLogFilePath() string { return fmt.Sprintf("%s", LogSavePath)}func getLogFileFullPath() string { prefixPath := getLogFilePath() suffixPath := fmt.Sprintf("%s%s.%s", LogSaveName, time.Now().Format(TimeFormat), LogFileExt) return fmt.Sprintf("%s%s", prefixPath, suffixPath)}func openLogFile(filePath string) *os.File { _, err := os.Stat(filePath) switch { case os.IsNotExist(err): mkDir(getLogFilePath()) case os.IsPermission(err): log.Fatalf("Permission :%v", err) } handle, err := os.OpenFile(filePath, os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644) if err != nil { log.Fatalf("Fail to OpenFile :%v", err) } return handle}func mkDir(filePath string) { dir, _ := os.Getwd() err := os.MkdirAll(dir + "/" + getLogFilePath(), os.ModePerm) if err != nil { panic(err) }}
os.Stat:返迴文件資訊結構描述檔案。如果出現錯誤,會返回*PathError
type PathError struct { Op string Path string Err error}
os.IsNotExist:能夠接受ErrNotExist、syscall的一些錯誤,它會返回一個布爾值,能夠得知檔案不存在或目錄不存在
os.IsPermission:能夠接受ErrPermission、syscall的一些錯誤,它會返回一個布爾值,能夠得知許可權是否滿足
os.OpenFile:調用檔案,支援傳入檔案名稱、指定的模式調用檔案、檔案許可權,返回的檔案的方法可以用於I/O。如果出現錯誤,則為*PathError。
const ( // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified. O_RDONLY int = syscall.O_RDONLY // 以唯讀模式開啟檔案 O_WRONLY int = syscall.O_WRONLY // 以唯寫模式開啟檔案 O_RDWR int = syscall.O_RDWR // 以讀寫入模式開啟檔案 // The remaining values may be or'ed in to control behavior. O_APPEND int = syscall.O_APPEND // 在寫入時將資料追加到檔案中 O_CREATE int = syscall.O_CREAT // 如果不存在,則建立一個新檔案 O_EXCL int = syscall.O_EXCL // 使用O_CREATE時,檔案必須不存在 O_SYNC int = syscall.O_SYNC // 同步IO O_TRUNC int = syscall.O_TRUNC // 如果可以,開啟時)
os.Getwd:返回與目前的目錄對應的根路徑名
os.MkdirAll:建立對應的目錄以及所需的子目錄,若成功則返回nil,否則返回error
os.ModePerm:const定義ModePerm FileMode = 0777
- log.go
package loggingimport ( "log" "os" "runtime" "path/filepath" "fmt")type Level intvar ( F *os.File DefaultPrefix = "" DefaultCallerDepth = 2 logger *log.Logger logPrefix = "" levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"})const ( DEBUG Level = iota INFO WARNING ERROR FATAL)func init() { filePath := getLogFileFullPath() F = openLogFile(filePath) logger = log.New(F, DefaultPrefix, log.LstdFlags)}func Debug(v ...interface{}) { setPrefix(DEBUG) logger.Println(v)}func Info(v ...interface{}) { setPrefix(INFO) logger.Println(v)}func Warn(v ...interface{}) { setPrefix(WARNING) logger.Println(v)}func Error(v ...interface{}) { setPrefix(ERROR) logger.Println(v)}func Fatal(v ...interface{}) { setPrefix(FATAL) logger.Fatalln(v)}func setPrefix(level Level) { _, file, line, ok := runtime.Caller(DefaultCallerDepth) if ok { logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line) } else { logPrefix = fmt.Sprintf("[%s]", levelFlags[level]) } logger.SetPrefix(logPrefix)}
- log.New:建立一個新的日誌記錄器。
out定義要寫入日誌資料的IO控制代碼。prefix定義每個產生的日誌行的開頭。flag定義了日誌記錄屬性
func New(out io.Writer, prefix string, flag int) *Logger { return &Logger{out: out, prefix: prefix, flag: flag}}
- log.LstdFlags:日誌記錄的格式屬性之一,其餘的選項如下
const ( Ldate = 1 << iota // the date in the local time zone: 2009/01/23 Ltime // the time in the local time zone: 01:23:23 Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. Llongfile // full file name and line number: /a/b/c/d.go:23 Lshortfile // final file name element and line number: d.go:23. overrides Llongfile LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone LstdFlags = Ldate | Ltime // initial values for the standard logger)
目前的目錄結構:
gin-blog/├── conf│ └── app.ini├── main.go├── middleware│ └── jwt│ └── jwt.go├── models│ ├── article.go│ ├── auth.go│ ├── models.go│ └── tag.go├── pkg│ ├── e│ │ ├── code.go│ │ └── msg.go│ ├── logging│ │ ├── file.go│ │ └── log.go│ ├── setting│ │ └── setting.go│ └── util│ ├── jwt.go│ └── pagination.go├── routers│ ├── api│ │ ├── auth.go│ │ └── v1│ │ ├── article.go│ │ └── tag.go│ └── router.go├── runtime
我們自訂的logging包,已經基本完成了,接下來讓它接入到我們的項目之中吧!
我們開啟先前包含log包的代碼,
- 開啟
routers目錄下的article.go、tag.go、auth.go
- 將
log包的引用刪除,修改引用我們自己的日誌包為gin-blog/pkg/logging
- 將原本的
log.Println(...)改為log.Info(...)
例如auth.go檔案的修改內容:
package apiimport ( "net/http" "github.com/gin-gonic/gin" "github.com/astaxie/beego/validation" "gin-blog/pkg/e" "gin-blog/pkg/util" "gin-blog/models" "gin-blog/pkg/logging")...func GetAuth(c *gin.Context) { ... code := e.INVALID_PARAMS if ok { ... } else { for _, err := range valid.Errors { logging.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code" : code, "msg" : e.GetMsg(code), "data" : data, })}
驗證功能
修改檔案後,重啟服務,我們來試試吧!
擷取到API的Token後,我們故意傳錯誤URL參數給介面,如:http://127.0.0.1:8000/api/v1/articles?tag_id=0&state=9999999&token=eyJhbG..
然後我們到$GOPATH/gin-blog/runtime/logs查看日誌:
$ tail -f log20180216.log [INFO][article.go:79]2018/02/16 18:33:12 [state 狀態只允許0或1][INFO][article.go:79]2018/02/16 18:33:42 [state 狀態只允許0或1][INFO][article.go:79]2018/02/16 18:33:42 [tag_id 標籤ID必須大於0][INFO][article.go:79]2018/02/16 18:38:39 [state 狀態只允許0或1][INFO][article.go:79]2018/02/16 18:38:39 [tag_id 標籤ID必須大於0]
日誌結構一切正常,我們的記錄模式都為Info,因此首碼是對的,並且我們是入參有問題,也把錯誤記錄下來了,這樣排錯就很方便了!
至此,本節就完成了,這隻是一個簡單的擴充,實際上我們線上項目要使用的檔案日誌,是更複雜一些,開動你的大腦 舉一反三吧!
參考
本系列範例程式碼