Logrus libraries use

  1. Introduction to the

    Logrus is a structured logger for Go (Golang), and the API is fully compatible with standard library loggers. The log library in Golang is too simple to meet our current needs most of the time, mainly because it provides too simple interface functions. Logrus is designed to solve this problem. It is currently compatible with standard log libraries and supports json and Text text output.

  2. The installation

    go get github.com/sirupsen/logrus

  3. Quick learning

    1. // Create an instance
      log := *logrus.New()	
      // Set it to json format
      log.SetFormatter(&logrus.JSONFormatter{
         TimestampFormat: "The 2006-01-02 15:04:05",})// Set the log level
      log.SetLevel(logrus.InfoLevel)
      // Write to the log
      log.WithFields(logrus.Fields{
         "name": "A ball.",
      }).Info("Here's Logrus quick use.")
      Copy the code
    2. Log levels supported by Logrus

      1. Panic: Logs are recorded, and Panic occurs
      2. Fatal: Fatal errors that cause the program to crash, log, and exit
      3. Error: indicates Error logs
      4. Warn: warning logs
      5. Info: core flow logs
      6. Debug: Debug logs (Debug logs)
      7. Trace: Ultra-fine grained, which is not used under normal circumstances

      Lavel: Panic < Fatal < Error < Warn < Info < Debug < Trace

  4. The log information is output to a file

    package tool import ( "bytes" "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/sirupsen/logrus" Type bodyLogWriter struct {gin.ResponseWriter body * bytes.buffer} // Define an instance var LoggerInfo logrus.Logger // Set the user uid, Var uid string func init() {setUid() Logger()} func Logger(){nowTime := time.now () LogFilePath := GetString(" logrus.log_file_path ") // If len(logFilePath) <= 0 {if dir, err := os.getwd (); if len(logFilePath) <= 0 { Err == nil {logFilePath = dir + "/logs/"}} // Create folder if err := os.mkdirall (logFilePath, os.modeperm); err ! // fileName logFileName := nowtime. Format("2006-01-02") + ".log" // log file address spliced fileName := Path. Join(logFilePath, logFileName) //fmt.Println(" fileName: "+fileName) if _, err := os.stat (fileName); err ! Println(" Check file: "+ err.error ()) _, err := os.create (fileName) if err! Println(err.error ())}} // OpenFile SRC, err := os.openfile (fileName, os.O_CREATE|os.O_RDWR|os.O_APPEND,os.ModeAppend|os.ModePerm) if err ! = nil { fmt.Println("write file log error", Err)} // instantiate loggerInfo = * logrus.new () // Set the output loggerInfo.out = SRC // // Logger. SetLevel(logrus.infolevel) //// Set log format json format // Logger. SetFormatter(& Logrus.jsonformatter {// TimestampFormat: "The 2006-01-02 15:04:05", //}) loggerInfo.AddHook(&LogrusHook{}) loggerInfo.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: })} // Func LogErrorInfoToFile(fields logrus.fields) {loggerInfo.setlevel (logrus.infolevel) Loggerinfo.withfields (fields).info ()} // Write the binary to the buffer func (w bodyLogWriter) Write(b []byte) (int, Error) {w.body.write (b) return w.responsewriter.write (b)} // Write the string to the buffer func (w bodyLogWriter) WriteString(s string) (int, error) {w.b ody. WriteString (s) return w.R esponseWriter. WriteString (s)} / / get back to their body middleware func GinBodyLogMiddleware () gin.HandlerFunc { return func(c *gin.Context) { blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: ResponseStr := blw.body.string () // startTime := time.now () // endTime := Time.now () // Execution time latencyTime := endtime.sub (startTime) // Request Method reqMethod := c.equest.Method // Request route reqUri := C. equest.requesturi // Status code statusCode := C. whiter.status () // Request IP clientIP := c. clientIP () // Request parameter reqParams := C. equest.body // Request ua reqUa := C. equest.userAgent () var resultBody logrus.fields resultBody = make(map[string]interface{}) resultBody["response"] = responseStr resultBody["requestUri"] = reqUri resultBody["clientIp"] = clientIP resultBody["body"] = reqParams resultBody["userAgent"] = reqUa resultBody["requestMethod"] = reqMethod resultBody["startTime"] = startTime resultBody["endTime"] = endTime resultBody["latencyTime"] = latencyTime resultBody["statusCode"] = statusCode LogErrorInfoToFile(resultBody) setUid() } } func setUid() { uid = uuid.New().String() } func GetNewUid() string { return uid }Copy the code

    Since I am using the GIN framework here, I use a middleware to log, which writes each returned structure to the log. However, there is still a problem with how to distinguish a log that has been requested to end. In this case, we need to use hook, see the next section.

  5. hook

    “Hook is the function of extending logs. It intercepts and modifies entries every time logs are written.

    Based on this feature, we can think deeply about whether this is something we can manipulate to add the data we want.

    To implement this idea we must first implement the hook interface, which is defined in Logrus.

    This is the interface defined in Logruspackage logrus
    
    // A hook to be fired when logging on the logging levels returned from
    // `Levels()` on your implementation of the interface. Note that this is not
    // fired in a goroutine or a channel with workers, you should handle such
    // functionality yourself if your call is non-blocking and you don't wish for
    // the logging calls for levels returned from `Levels()` to block.
    type Hook interface {
       Levels() []Level
       Fire(*Entry) error
    }
    
    // Internal type for storing the hooks on a logger instance.
    type LevelHooks map[Level][]Hook
    
    // Add a hook to an instance of logger. This is called with
    // `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
    func (hooks LevelHooks) Add(hook Hook) {
       for _, level := range hook.Levels() {
          hooks[level] = append(hooks[level], hook)
       }
    }
    
    // Fire all the hooks for the passed level. Used by `entry.log` to fire
    // appropriate hooks for a log entry.
    func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
       for _, hook := range hooks[level] {
          iferr := hook.Fire(entry); err ! =nil {
             return err
          }
       }
    
       return nil
    }
    Copy the code

    So we’re going to implement this interface, and we’re going to go crazy with our own methods

    package tool
    
    import (
       "github.com/sirupsen/logrus"
    )
    
    type LogrusHook struct{}// Set all log levels to follow this hook
    func (hook *LogrusHook) Levels(a) []logrus.Level {
       return logrus.AllLevels
    }
    // Modify the data, or other operations
    func (hook *LogrusHook) Fire(entry *logrus.Entry) error {
       entry.Data["request_id"] = GetNewUid()
       return nil
    }
    Copy the code

    Here I have modified the data in data and added a request ID to it. This ID is unique under the current request, so that we can ensure that the request is recorded uniformly and it is convenient for us to find the log.

  6. Extend error logging to get the file and line number of the error message

    func getErrorFileAndLine(err error) {
       // Get the file and line count of the upper runtime
       for skip := 1; true; skip++ {
          // Get the file and line count of the upper runtime
          _, file, line, ok := runtime.Caller(skip)
          if ok {
             var resultBody logrus.Fields
             resultBody = make(map[string]interface{})
             resultBody["file_path"] = file
             resultBody["error_line"] = line
             resultBody["error_message"] = err.Error()
             LogErrorInfoToFile(resultBody)
          }else {
             break}}}Copy the code

    As for the runtime.Caller method, it uses an infinite loop to obtain the records from the request to the error.

  7. In general, this is still not deep enough. Logrus and hook, log cutting or runtime.Caller are all points that can be optimized.