How is the log package used in the Golang language standard library?

A simple log package is provided in the standard library of the Golang language. It not only provides many functions, but also defines a type Logger that contains many methods. But it also has shortcomings, for example, it does not support distinguishing log levels, and does not support log file cutting.

01. Introduction

A simple log package is provided in the standard library of the Golang language. It not only provides many functions, but also defines a type Logger that contains many methods. But it also has shortcomings, for example, it does not support distinguishing log levels, and does not support log file cutting.

How is the log package used in the Golang language standard library?  How is the log package used in the Golang language standard library?

02, function

Golang's log package mainly provides the following functions with output functions:

func Fatal(v ...interface{})  
func Fatalf(format string, v ...interface{})  
func Fatalln(v ...interface{})  
func Panic(v ...interface{})  
func Panicf(format string, v ...interface{})  
func Panicln(v ...interface{})  
func Print(v ...interface{})  
func Printf(format string, v ...interface{})  
func Println(v ...interface{}) 

The usage of these functions is exactly the same as the fmt package. By looking at the source code, you can find that Fatal[ln|f] and Panic[ln|f] are actually called Print[ln|f], and Print[ln|f] is actually Above is the called Output() function.

Fatal[ln|f] calls Print[ln|f] and then calls os.Exit(1) to exit the program.

Among them, Panic[ln|f] calls the panic() function after calling Panic[ln|f], throwing a panic.

Therefore, it is necessary for us to read the source code of the Output() function.

The source code of the function Output():

func (l * Logger) Output (calldepth int, s string) error { 
 now := time.Now() // get this early. 
 var file string 
 var line int 
 l.mu.Lock() 
 defer l.mu.Unlock() 
 if l.flag&(Lshortfile|Llongfile) != 0 { 
  // Release lock while getting caller info - it's expensive. 
  l.mu.Unlock() 
  var ok bool 
  _, file, line, ok = runtime.Caller(calldepth) 
  if !ok { 
   file = "???" 
   line = 0 
  } 
  l.mu.Lock() 
 } 
 l.buf = l.buf[:0] 
 l.formatHeader(&l.buf, now, file, line) 
 l.buf = append(l.buf, s...) 
 if len (s) == 0 || s [len (s) -1]! = '\ n' {
  l.buf = append(l.buf, '\n') 
 } 
 _, err := l.out.Write(l.buf) 
 return err 
} 

By reading the source code of the Output() function, you can find that the mutex is used to ensure the safety of multiple goroutines to write logs, and before the runtime.Caller() function is called, the mutex is released first, and the mutual exclusion lock is added after the information is obtained. Exclusion lock to ensure safety.

Use the formatHeader() function to format the log information, and then save it in buf, and then append the log information to the end of buf, and then check to see if the log is empty or the end is not\n, if it is, then add \n Append to the end of buf, and finally output the log information.

The source code of the function Output() is also relatively simple, the most notable of which is the runtime.Caller() function, the source code is as follows:

func Caller(skip int) (pc uintptr, file string, line int, ok bool) { 
 rpc := make([]uintptr, 1) 
 n := callers(skip+1, rpc[:]) 
 if n < 1 { 
  return 
 } 
 frame, _ := CallersFrames(rpc).Next() 
 return frame.PC, frame.File, frame.Line, frame.PC != 0 
} 

By reading the source code of the runtime.Caller() function, you can find that it receives an int type parameter skip, which indicates the number of stack frames to skip. The output function in the log package uses the default value of 2. The reason is what?

For example, if log.Print is called in the main function, the method call stack is main->log.Print->*Logger.Output->runtime.Caller, so the value of the parameter skip at this time is 2, which means the main function is called The source file and code line number of log.Print;

The parameter value is 1, which means the source file and code line number of calling *Logger.Output in the log.Print function; the parameter value is 0, which means the source file and code line number of calling runtime.Caller in the *Logger.Output function.

So far, we found that the log package's output function functions are all outputting information to the console, so how to output the information to a file?

The function SetOutPut is used to set the output destination. The source code is as follows:

func SetOutput(w io.Writer) { 
 std.mu.Lock() 
 defer std.mu.Unlock() 
 std.out = w 
} 

We can open a file for I/O through the function os.OpenFile, and the return value is used as the parameter of the function SetOutput.

In addition, readers should have discovered a problem. The output information starts with date and time. How can we record richer information? For example, source files and line numbers.

This uses the function SetFlags, which can set the output format. The source code is as follows:

func SetFlags(flag int) { 
 std.SetFlags(flag) 
} 

