panicwrap

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2019 License: MIT Imports: 10 Imported by: 486

README

panicwrap

panicwrap is a Go library that re-executes a Go binary and monitors stderr output from the binary for a panic. When it finds a panic, it executes a user-defined handler function. Stdout, stderr, stdin, signals, and exit codes continue to work as normal, making the existence of panicwrap mostly invisible to the end user until a panic actually occurs.

Since a panic is truly a bug in the program meant to crash the runtime, globally catching panics within Go applications is not supposed to be possible. Despite this, it is often useful to have a way to know when panics occur. panicwrap allows you to do something with these panics, such as writing them to a file, so that you can track when panics occur.

panicwrap is not a panic recovery system. Panics indicate serious problems with your application and should crash the runtime. panicwrap is just meant as a way to monitor for panics. If you still think this is the worst idea ever, read the section below on why.

Features

  • SIMPLE!
  • Works with all Go applications on all platforms Go supports
  • Custom behavior when a panic occurs
  • Stdout, stderr, stdin, exit codes, and signals continue to work as expected.

Usage

Using panicwrap is simple. It behaves a lot like fork, if you know how that works. A basic example is shown below.

Because it would be sad to panic while capturing a panic, it is recommended that the handler functions for panicwrap remain relatively simple and well tested. panicwrap itself contains many tests.

package main

import (
	"fmt"
	"github.com/mitchellh/panicwrap"
	"os"
)

func main() {
	exitStatus, err := panicwrap.BasicWrap(panicHandler)
	if err != nil {
		// Something went wrong setting up the panic wrapper. Unlikely,
		// but possible.
		panic(err)
	}

	// If exitStatus >= 0, then we're the parent process and the panicwrap
	// re-executed ourselves and completed. Just exit with the proper status.
	if exitStatus >= 0 {
		os.Exit(exitStatus)
	}

	// Otherwise, exitStatus < 0 means we're the child. Continue executing as
	// normal...

	// Let's say we panic
	panic("oh shucks")
}

func panicHandler(output string) {
	// output contains the full output (including stack traces) of the
	// panic. Put it in a file or something.
	fmt.Printf("The child panicked:\n\n%s\n", output)
	os.Exit(1)
}

How Does it Work?

panicwrap works by re-executing the running program (retaining arguments, environmental variables, etc.) and monitoring the stderr of the program. Since Go always outputs panics in a predictable way with a predictable exit code, panicwrap is able to reliably detect panics and allow the parent process to handle them.

WHY?! Panics should CRASH!

Yes, panics should crash. They are 100% always indicative of bugs and having information on a production server or application as to what caused the panic is critical.

User Facing

In user-facing programs (programs like Packer or Docker), it is up to the user to report such panics. This is unreliable, at best, and it would be better if the program could have a way to automatically report panics. panicwrap provides a way to do this.

Server

For backend applications, it is easier to detect crashes (since the application exits) and having an idea as to why the crash occurs is equally important; particularly on a production server.

At HashiCorp, we use panicwrap to log panics to timestamped files with some additional data (configuration settings at the time, environmental variables, etc.)

The goal of panicwrap is not to hide panics. It is instead to provide a clean mechanism for capturing them and ultimately crashing.

Documentation

Overview

The panicwrap package provides functions for capturing and handling panics in your application. It does this by re-executing the running application and monitoring stderr for any panics. At the same time, stdout/stderr/etc. are set to the same values so that data is shuttled through properly, making the existence of panicwrap mostly transparent.

Panics are only detected when the subprocess exits with a non-zero exit status, since this is the only time panics are real. Otherwise, "panic-like" output is ignored.

Index

Constants

View Source
const (
	DEFAULT_COOKIE_KEY = "cccf35992f8f3cd8d1d28f0109dd953e26664531"
	DEFAULT_COOKIE_VAL = "7c28215aca87789f95b406b8dd91aa5198406750"
)

Variables

This section is empty.

Functions

func BasicWrap

func BasicWrap(f HandlerFunc) (int, error)

BasicWrap calls Wrap with the given handler function, using defaults for everything else. See Wrap and WrapConfig for more information on functionality and return values.

func Wrap

func Wrap(c *WrapConfig) (int, error)

Wrap wraps the current executable in a handler to catch panics. It returns an error if there was an error during the wrapping process. If the error is nil, then the int result indicates the exit status of the child process. If the exit status is -1, then this is the child process, and execution should continue as normal. Otherwise, this is the parent process and the child successfully ran already, and you should exit the process with the returned exit status.

This function should be called very very early in your program's execution. Ideally, this runs as the first line of code of main.

Once this is called, the given WrapConfig shouldn't be modified or used any further.

func Wrapped

func Wrapped(c *WrapConfig) bool

Wrapped checks if we're already wrapped according to the configuration given.

It must be only called once with a non-nil configuration as it unsets the environment variable it uses to check if we are already wrapped. This prevents false positive if your program tries to execute itself recursively.

Wrapped is very cheap and can be used early to short-circuit some pre-wrap logic your application may have.

If the given configuration is nil, then this will return a cached value of Wrapped. This is useful because Wrapped is usually called early to verify a process hasn't been wrapped before wrapping. After this, the value of Wrapped hardly changes and is process-global, so other libraries can check with Wrapped(nil).

Types

type HandlerFunc

type HandlerFunc func(string)

HandlerFunc is the type called when a panic is detected.

type WrapConfig

type WrapConfig struct {
	// Handler is the function called when a panic occurs.
	Handler HandlerFunc

	// The cookie key and value are used within environmental variables
	// to tell the child process that it is already executing so that
	// wrap doesn't re-wrap itself.
	CookieKey   string
	CookieValue string

	// If true, the panic will not be mirrored to the configured writer
	// and will instead ONLY go to the handler. This lets you effectively
	// hide panics from the end user. This is not recommended because if
	// your handler fails, the panic is effectively lost.
	HidePanic bool

	// The amount of time that a process must exit within after detecting
	// a panic header for panicwrap to assume it is a panic. Defaults to
	// 300 milliseconds.
	DetectDuration time.Duration

	// The writer to send the stderr to. If this is nil, then it defaults
	// to os.Stderr.
	Writer io.Writer

	// The writer to send stdout to. If this is nil, then it defaults to
	// os.Stdout.
	Stdout io.Writer

	// Catch and igore these signals in the parent process, let the child
	// handle them gracefully.
	IgnoreSignals []os.Signal

	// Catch these signals in the parent process and manually forward
	// them to the child process. Some signals such as SIGINT are usually
	// sent to the entire process group so setting it isn't necessary. Other
	// signals like SIGTERM are only sent to the parent process and need
	// to be forwarded. This defaults to empty.
	ForwardSignals []os.Signal
}

WrapConfig is the configuration for panicwrap when wrapping an existing binary. To get started, in general, you only need the BasicWrap function that will set this up for you. However, for more customizability, WrapConfig and Wrap can be used.

Jump to

Keyboard shortcuts

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