Jida's Blog

Golang Standard Package: log

23rd November 2024
Golang
Golang Standard Package
logger
Last updated:25th January 2025
4 Minutes
707 Words

1. Methods in log

1.1 Log

  1. Print/ Printf/ Println: call Output to print to the standard logger.
  2. Panic/ Panicf/ Panicln: equal to Print followed by a call to panic().
  3. Fatal/ Fatalf/ Fatalln: equal to Print followed by a call to os.Exist(1).

example:

1
type User struct {
2
Name string
3
Age int
4
}
5
func 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:18
13
14
log.Panicf("Oh, system error when %s login", u.Name)
15
// **output:**
10 collapsed lines
16
// panic: Oh, system error when Alice login
17
//
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 login
24
// exit status 1
25
}

1.2 Custom

0) logger struct

1
type Logger struct {
2
outMu sync.Mutex
3
out io.Writer // destination for output
4
5
prefix atomic.Pointer[string] // prefix on each line to identify the logger
6
flag atomic.Int32 // flags which control various aspects of the logger's behavior
7
isDiscard atomic.Bool // whether the logger should discard log messages
8
}

1) flags

The log library provides these predefined flags to prefix to each log entry generated by the Logger.

1
const (
2
Ldate = 1 << iota // the date in the local time zone: 2009/01/23
3
Ltime // the time in the local time zone: 01:23:23
4
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
5
Llongfile // full file name and line number: /a/b/c/d.go:23
6
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
7
LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone
8
Lmsgprefix // move the "prefix" from the beginning of the line to before the message
9
LstdFlags = Ldate | Ltime // initial values for the standard logger
10
)

example:

1
log.SetFlags(log.Llongfile | log.LstdFlags)
2
log.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:18
6
7
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)
8
log.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:18

2) prefix of log message

The log library provides a SetPrefix method to allow users to set the output prefix for the standard logger:

1
log.SetPrefix("[app]: ")
2
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)
3
log.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:18

3) log file

The log library also provdies a SetOutput method to set the output destination for the standard logger:

1
logFile, err := os.OpenFile("./output.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
2
if err != nil {
3
fmt.Println("open log file failed, err:", err)
4
return
5
}
6
7
log.SetPrefix("[app]: ")
8
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)
9
// the log message would be written to ./output.log, instead of the default CLI
10
log.SetOutput(logFile)
11
12
log.Printf("%s login, age:%d", u.Name, u.Age)
13
log.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]: "
2
newLogger := log.New(logFile, "[new]: ", log.Lshortfile|log.Ldate|log.Lmicroseconds)
3
newLogger.Printf("Hello, %s", u.Name)

If you want to write to multiple places, you can use io.MultiWriter:

1
writer1 := os.Stdout
2
writer2, err := os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
3
if err != nil {
4
log.Fatalf("create file log.txt failed: %v", err)
5
}
6
7
logger := log.New(io.MultiWriter(writer1, writer2), "[logger]: ", log.Lshortfile|log.LstdFlags)
8
logger.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:

1
var logFileName = "./output2.log"
2
var (
3
InfoLogger *log.Logger
4
WarnLogger *log.Logger
5
DebugLogger *log.Logger
6
ErrorLogger *log.Logger
7
FatalLogger *log.Logger
8
)
9
10
func 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
return
15
}
18 collapsed lines
16
log.SetOutput(logFile)
17
logFlag := log.Ldate | log.Ltime | log.Lshortfile
18
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
25
func 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:

  1. The setup is complex and hard to maintain.
  2. Not natively support different log levels.
  3. Not support structured data (e.g. JSON).
  4. Not support log filtering by log levels and sources.
  5. 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

References

Article title:Golang Standard Package: log
Article author:Jida-Li
Release time:23rd November 2024