1. Methods in log
1.1 Log
Print/Printf/Println: call Output to print to the standard logger.Panic/Panicf/Panicln: equal toPrintfollowed by a call topanic().Fatal/Fatalf/Fatalln: equal toPrintfollowed by a call toos.Exist(1).
example:
1type User struct {2 Name string3 Age int4}5func main() {6 u := User{7 Name: "Alice",8 Age: 18,9 }10 log.Printf("%s login, age:%d", u.Name, u.Age)11 // **output:**12 // 2024/11/23 19:04:00 Alice login, age:1813
14 log.Panicf("Oh, system error when %s login", u.Name)15 // **output:**10 collapsed lines
16 // panic: Oh, system error when Alice login17 //18 // goroutine 1 [running]:19 // log.Panicf({0x104d19fa4?, 0x5?}, {0x1400010cf28?, 0x0?, 0x1400010cf38?})20
21 log.Fatalf("Danger! hacker %s login", u.Name)22 // **output:**23 // 2024/11/23 19:06:45 Danger! hacker Alice login24 // exit status 125}1.2 Custom
0) logger struct
1type Logger struct {2 outMu sync.Mutex3 out io.Writer // destination for output4
5 prefix atomic.Pointer[string] // prefix on each line to identify the logger6 flag atomic.Int32 // flags which control various aspects of the logger's behavior7 isDiscard atomic.Bool // whether the logger should discard log messages8}1) flags
The log library provides these predefined flags to prefix to each log entry generated by the Logger.
1const (2 Ldate = 1 << iota // the date in the local time zone: 2009/01/233 Ltime // the time in the local time zone: 01:23:234 Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.5 Llongfile // full file name and line number: /a/b/c/d.go:236 Lshortfile // final file name element and line number: d.go:23. overrides Llongfile7 LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone8 Lmsgprefix // move the "prefix" from the beginning of the line to before the message9 LstdFlags = Ldate | Ltime // initial values for the standard logger10)example:
1log.SetFlags(log.Llongfile | log.LstdFlags)2log.Printf("%s login, age:%d", u.Name, u.Age)3
4// **output:**5// 2024/11/23 19:16:09 main.go:19: Alice login, age:186
7log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)8log.Printf("%s login, age:%d", u.Name, u.Age)9// **output:**10// 2024/11/23 19:17:53.498562 main.go:19: Alice login, age:182) prefix of log message
The log library provides a SetPrefix method to allow users to set the output prefix for the standard logger:
1log.SetPrefix("[app]: ")2log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)3log.Printf("%s login, age:%d", u.Name, u.Age)4
5// **output:**6// [app]: 2024/11/23 19:19:47.075523 main.go:21: Alice login, age:183) log file
The log library also provdies a SetOutput method to set the output destination for the standard logger:
1logFile, err := os.OpenFile("./output.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)2if err != nil {3 fmt.Println("open log file failed, err:", err)4 return5}6
7log.SetPrefix("[app]: ")8log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)9// the log message would be written to ./output.log, instead of the default CLI10log.SetOutput(logFile)11
12log.Printf("%s login, age:%d", u.Name, u.Age)13log.Printf("Hello, %s", u.Name)4) create a custom logger
The New method creates a new Logger.
Syntax: func New(out io.Writer, prefix string, flag int) *Logger
1// the following logger behaves the same as the one above, except the prefix is "[new]: "2newLogger := log.New(logFile, "[new]: ", log.Lshortfile|log.Ldate|log.Lmicroseconds)3newLogger.Printf("Hello, %s", u.Name)If you want to write to multiple places, you can use io.MultiWriter:
1writer1 := os.Stdout2writer2, err := os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)3if err != nil {4 log.Fatalf("create file log.txt failed: %v", err)5}6
7logger := log.New(io.MultiWriter(writer1, writer2), "[logger]: ", log.Lshortfile|log.LstdFlags)8logger.Printf("%s login, age:%d", u.Name, u.Age)2. More customs
Sometimes, we want to categorize and prioritize log messages based on their severity and importance by different levels, like Debug, Info, Error, etc. Here how we can achieve that using the log library:
1var logFileName = "./output2.log"2var (3 InfoLogger *log.Logger4 WarnLogger *log.Logger5 DebugLogger *log.Logger6 ErrorLogger *log.Logger7 FatalLogger *log.Logger8)9
10func init() {11 logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)12 if err != nil {13 log.Println("Unable to create Logger file:", err.Error())14 return15 }18 collapsed lines
16 log.SetOutput(logFile)17 logFlag := log.Ldate | log.Ltime | log.Lshortfile18 InfoLogger = log.New(logFile, "Info:", logFlag)19 WarnLogger = log.New(logFile, "Warn:", logFlag)20 DebugLogger = log.New(logFile, "Debug:", logFlag)21 ErrorLogger = log.New(logFile, "Error:", logFlag)22 FatalLogger = log.New(logFile, "Fatal:", logFlag)23}24
25func main() {26 InfoLogger.Println("Starting Service!")27 t := time.Now()28 InfoLogger.Printf("Time taken: %s \n", time.Since(t))29 DebugLogger.Println("Debug: Service is running!")30 WarnLogger.Println("Warning: Service is about to shutdown!")31 ErrorLogger.Println("Error: Service is shutting down!")32 FatalLogger.Println("Fatal: Service shut down!")33}Here comes the problems and limitation in functionalities:
- The setup is complex and hard to maintain.
- Not natively support different log levels.
- Not support structured data (e.g. JSON).
- Not support log filtering by log levels and sources.
- Limited ability to customize log messages.
Thus, we might need to use other logger libraries, like slog and logrus.
To see the full example of the code, checkout this link: https://github.com/jidalii/go-playground/tree/main/log