Gin Practice Serialization 6 Write a Simple File Log

  golang, mysql

Write a simple file log

Original address:The Portal of Fans of Fried Fish

In the previous section, we solved the problem that API’s can be accessed at will, so we have another problem now.

Even our logs are output to the console, which is obviously unreasonable for a project, so this section is simply packagedlogLibrary, make it support simple file log!

Project address:https://github.com/EDDYCJY/go …


Newloggingpackage

We arepkgNew belowloggingDirectory, Newfile.goAndlog.goFile, write content:

writefileFile

1、 file.go:

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: Returns the file information structure description file. If there is an error, it will return*PathError
type PathError struct {
    Op   string
    Path string
    Err  error
}
  • os.IsNotExist: AcceptableErrNotExistsyscallSome errors of, it will return a boolean value, can know the file does not exist or directory does not exist
  • os.IsPermission: AcceptableErrPermissionsyscallSome errors of, it will return a boolean value, can know whether permissions meet
  • os.OpenFile: call file, supports the name of the incoming file, the specified mode call file, file permissions, and the method of the returned file can be used for I/o. If an error occurs; otherwise*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: Returns the root pathname corresponding to the current directory
  • os.MkdirAll: Create the corresponding directory and required subdirectories, and return if successfulnil, otherwise returnerror
  • os.ModePermconstDefinitionModePerm FileMode = 0777

writelogFile

2、 log.go

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: Create a new logger.outThe that defines the log data to write toIOHandle.prefixDefines the beginning of each generated log line.flagLogging properties are defined
func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}
  • log.LstdFlags: one of the format attributes of logging, the remaining options are as follows
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
)

Current directory structure:

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

We customizedloggingThe package has been basically completed. Let’s connect it to our project.

We open the previously containedlogThe code of the package,

  1. OpenroutersUnder the directoryarticle.gotag.goauth.go
  2. willlogThe reference of the package is deleted, and the modified reference of our own log package isgin-blog/pkg/logging
  3. Will the originallog.Println(...)insteadlog.Info(...)

For exampleauth.goChanges to the file:

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,
    })
}

Verification function

After modifying the file, restart the service, let’s try it!

After obtaining the Token of API, we intentionally passed the wrong URL parameter to the interface, such as:http://127.0.0.1:8000/api/v1/articles? tag_id=0&state=9999999&token=eyJhbG ..

Then we arrived$GOPATH/gin-blog/runtime/logsView log:

$ 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]

The log structure is normal and our recording mode isInfoTherefore, the prefix is correct, and we have problems with our participation, and we have also recorded the errors, so it is very convenient to arrange the errors!

At this point, this section has been completed. This is only a simple extension. In fact, the file log for our online project is more complicated. Start your brain to draw inferences from other examples.

References

This series of sample codes

This series of catalogues