The value of the parameter flag can be any of the following constants:

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 
 Lmsgprefix                    // move the "prefix" from the beginning of the line to before the message 
 LstdFlags     = Ldate | Ltime // initial values for the standard logger 
) 

Among them, Ldate, Ltime and Lmicroseconds represent date, time and microsecond respectively. It should be noted that if you set Lmicroseconds, then setting Ltime will not take effect.

Among them, Llongfile and Lshortfile are code absolute path, source file name, line number, and code relative path, source file name, line number. It should be noted that if Lshortfile is set, even if Llongfile is set, it will not take effect.

Where LUTC means to set the time zone to UTC time zone.

Among them, LstdFlags represents the initial value of the standard recorder, including date and time.

Up to now, there is still something missing, that is, the prefix of the log information. For example, we need to distinguish the log information as DEBUG, INFO, and ERROR. Yes, we also have a function SetPrefix to achieve this function, the source code is as follows:

func SetPrefix(prefix string) { 
 std.SetPrefix(prefix) 
} 

The function SetPrefix receives a string type parameter to set the prefix of the log information.

03、Logger

The log package defines a type Logger that contains many methods. We look at the functions of the output function and find that they all call std.Output. What is std? Let’s check the source code of the log package.

type Logger struct { 
 mu     sync.Mutex // ensures atomic writes; protects the following fields 
 prefix string     // prefix on each line to identify the logger (but see Lmsgprefix) 
 flag   int        // properties 
 out    io.Writer  // destination for output 
 buf    []byte     // for accumulating text to write 
} 
 
func New(out io.Writer, prefix string, flag int) *Logger { 
 return &Logger{out: out, prefix: prefix, flag: flag} 
} 
 
var std = New(os.Stderr, "", LstdFlags) 

By reading the source code, we found that std is actually an instance of Logger type, and Output is a method of Logger.

std is created by the New function. The parameters are os.Stderr, an empty string, and LstdFlags, which represent standard error output, empty string prefix, and date and time.

For Logger type fields, the comments have already been explained, so I won't repeat them here.

Custom Logger:

func main () { 
 logFile, err := os.OpenFile("error1.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) 
 if err != nil { 
  fmt.Println(err) 
  return 
 } 
 defer logFile.Close() 
 logs := DefinesLogger(logFile, "", log.LstdFlags|log.Lshortfile) 
 logs.Debug("message") 
 logs.Debugf("%s", "content") 
} 
 
// Custom logger 
type Logger struct { 
 definesLogger *log.Logger 
} 
 
type Level int8 
 
const( 
 LevelDebug Level = iota 
 LevelInfo 
 LevelError 
) 
 
func (l Level) String() string { 
 switch l { 
 case LevelDebug: 
  return " [debug] " 
 case LevelInfo: 
  return "  " 
 case LevelError: 
  return " [error] " 
 } 
 return "" 
} 
 
func DefinesLogger(w io.Writer, prefix string, flag int) *Logger { 
 l := log.New(w, prefix, flag) 
 return &Logger{definesLogger: l} 
} 
 
func (l *Logger) Debug(v ...interface{}) { 
 l.definesLogger.Print(LevelDebug, fmt.Sprint(v...)) 
} 
 
func (l *Logger) Debugf(format string, v ...interface{}) { 
 l.definesLogger.Print(LevelDebug, fmt.Sprintf(format, v...)) 
} 
 
func (l *Logger) Info(v ...interface{}) { 
 l.definesLogger.Print(LevelInfo, fmt.Sprint(v...)) 
} 
 
func (l *Logger) Infof(format string, v ...interface{}) { 
 l.definesLogger.Print(LevelInfo, fmt.Sprintf(format, v...)) 
} 
 
func (l *Logger) Error(v ...interface{}) { 
 l.definesLogger.Print(LevelError, fmt.Sprint(v...)) 
} 
 
func (l *Logger) Errorf(format string, v ...interface{}) { 
 l.definesLogger.Print(LevelError, fmt.Sprintf(format, v...)) 
} 

04. Summary

This article mainly introduces the log package in the standard library of the Golang language, including the functions of the log package, the usage of the custom type logger, and some detailed precautions. It was also mentioned at the beginning that the log package does not support log file cutting, we need to code it ourselves, or use a third-party library, such as lumberjack. In a production environment, the log package is generally less used to record logs, and third-party libraries, such as zap and logrus, are usually used to record logs.

Guess you like

Origin blog.csdn.net/yaxuan88521/article/details/113804249