In this article, we shall discuss about different ways of golang panic handling. We will cover the following scenarios in detail:
- How to capture a panic message
- Panic messages are generally printed on console, so we will learn to store panic message in log file
- Using defer with panic
- Handling panics using recover function
What is panic in Golang?
In Golang, panic()
is the immediate end of the execution of a program. It can occur in two scenarios namely:-
- Unrecovered errors:- This occurs when a program can not continue its execution due to a code syntax or missing arguments. e.g executing a web server whose ports are being used by another service it fails to bind.
- Program logic error:- When using methods in Golang, they accept pointers as a parameter but the developer might use a nil argument. This will cause panic since the method expects valid pointers, not nil arguments.
Example of panic
In Golang, we use inbuilt panic
function i.e
func panic(interface{})
It's used to print all arguments to it when the program terminates into the standard output.
package main
import "fmt"
func main() {
fmt.Println("Start execution")
for i := 0; i < 20; i++ {
if i == 4 {
fmt.Printf("\n i is equal to %d \n ", i)
panic("End of the program execution")
}
fmt.Printf("%d \n", i)
}
}
Output:-
Start execution 0 1 2 3 i is equal to 4 panic: End of the program execution goroutine 1 [running]: main.main() /tmp/sandbox4175403200/prog.go:13 +0x129 Program exited.
You can test the code at Golang playground using Panic code
Use of defer() during a panic in Golang
In Golang, Once the panic occurs in a program, the execution of that particular program is stopped. Defer is used with goroutines to return to its caller function thus controlling the flow of execution of a program. It is executed until the point of panic error is printed, followed by the stack trace, and terminates the whole program.
For example
package main
import "fmt"
func printNumber(max int) {
defer fmt.Printf(" \n Defered calling Printing numbers from 1 up to %d \n", max)
for i := 1; i < max; i++ {
if i == max/2 {
fmt.Printf(" \n i is equal to %d \t ", i)
panic(" \n End of the program execution")
}
fmt.Printf("%d \t", i)
}
}
func main() {
defer fmt.Println("\n deffered calling main function ")
printNumber(20)
fmt.Println("End of the program")
}
Output:-
1 2 3 4 5 6 7 8 9 i is equal to 10 Defered calling Printing numbers from 1 up to 20 deffered calling main function panic: End of the program execution goroutine 1 [running]: main.printNumber(0x14) /tmp/sandbox3800879957/prog.go:11 +0x1a5 main.main() /tmp/sandbox3800879957/prog.go:19 +0x75 Program exited.
The code can be collected from Go playground
Recover from panic in Golang
In Golang,We can resolve panic using recover()
function. It allows the program to completely execute and return the desired results. in a nutshell, the recover function is used to handle the panic in a program with Golang. It should be declared before the panic statement, thus preventing the termination of the execution of the program. By doing so it recovers the program from panic.
For example:- maintaining a simple example of finding a prime number from a given range
package main
import (
"fmt"
"math"
)
// Create a recovery function
// Here you can add list of tasks to be done when panic is seen
// such as cleanup, custom messages
func handleRecover() {
if r := recover(); r != nil {
fmt.Println("recovered from ", r)
}
}
// printing prime numbers in golang
func PrimeNumber(min, max int) {
// defer the recovery function which gets executed when panic() is observed
defer handleRecover()
if min < 2 || max < 2 {
panic("minimum number must be greater than 2.")
return
}
for min <= max {
prime := true
for i := 2; i <= int(math.Sqrt(float64(min))); i++ {
if min%i == 0 {
prime = false
break
}
}
if prime {
fmt.Printf("%d\t", min)
}
min++
}
println()
}
func main() {
fmt.Println("started main ")
// call func primenumber
PrimeNumber(100, 200)
PrimeNumber(0, 3)
fmt.Println("returned normally from main")
}
Output:-
started main
101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199
recovered from minimum number must be greater than 2.
returned normally from main
Program exited.
The code can be collected from Play ground
What is a logfile in golang?
Logfile in a system is considered as important data points for security, debugging and surveillance, providing a full history of events overtime about a particular action performed by individual i.e user login and activities against time. We have application logs and system logs. in golang, we can utilize so called syslog
file, to keep golang app logs alongside the system logs or create our own custom log file. Writes to the log file are buffered but are automatically flushed. using golang log standard package enables us to send a standard output into either syslogs or custom log file. The syslog file is always located at /var/log/syslog
of the system files.
You can read more about golang logging at Golang Logrus Complete Tutorial with Examples and How to change logging format of log module in GO
Example of logging into syslogs file
package main
import (
"log"
"log/syslog"
"os"
)
func main() {
firstName := os.Args[1]
lastName := os.Args[2]
fullname := firstName + "-" + lastName
// Log to syslog
logFile, err := syslog.New(syslog.LOG_SYSLOG, fullname)
if err != nil {
log.Fatalln("Unable to set logfile:", err.Error())
}
// + set log flag
log.SetFlags(log.Lshortfile)
// set the log output
log.SetOutput(logFile)
log.Println("Writing to syslogs file using Golang log package")
}
Monitor the logging of logs into syslogs file using tail
command
$ tail -f /var/log/syslog
Output:-
$ go run main.go John Doe
Sep 1 23:31:14 .... John-Doe[84798]: main.go:27: Writing to syslogs file using Golang log package
How to capture panic into logfile in golang
By default the panic prints the error message on STDERR. Since we now know what is panic, logfile and how we can recover from panic, let now jump into capturing panic into a logfile. We shall be storing all logs into a custom logfile.
package main
import (
"log"
"os"
"runtime/debug"
)
func main() {
var fileName = "/tmp/logfile"
defer func() {
if err := recover(); err != nil {
log.Println(string(debug.Stack()))
}
}()
// store all logs into /tmp/ folder for the will be deleted immediately during reboot or shutdown of your pc
// create your file with desired read/write permissions
file, errs := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if errs != nil {
log.Println(errs)
}
// set output of logs to f#
log.SetOutput(file)
var mySlice []int
j := mySlice[0]
log.Println(j)
log.Printf("Just stored panic in a our %s log file", fileName)
// defer to close when you're done with it, not because you think its idiomatic!
file.Close()
}
Output:-
$ go run main.go
$ tail -f /tmp/logfile
2022/09/02 00:00:09 Just stored panic in a our /tmp/logfile log file
2022/09/02 00:07:09 goroutine 1 [running]:
runtime/debug.Stack()
/usr/lib/go-1.18/src/runtime/debug/stack.go:24 +0x65
main.main.func1()
/home/username/gocloudlinux/log_file/main.go:13 +0x2a
panic({0x497900, 0xc00001a240})
/usr/lib/go-1.18/src/runtime/panic.go:838 +0x207
main.main()
/home/username/gocloudlinux/log_file/main.go:27 +0xaf
Explanation: The above code stores the logs into /tmp/logfile
this is so due to preventing long-growing logs written into the file, once we restart the machine the temp files are cleared. However, we can have a cron job to delete that file at a given period.
panic vs os.Exit(1) - which one should be preferred?
This is one of the most asked questions as we sometimes are not sure if we should use os.Exit(1)
to exit our code in case of failures or panic()
.
From When to use os.Exit() and panic()?
When panic
 is called, including implicitly for run-time errors such as indexing a slice out of bounds or failing a type assertion, it immediately stops execution of the current function and begins unwinding the stack of the goroutine, running any deferred functions along the way. If that unwinding reaches the top of the goroutine's stack, the program dies.
os.Exit
 is used when you need to abort the program immediately, with no possibility of recovery or running a deferred clean-up statement, and also return an error code (that other programs can use to report what happened). This is useful in tests, when you already know that after this one test fails, the other will fail as well, so you might as well just exit now. This can also be used when your program has done everything it needed to do, and now just needs to exit, i.e. after printing a help message.
Summary
We have covered how to store the application logs into a system log file and how to create a custom log file. It's highlights how the importance of the log package in the Golang standard library. The log package helps developers to write a logfile rotation goroutine that deletes the file when a particular file size is attained.
References