Golang panic handing [capture, defer, recover, log]

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:-

Advertisement
  • 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.

Advertisement

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.

Advertisement
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.

Advertisement

 

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

logfile
golang panic

 

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X