Gin實踐 連載六 編寫一個簡單的文件日誌

原文地址: 編寫一個簡單的文件日誌

在上一節中,咱們解決了API's能夠任意訪問的問題,那麼咱們如今還有一個問題。html

就是咱們的日誌,都是輸出到控制檯上的,這顯然對於一個項目來講是不合理的,所以咱們這一節簡單封裝log庫,使其支持簡單的文件日誌!git

項目地址:https://github.com/EDDYCJY/go...github


新建logging

咱們在pkg下新建logging目錄,新建file.golog.go文件,寫入內容:golang

編寫file文件

一、 file.go:segmentfault

package logging

import (
    "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()
        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() {
    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:可以接受ErrNotExistsyscall的一些錯誤,它會返回一個布爾值,可以得知文件不存在或目錄不存在
  • os.IsPermission:可以接受ErrPermissionsyscall的一些錯誤,它會返回一個布爾值,可以得知權限是否知足
  • 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.ModePermconst定義ModePerm FileMode = 0777

編寫log文件

二、 log.goapi

package logging

import (
    "log"
    "os"
    "runtime"
    "path/filepath"
    "fmt"
)

type Level int

var (
    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包,已經基本完成了,接下來讓它接入到咱們的項目之中吧!app

咱們打開先前包含log包的代碼,ide

  1. 打開routers目錄下的article.gotag.goauth.go
  2. log包的引用刪除,修改引用咱們本身的日誌包爲gin-blog/pkg/logging
  3. 將本來的log.Println(...)改成log.Info(...)

例如auth.go文件的修改內容:優化

package api

import (
    "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,所以前綴是對的,而且咱們是入參有問題,也把錯誤記錄下來了,這樣排錯就很方便了!

至此,本節就完成了,這只是一個簡單的擴展,實際上咱們線上項目要使用的文件日誌,是更復雜一些,開動你的大腦 觸類旁通吧!

參考

本系列示例代碼

本系列目錄

相關文章
相關標籤/搜索