Move to vendor
Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
parent
c8d8e7e357
commit
77e69b9cf3
1268 changed files with 34 additions and 24 deletions
21
vendor/github.com/bugsnag/panicwrap/LICENSE
generated
vendored
Normal file
21
vendor/github.com/bugsnag/panicwrap/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Mitchell Hashimoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
101
vendor/github.com/bugsnag/panicwrap/README.md
generated
vendored
Normal file
101
vendor/github.com/bugsnag/panicwrap/README.md
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
# panicwrap
|
||||
|
||||
panicwrap is a Go library that re-executes a Go binary and monitors stderr
|
||||
output from the binary for a panic. When it find 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
|
||||
invisble 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.
|
||||
|
||||
```go
|
||||
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.
|
||||
However, in some cases, such as user-facing programs (programs like
|
||||
[Packer](http://github.com/mitchellh/packer) or
|
||||
[Docker](http://github.com/dotcloud/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.
|
||||
|
||||
For backend applications, it is easier to detect crashes (since the application
|
||||
exits). However, it is still nice sometimes to more intelligently log
|
||||
panics in some way. For example, at [HashiCorp](http://www.hashicorp.com),
|
||||
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 handling them before bubbling the up to the user
|
||||
and ultimately crashing.
|
11
vendor/github.com/bugsnag/panicwrap/dup2.go
generated
vendored
Normal file
11
vendor/github.com/bugsnag/panicwrap/dup2.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// +build darwin dragonfly freebsd linux,!arm64 netbsd openbsd
|
||||
|
||||
package panicwrap
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func dup2(oldfd, newfd int) error {
|
||||
return syscall.Dup2(oldfd, newfd)
|
||||
}
|
11
vendor/github.com/bugsnag/panicwrap/dup3.go
generated
vendored
Normal file
11
vendor/github.com/bugsnag/panicwrap/dup3.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// +build linux,arm64
|
||||
|
||||
package panicwrap
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func dup2(oldfd, newfd int) error {
|
||||
return syscall.Dup3(oldfd, newfd, 0)
|
||||
}
|
62
vendor/github.com/bugsnag/panicwrap/monitor.go
generated
vendored
Normal file
62
vendor/github.com/bugsnag/panicwrap/monitor.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
// +build !windows
|
||||
|
||||
package panicwrap
|
||||
|
||||
import (
|
||||
"github.com/bugsnag/osext"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func monitor(c *WrapConfig) (int, error) {
|
||||
|
||||
// If we're the child process, absorb panics.
|
||||
if Wrapped(c) {
|
||||
panicCh := make(chan string)
|
||||
|
||||
go trackPanic(os.Stdin, os.Stderr, c.DetectDuration, panicCh)
|
||||
|
||||
// Wait on the panic data
|
||||
panicTxt := <-panicCh
|
||||
if panicTxt != "" {
|
||||
if !c.HidePanic {
|
||||
os.Stderr.Write([]byte(panicTxt))
|
||||
}
|
||||
|
||||
c.Handler(panicTxt)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
exePath, err := osext.Executable()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||
|
||||
read, write, err := os.Pipe()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
cmd.Stdin = read
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = append(os.Environ(), c.CookieKey+"="+c.CookieValue)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
err = dup2(int(write.Fd()), int(os.Stderr.Fd()))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return -1, nil
|
||||
}
|
7
vendor/github.com/bugsnag/panicwrap/monitor_windows.go
generated
vendored
Normal file
7
vendor/github.com/bugsnag/panicwrap/monitor_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package panicwrap
|
||||
|
||||
import "fmt"
|
||||
|
||||
func monitor(c *WrapConfig) (int, error) {
|
||||
return -1, fmt.Errorf("Monitor is not supported on windows")
|
||||
}
|
339
vendor/github.com/bugsnag/panicwrap/panicwrap.go
generated
vendored
Normal file
339
vendor/github.com/bugsnag/panicwrap/panicwrap.go
generated
vendored
Normal file
|
@ -0,0 +1,339 @@
|
|||
// 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.
|
||||
package panicwrap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/bugsnag/osext"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_COOKIE_KEY = "cccf35992f8f3cd8d1d28f0109dd953e26664531"
|
||||
DEFAULT_COOKIE_VAL = "7c28215aca87789f95b406b8dd91aa5198406750"
|
||||
)
|
||||
|
||||
// HandlerFunc is the type called when a panic is detected.
|
||||
type HandlerFunc func(string)
|
||||
|
||||
// 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.
|
||||
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
|
||||
|
||||
// If true, panicwrap will boot a monitor sub-process and let the parent
|
||||
// run the app. This mode is useful for processes run under supervisors
|
||||
// like runit as signals get sent to the correct codebase. This is not
|
||||
// supported when GOOS=windows, and ignores c.Stderr and c.Stdout.
|
||||
Monitor 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
|
||||
}
|
||||
|
||||
// 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 BasicWrap(f HandlerFunc) (int, error) {
|
||||
return Wrap(&WrapConfig{
|
||||
Handler: f,
|
||||
})
|
||||
}
|
||||
|
||||
// BasicMonitor calls Wrap with Monitor set to true on supported platforms.
|
||||
// It forks your program and runs it again form the start. In one process
|
||||
// BasicMonitor never returns, it just listens on stderr of the other process,
|
||||
// and calls your handler when a panic is seen. In the other it either returns
|
||||
// nil to indicate that the panic monitoring is enabled, or an error to indicate
|
||||
// that something else went wrong.
|
||||
func BasicMonitor(f HandlerFunc) error {
|
||||
exitStatus, err := Wrap(&WrapConfig{
|
||||
Handler: f,
|
||||
Monitor: runtime.GOOS != "windows",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exitStatus >= 0 {
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 Wrap(c *WrapConfig) (int, error) {
|
||||
if c.Handler == nil {
|
||||
return -1, errors.New("Handler must be set")
|
||||
}
|
||||
|
||||
if c.DetectDuration == 0 {
|
||||
c.DetectDuration = 300 * time.Millisecond
|
||||
}
|
||||
|
||||
if c.Writer == nil {
|
||||
c.Writer = os.Stderr
|
||||
}
|
||||
|
||||
if c.Monitor {
|
||||
return monitor(c)
|
||||
} else {
|
||||
return wrap(c)
|
||||
}
|
||||
}
|
||||
|
||||
func wrap(c *WrapConfig) (int, error) {
|
||||
|
||||
// If we're already wrapped, exit out.
|
||||
if Wrapped(c) {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// Get the path to our current executable
|
||||
exePath, err := osext.Executable()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Pipe the stderr so we can read all the data as we look for panics
|
||||
stderr_r, stderr_w := io.Pipe()
|
||||
|
||||
// doneCh is closed when we're done, signaling any other goroutines
|
||||
// to end immediately.
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
// panicCh is the channel on which the panic text will actually be
|
||||
// sent.
|
||||
panicCh := make(chan string)
|
||||
|
||||
// On close, make sure to finish off the copying of data to stderr
|
||||
defer func() {
|
||||
defer close(doneCh)
|
||||
stderr_w.Close()
|
||||
<-panicCh
|
||||
}()
|
||||
|
||||
// Start the goroutine that will watch stderr for any panics
|
||||
go trackPanic(stderr_r, c.Writer, c.DetectDuration, panicCh)
|
||||
|
||||
// Create the writer for stdout that we're going to use
|
||||
var stdout_w io.Writer = os.Stdout
|
||||
if c.Stdout != nil {
|
||||
stdout_w = c.Stdout
|
||||
}
|
||||
|
||||
// Build a subcommand to re-execute ourselves. We make sure to
|
||||
// set the environmental variable to include our cookie. We also
|
||||
// set stdin/stdout to match the config. Finally, we pipe stderr
|
||||
// through ourselves in order to watch for panics.
|
||||
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||
cmd.Env = append(os.Environ(), c.CookieKey+"="+c.CookieValue)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = stdout_w
|
||||
cmd.Stderr = stderr_w
|
||||
if err := cmd.Start(); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
// Listen to signals and capture them forever. We allow the child
|
||||
// process to handle them in some way.
|
||||
sigCh := make(chan os.Signal)
|
||||
signal.Notify(sigCh, os.Interrupt)
|
||||
go func() {
|
||||
defer signal.Stop(sigCh)
|
||||
for {
|
||||
select {
|
||||
case <-doneCh:
|
||||
return
|
||||
case <-sigCh:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
exitErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
// This is some other kind of subprocessing error.
|
||||
return 1, err
|
||||
}
|
||||
|
||||
exitStatus := 1
|
||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = status.ExitStatus()
|
||||
}
|
||||
|
||||
// Close the writer end so that the tracker goroutine ends at some point
|
||||
stderr_w.Close()
|
||||
|
||||
// Wait on the panic data
|
||||
panicTxt := <-panicCh
|
||||
if panicTxt != "" {
|
||||
if !c.HidePanic {
|
||||
c.Writer.Write([]byte(panicTxt))
|
||||
}
|
||||
|
||||
c.Handler(panicTxt)
|
||||
}
|
||||
|
||||
return exitStatus, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Wrapped checks if we're already wrapped according to the configuration
|
||||
// given.
|
||||
//
|
||||
// Wrapped is very cheap and can be used early to short-circuit some pre-wrap
|
||||
// logic your application may have.
|
||||
func Wrapped(c *WrapConfig) bool {
|
||||
if c.CookieKey == "" {
|
||||
c.CookieKey = DEFAULT_COOKIE_KEY
|
||||
}
|
||||
|
||||
if c.CookieValue == "" {
|
||||
c.CookieValue = DEFAULT_COOKIE_VAL
|
||||
}
|
||||
|
||||
// If the cookie key/value match our environment, then we are the
|
||||
// child, so just exit now and tell the caller that we're the child
|
||||
return os.Getenv(c.CookieKey) == c.CookieValue
|
||||
}
|
||||
|
||||
// trackPanic monitors the given reader for a panic. If a panic is detected,
|
||||
// it is outputted on the result channel. This will close the channel once
|
||||
// it is complete.
|
||||
func trackPanic(r io.Reader, w io.Writer, dur time.Duration, result chan<- string) {
|
||||
defer close(result)
|
||||
|
||||
var panicTimer <-chan time.Time
|
||||
panicBuf := new(bytes.Buffer)
|
||||
panicHeader := []byte("panic:")
|
||||
|
||||
tempBuf := make([]byte, 2048)
|
||||
for {
|
||||
var buf []byte
|
||||
var n int
|
||||
|
||||
if panicTimer == nil && panicBuf.Len() > 0 {
|
||||
// We're not tracking a panic but the buffer length is
|
||||
// greater than 0. We need to clear out that buffer, but
|
||||
// look for another panic along the way.
|
||||
|
||||
// First, remove the previous panic header so we don't loop
|
||||
w.Write(panicBuf.Next(len(panicHeader)))
|
||||
|
||||
// Next, assume that this is our new buffer to inspect
|
||||
n = panicBuf.Len()
|
||||
buf = make([]byte, n)
|
||||
copy(buf, panicBuf.Bytes())
|
||||
panicBuf.Reset()
|
||||
} else {
|
||||
var err error
|
||||
buf = tempBuf
|
||||
n, err = r.Read(buf)
|
||||
if n <= 0 && err == io.EOF {
|
||||
if panicBuf.Len() > 0 {
|
||||
// We were tracking a panic, assume it was a panic
|
||||
// and return that as the result.
|
||||
result <- panicBuf.String()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if panicTimer != nil {
|
||||
// We're tracking what we think is a panic right now.
|
||||
// If the timer ended, then it is not a panic.
|
||||
isPanic := true
|
||||
select {
|
||||
case <-panicTimer:
|
||||
isPanic = false
|
||||
default:
|
||||
}
|
||||
|
||||
// No matter what, buffer the text some more.
|
||||
panicBuf.Write(buf[0:n])
|
||||
|
||||
if !isPanic {
|
||||
// It isn't a panic, stop tracking. Clean-up will happen
|
||||
// on the next iteration.
|
||||
panicTimer = nil
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
flushIdx := n
|
||||
idx := bytes.Index(buf[0:n], panicHeader)
|
||||
if idx >= 0 {
|
||||
flushIdx = idx
|
||||
}
|
||||
|
||||
// Flush to stderr what isn't a panic
|
||||
w.Write(buf[0:flushIdx])
|
||||
|
||||
if idx < 0 {
|
||||
// Not a panic so just continue along
|
||||
continue
|
||||
}
|
||||
|
||||
// We have a panic header. Write we assume is a panic os far.
|
||||
panicBuf.Write(buf[idx:n])
|
||||
panicTimer = time.After(dur)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue