panichandler

package module
v1.6.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 11, 2021 License: MIT Imports: 5 Imported by: 1

README

Introduction

This package will allow you to deal with panics in the following ways:

  • Call a function when a panic is called.
  • Send the information from the panic over a channel.
  • Cancel contexts with a provided context.CancelFunc.
  • Implement the panichandler.Task interface by defining its DoPanicTask method.
  • All these things in a struct, which can execute everything stated above, only some of these things, or only one of them.

Explanation

In writing one of my programs, I wanted a way to handle panics in the same fassion across the application. Using a defer statement to do this seemed fine, but calling an included function within that specific program seemed somewhat limiting to me, so I created this package, just in case anyone else might want to do something similar. I expanded it since then to work with channels, context cancel functions, implemented a Task interface, and a struct which can deal with everything.

Full documentation is available, but here are a couple of different things.

The panichandler.Info struct has variables that will be populated with the precise value of the call to recover, along with its string and byte formatted copies. The stack trace is also available as bytes and a string. A complete format of the string value to recover, then the stack trace on another line, is available in both string and bytes using appropriate functions.

With all this information, you should be able to create a defer statement anywhere you like, which can then catch panics that you will then be able to do anything you like with, such as sending yourself an email with their values, etc. Typically, you should create the defer statement in one of two places, either when you begin execution of a new goroutine, or at a location where you may not want the entire program to crash.

The panichandler.HandlerFunc is only called when a panic is caught. If a panic is not caught, this function isn't called. If you initialize the panichandler.Handle function with a nil value, the program will print the string formatted value and stack trace, then exit the program with a default exit code of 111. You can change this exit code with the panichandler.ExitCode variable.

The same exit code will be used if the function you define to catch a panic, causes a panic itself. This will be the same for the Task interface, also. The complete stack trace will be printed for both panics, along with the string value of the reason behind the panic. There may be redundent information in the stack traces, as in my testing, the stack trace from the second panic included everything from the first. Both are printed for now, however, to provide a bit more clarity on where individual panics occurred. This may change in the future, and as it won't be a breaking API change, only an information update, a patch version update will satisfy this, should it be changed.

When sending the data of a panic over a channel with the HandleWithChan function, the data will only be sent if a panic is caught. The panic will not be sent over a closed channel, which will cause its own panic that will be returned to you, along with the original panic. The program will then terminate immediately. Attempting to send data over a nil channel will cause the program to exit with the stack trace of the panic printed, and a warning that a nil channel is unsupported.

When using the panichandler.HandleWithContextCancel function, you cannot declare the context.CancelFunc value as nil. The program will warn you of an uncatchable panic, then will terminate. You can, however, make the panichandler.HandleFunc a nil value, in which case, only the context.CancelFunc will be called. I wouldn't recommend this, though, as you may want to do something more with your panics.

The panichandler.Task interface defines one method, DoPanicTask(*panichandler.Info)

The panichandler.Capture struct can catch panics and pass them to a function, task interface, channel, and context cancel function, in that specific order and one at a time. A nil value on all such values will cause the program to exit with the stacktrace of the panic that was essentially uncaught. Catching panics with an uninitialized struct is not going to do anything for you, so the library won't let you do this. There is only one partial exception to this rule. If you catch a panic by using an already canceled contexts CancelFunc, the panic will be caught silently. This is bad practice, don't do this, and please, do check your programs.

This package makes no attempt to be concurrent safe within itself, due to the fact that multiple panics could be caught concurrently at one time, each one performing a specific task once they are caught. It should be up to the user to properly test their program for race conditions and make it concurrent safe.

All tests should pass race testing.

Examples

Function

package main

import (
	"fmt"
	"github.com/tech10/panichandler"
)

func main() {
	fmt.Println("This will test catching panics.")
	defer panichandler.Handle(func(i *panichandler.Info) {
		fmt.Println("This function has been called because a panic has been caught. Here is the reason for this panic.")
		fmt.Println(i.PanicString)
		fmt.Println("Here is the stack trace.")
		fmt.Println(i.StackString)
		fmt.Println("Goodbye!")
	})
	panic("This is a test panic.")
}

Channel

package main

import (
	"fmt"
	"github.com/tech10/panichandler"
)

func main() {
	fmt.Println("This will test catching panics over a channel.")
	c := make(chan *panichandler.Info)
	go func() {
		defer panichandler.HandleWithChan(c)
		panic("Testing channels.")
	}()
	i := <-c
	fmt.Println("This program has continued execution because a panic has been caught. Here is the reason for this panic.")
	fmt.Println(i.PanicString)
	fmt.Println("Here is the stack trace.")
	fmt.Println(i.StackString)
	fmt.Println("Goodbye!")
}

Context cancelation

package main

import (
	"context"
	"fmt"
	"github.com/tech10/panichandler"
	"sync"
)

func main() {
	fmt.Println("Catching a panic and canceling contexts.")
	var wg sync.WaitGroup
	mctx, cancel := context.WithCancel(context.Background())
	wg.Add(1)
	go func() {
		defer wg.Done()
		<-mctx.Done()
		fmt.Println("Parent context canceled.")
	}()
	var cctx context.Context
	cctx, _ = context.WithCancel(mctx)
	wg.Add(1)
	go func() {
		defer wg.Done()
		<-cctx.Done()
		fmt.Println("Child context canceled.")
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer panichandler.HandleWithContextCancel(cancel, func(i *Info) {
			fmt.Println("Panic caught.\n", i.String())
		})
		panic("Testing context cancelations.")
	}()
	wg.Wait()
	fmt.Println("All contexts have been canceled. Goodbye.")
}

Task interface

package main

import (
	"fmt"
	"github.com/tech10/panichandler"
	"sync"
)

type CP struct {
	sync.WaitGroup
}

func (cp *CP) DoPanicTask(i *panichandler.Info) {
	defer cp.Done()
	fmt.Println("Panic captured.")
	fmt.Println(i.String())
}

func main() {
	fmt.Println("Capture panics with the Task interface.")
	c := &CP{}
	c.Add(2)
	go func() {
		defer c.Done()
		defer panichandler.HandleTask(c)
		panic("Testing the Task interface with this panic.")
	}()
	c.Wait()
	fmt.Println("Panic caught, task complete. Goodbye.")
}

Capture struct

This is the only example that will have an uncaught panic, to demonstrate that an uninitialized panichandler.Capture struct can't be used.

package main

import (
	"context"
	"fmt"
	"github.com/tech10/panichandler"
	"sync"
)

type captT struct {
	f func()
}

func (c *captT) DoPanicTask(i *panichandler.Info) {
	c.f()
}

func main() {
	fmt.Println("Testing capture struct. Watch as a panic is caught and passed down the stack of a function, interface, channel, and context cancelation.")
	var l sync.Mutex
	var wg sync.WaitGroup
	ctx, ctxc := context.WithCancel(context.Background())
	wg.Add(1)
	go func() {
		defer wg.Done()
		<-ctx.Done()
		l.Lock()
		defer l.Unlock()
		fmt.Println("Context canceled.")
	}()
	c := panichandler.New()
	c.CC = ctxc
	c.C = make(chan *panichandler.Info)
	wg.Add(1)
	go func() {
		defer wg.Done()
		<-c.C
		l.Lock()
		defer l.Unlock()
		fmt.Println("Channel received data.")
	}()
	wg.Add(1)
	c.F = func(i *panichandler.Info) {
		defer wg.Done()
		l.Lock()
		defer l.Unlock()
		fmt.Println("Function called.")
	}
	wg.Add(1)
	c.T = &captT{
		f: func() {
			defer wg.Done()
			l.Lock()
			defer l.Unlock()
			fmt.Println("Task interface executed.")
		},
	}
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer c.Catch()
		panic("Demonstrating capture struct.")
	}()
	wg.Wait()
	fmt.Println("All the work is done.")
	fmt.Println("Let's take the task and channel out of the work, now. Notice that the context will not cancel again, since it has already been canceled.")
	c.T = nil
	c.C = nil
	fmt.Println("Catch another panic.")
	wg.Add(2)
	go func() {
		defer wg.Done()
		defer c.Catch()
		panic("Removing work.")
	}()
	wg.Wait()
	fmt.Println("Let's only cancel a context. Don't do this with an already canceled context like this, it won't work out well for you.")
	c.F = nil
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer c.Catch()
		panic("A silent panic, you really don't want this in production.")
	}()
	wg.Wait()
	fmt.Println("And as you can see, a Capture struct with all values at nil won't capture panics, crash the program instead, as we are expecting to do something in the struct.")
	c.CC = nil
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer c.Catch()
		panic("Empty the struct.")
	}()
	wg.Wait()
	fmt.Println("If we got this far, something is wrong. Have a good day!")
}

