Setup panicwrap

This commit is contained in:
Mitchell Hashimoto 2014-05-30 16:07:26 -07:00
parent 38d4f2a1bd
commit 7b64c2597b
3 changed files with 167 additions and 1 deletions

29
log.go Normal file
View File

@ -0,0 +1,29 @@
package main
import (
"io"
"os"
)
// These are the environmental variables that determine if we log, and if
// we log whether or not the log should go to a file.
const EnvLog = "TF_LOG"
const EnvLogFile = "TF_LOG_PATH"
// logOutput determines where we should send logs (if anywhere).
func logOutput() (logOutput io.Writer, err error) {
logOutput = nil
if os.Getenv(EnvLog) != "" {
logOutput = os.Stderr
if logPath := os.Getenv(EnvLogFile); logPath != "" {
var err error
logOutput, err = os.Create(logPath)
if err != nil {
return nil, err
}
}
}
return
}

76
main.go
View File

@ -6,7 +6,9 @@ import (
"log"
"os"
"github.com/ActiveState/tail"
"github.com/mitchellh/cli"
"github.com/mitchellh/panicwrap"
)
func main() {
@ -14,7 +16,79 @@ func main() {
}
func realMain() int {
log.SetOutput(ioutil.Discard)
var wrapConfig panicwrap.WrapConfig
if !panicwrap.Wrapped(&wrapConfig) {
// Determine where logs should go in general (requested by the user)
logWriter, err := logOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
return 1
}
// We always send logs to a temporary file that we use in case
// there is a panic. Otherwise, we delete it.
logTempFile, err := ioutil.TempFile("", "terraform-log")
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
return 1
}
logTempFile.Close()
defer os.Remove(logTempFile.Name())
// Tell the logger to log to this file
os.Setenv(EnvLog, "1")
os.Setenv(EnvLogFile, logTempFile.Name())
if logWriter != nil {
// Start tailing the file beforehand to get the data
t, err := tail.TailFile(logTempFile.Name(), tail.Config{
Follow: true,
Logger: tail.DiscardingLogger,
MustExist: true,
})
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
return 1
}
go func() {
for line := range t.Lines {
logWriter.Write([]byte(line.Text + "\n"))
}
}()
}
// Create the configuration for panicwrap and wrap our executable
wrapConfig.Handler = panicHandler(logTempFile)
exitStatus, err := panicwrap.Wrap(&wrapConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err)
return 1
}
// If >= 0, we're the parent, so just exit
if exitStatus >= 0 {
return exitStatus
}
// We're the child, so just close the tempfile we made in order to
// save file handles since the tempfile is only used by the parent.
logTempFile.Close()
}
// Call the real main
return wrappedMain()
}
func wrappedMain() int {
logOutput, err := logOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "Error starting Terraform: %s", err)
return 1
}
if logOutput != nil {
log.SetOutput(logOutput)
}
// Get the command line args. We shortcut "--version" and "-v" to
// just show the version.

63
panic.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/mitchellh/panicwrap"
)
// This output is shown if a panic happens.
const panicOutput = `
!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!
Terraform crashed! This is always indicative of a bug within Terraform.
A crash log has been placed at "crash.log" relative to your current
working directory. It would be immensely helpful if you could please
report the crash with Terraform[1] so that we can fix this.
[1]: https://github.com/hashicorp/terraform/issues
!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!
`
// panicHandler is what is called by panicwrap when a panic is encountered
// within Terraform. It is guaranteed to run after the resulting process has
// exited so we can take the log file, add in the panic, and store it
// somewhere locally.
func panicHandler(logF *os.File) panicwrap.HandlerFunc {
return func(m string) {
// Right away just output this thing on stderr so that it gets
// shown in case anything below fails.
fmt.Fprintf(os.Stderr, fmt.Sprintf("%s\n", m))
// Create the crash log file where we'll write the logs
f, err := os.Create("crash.log")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create crash log file: %s", err)
return
}
defer f.Close()
// Seek the log file back to the beginning
if _, err = logF.Seek(0, 0); err != nil {
fmt.Fprintf(os.Stderr, "Failed to seek log file for crash: %s", err)
return
}
// Copy the contents to the crash file. This will include
// the panic that just happened.
if _, err = io.Copy(f, logF); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write crash log: %s", err)
return
}
// Tell the user a crash occurred in some helpful way that
// they'll hopefully notice.
fmt.Printf("\n\n")
fmt.Println(strings.TrimSpace(panicOutput))
}
}