• 编写一个简单的文件日志
  • 新建logging
    • 编写file文件
  • 参考
    • 本系列示例代码

    编写一个简单的文件日志

    在上一节中,我们解决了API’s可以任意访问的问题,那么我们现在还有一个问题。

    就是我们的日志,都是输出到控制台上的,这显然对于一个项目来说是不合理的,因此我们这一节简单封装log库,使其支持简单的文件日志!

    项目地址:https://github.com/EDDYCJY/go-gin-example


    新建logging

    我们在pkg下新建logging目录,新建file.golog.go文件,写入内容:

    编写file文件

    1、 file.go:

    1. package logging
    2. import (
    3. "os"
    4. "time"
    5. "fmt"
    6. "log"
    7. )
    8. var (
    9. LogSavePath = "runtime/logs/"
    10. LogSaveName = "log"
    11. LogFileExt = "log"
    12. TimeFormat = "20060102"
    13. )
    14. func getLogFilePath() string {
    15. return fmt.Sprintf("%s", LogSavePath)
    16. }
    17. func getLogFileFullPath() string {
    18. prefixPath := getLogFilePath()
    19. suffixPath := fmt.Sprintf("%s%s.%s", LogSaveName, time.Now().Format(TimeFormat), LogFileExt)
    20. return fmt.Sprintf("%s%s", prefixPath, suffixPath)
    21. }
    22. func openLogFile(filePath string) *os.File {
    23. _, err := os.Stat(filePath)
    24. switch {
    25. case os.IsNotExist(err):
    26. mkDir(getLogFilePath())
    27. case os.IsPermission(err):
    28. log.Fatalf("Permission :%v", err)
    29. }
    30. handle, err := os.OpenFile(filePath, os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644)
    31. if err != nil {
    32. log.Fatalf("Fail to OpenFile :%v", err)
    33. }
    34. return handle
    35. }
    36. func mkDir() {
    37. dir, _ := os.Getwd()
    38. err := os.MkdirAll(dir + "/" + getLogFilePath(), os.ModePerm)
    39. if err != nil {
    40. panic(err)
    41. }
    42. }
    • os.Stat:返回文件信息结构描述文件。如果出现错误,会返回*PathError
      1. type PathError struct {
      2. Op string
      3. Path string
      4. Err error
      5. }
    • 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 // 如果可以,打开时
      )
    1. - `os.Getwd`:返回与当前目录对应的根路径名
    2. - `os.MkdirAll`:创建对应的目录以及所需的子目录,若成功则返回`nil`,否则返回`error`
    3. - `os.ModePerm``const`定义`ModePerm FileMode = 0777`
    4. ### 编写`log`文件
    5. 2 log.go

    package logging

    import (
    “log”
    “os”
    “runtime”
    “path/filepath”
    “fmt”
    )

    type Level int

    var (
    F *os.File

    1. DefaultPrefix = ""
    2. DefaultCallerDepth = 2
    3. logger *log.Logger
    4. logPrefix = ""
    5. levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}

    )

    const (
    DEBUG Level = iota
    INFO
    WARNING
    ERROR
    FATAL
    )

    func init() {
    filePath := getLogFileFullPath()
    F = openLogFile(filePath)

    1. 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])
    }

    1. logger.SetPrefix(logPrefix)

    }

    1. - `log.New`:创建一个新的日志记录器。`out`定义要写入日志数据的`IO`句柄。`prefix`定义每个生成的日志行的开头。`flag`定义了日志记录属性

    func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
    }

    1. - `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
    )

    1. 当前目录结构:

    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

    1. 我们自定义的`logging`包,已经基本完成了,接下来让它接入到我们的项目之中吧!
    2. 我们打开先前包含`log`包的代码,
    3. 1. 打开`routers`目录下的`article.go``tag.go``auth.go`
    4. 2. `log`包的引用删除,修改引用我们自己的日志包为`gin-blog/pkg/logging`
    5. 3. 将原本的`log.Println(...)`改为`log.Info(...)`
    6. 例如`auth.go`文件的修改内容:

    package api

    import (
    “net/http”

    1. "github.com/gin-gonic/gin"
    2. "github.com/astaxie/beego/validation"
    3. "gin-blog/pkg/e"
    4. "gin-blog/pkg/util"
    5. "gin-blog/models"
    6. "gin-blog/pkg/logging"

    )

    func GetAuth(c *gin.Context) {

    code := e.INVALIDPARAMS
    if ok {

    } else {
    for
    , err := range valid.Errors {
    logging.Info(err.Key, err.Message)
    }
    }

    1. c.JSON(http.StatusOK, gin.H{
    2. "code" : code,
    3. "msg" : e.GetMsg(code),
    4. "data" : data,
    5. })

    }

    1. ## 验证功能
    2. 修改文件后,重启服务,我们来试试吧!
    3. 获取到APIToken后,我们故意传错误URL参数给接口,如:`http://127.0.0.1:8000/api/v1/articles?tag_id=0&state=9999999&token=eyJhbG..`
    4. 然后我们到`$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,因此前缀是对的,并且我们是入参有问题,也把错误记录下来了,这样排错就很方便了!

    至此,本节就完成了,这只是一个简单的扩展,实际上我们线上项目要使用的文件日志,是更复杂一些,开动你的大脑 举一反三吧!

    参考

    本系列示例代码

    • go-gin-example