Contributions

Open an issue or a pull request with any contributions. Remember to properly format your code with gofmt. If creating any new features, create tests for them as well.

Documentation

Overview

Handle panics in a simple manner.

Index

Constants

This section is empty.

Variables

View Source
var ExitCode = 111

ExitCode used if one isn't provided. This can be set with panichandler.ExitCode = code.

Functions

func Handle

func Handle(c HandlerFunc)

Handle panics. Call this in a defer statement, like this. panichandler.Handle(panichandler.HandlerFunc).

func HandleTask

func HandleTask(t Task)

Handle panics within the Task interface. Call it like this. panichandler.HandleTask(panichandler.Task).

func HandleWithChan

func HandleWithChan(c chan<- *Info)

Send the *Info struct to a channel rather than a function. Call it like this. panichandler.HandleWithChan(chan *panichandler.Info).

func HandleWithContextCancel

func HandleWithContextCancel(cancel context.CancelFunc, c HandlerFunc)

Handle panics in a function and cancel the provided context.CancelFunc. Call it like this. panichandler.HandleWithContextCancel(context.CancelFunc, panichandler.HandlerFunc).

Types

type Capture

type Capture struct {
	F        HandlerFunc        // Function to execute, handling panics, this is called first.
	T        Task               // Interface to run the DoPanicTask method on, this is called second.
	C        chan *Info         // Channel to pass panic information to, this is done third.
	CC       context.CancelFunc // Context cancelation function, this is called last.
	ExitCode int                // Status to exit with if a panic occurrs that crashes the program, and isn't caught by anything else.
}

Capture panics in something you can pass around the program in an easy to use struct. The execution order for catching panics is the following: Your own defined function, a task, a channel, and a context.CancelFunc. Any of these values can be omitted, but all of them can't be omitted. If you try and catch panics without filling at least one value, and a panic is recovered from, your program will have the panic passed along to standard error, and will exit immediately.

func New

func New() *Capture

Create a new instance of the Capture struct, uninitialized. The only variable set here is its ExitCode, which is set to the default ExitCode variable.

func (*Capture) Catch

func (c *Capture) Catch()

Catch panics, call this in a defer statement. Only do this if you have initialized the Capture struct with a function, Task interface, channel, or context.CancelFunc.

func (*Capture) CatchAndCancelContext

func (c *Capture) CatchAndCancelContext()

Always cancel a context, if it is available.

func (*Capture) GetContext

func (c *Capture) GetContext() context.Context

Get a context that will be returned to you, and canceled upon a panic. If you have already set a context cancelation function, this will override it. The context returned should be used on anything you want to cancel, and will not be derived from any parent contexts. This is a context that is designed to be canceled upon a panic, though you could call the Capture.CC function yourself. You may need to do this if a panic is not caught.

type HandlerFunc

type HandlerFunc func(*Info)

Handle panics with this function.

type Info

type Info struct {
	PanicBytes     []byte      // Bytes of the returned panic interface.
	PanicInterface interface{} // The direct interface panic was provided when called, either by the Go runtime or by the user.
	PanicString    string      // String of the returned panic interface.
	StackBytes     []byte      // The stack trace as taken from debug.Stack()
	StackString    string      // The stack as taken by debug.Stack() converted to a string.
}

Contains all information about a panic, formatted in various ways.

func (*Info) Bytes

func (i *Info) Bytes() []byte

Returns a byte formatted output of the panic and stack trace.

func (*Info) String

func (i *Info) String() string

Returns a string formatted output of the panic and stack trace.

type Task

type Task interface {
	DoPanicTask(*Info)
}

Interface for defining your own handler, perhaps within a struct.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL