Enable panicwrap and put crash logs in crash.log

This commit is contained in:
Mitchell Hashimoto 2013-08-13 23:54:35 -04:00
parent 80e37446e9
commit 0dfaacc09d
3 changed files with 143 additions and 29 deletions

View File

@ -5,6 +5,8 @@ FEATURES:
* New command: `packer inspect`. This command tells you the components of * New command: `packer inspect`. This command tells you the components of
a template. It respects the `-machine-readable` flag as well so you can a template. It respects the `-machine-readable` flag as well so you can
parse out components of a template. parse out components of a template.
* Packer will detect its own crashes (always a bug) and save a "crash.log"
file.
IMPROVEMENTS: IMPROVEMENTS:

110
packer.go
View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer/plugin" "github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/panicwrap"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
@ -15,32 +16,68 @@ import (
) )
func main() { func main() {
// Setup logging if PACKER_LOG is set. // Call realMain instead of doing the work here so we can use
// Log to PACKER_LOG_PATH if it is set, otherwise default to stderr. // `defer` statements within the function and have them work properly.
var logOutput io.Writer = ioutil.Discard // (defers aren't called with os.Exit)
if os.Getenv("PACKER_LOG") != "" { os.Exit(realMain())
logOutput = os.Stderr }
if logPath := os.Getenv("PACKER_LOG_PATH"); logPath != "" {
var err error
logOutput, err = os.Create(logPath)
if err != nil {
fmt.Fprintf(
os.Stderr,
"Couldn't open '%s' for logging: %s",
logPath, err)
os.Exit(1)
}
}
}
log.SetOutput(logOutput)
// realMain is executed from main and returns the exit status to exit with.
func realMain() int {
// If there is no explicit number of Go threads to use, then set it // If there is no explicit number of Go threads to use, then set it
if os.Getenv("GOMAXPROCS") == "" { if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
} }
// 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 also always send logs to a temporary file that we use in case
// there is a panic. Otherwise, we delete it.
logTempFile, err := ioutil.TempFile("", "packer-log")
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
return 1
}
defer os.Remove(logTempFile.Name())
defer logTempFile.Close()
// Reset the log variables to minimize work in the subprocess
os.Setenv("PACKER_LOG", "")
os.Setenv("PACKER_LOG_FILE", "")
// Create the configuration for panicwrap and wrap our executable
wrapConfig := &panicwrap.WrapConfig{
Handler: panicHandler(logTempFile),
Writer: io.MultiWriter(logTempFile, logWriter),
}
exitStatus, err := panicwrap.Wrap(wrapConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err)
return 1
}
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()
return wrappedMain()
}
// wrappedMain is called only when we're wrapped by panicwrap and
// returns the exit status to exit with.
func wrappedMain() int {
log.SetOutput(os.Stderr)
log.Printf( log.Printf(
"Packer Version: %s %s %s", "Packer Version: %s %s %s",
packer.Version, packer.VersionPrerelease, packer.GitCommit) packer.Version, packer.VersionPrerelease, packer.GitCommit)
@ -52,7 +89,7 @@ func main() {
config, err := loadConfig() config, err := loadConfig()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err) fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
os.Exit(1) return 1
} }
log.Printf("Packer config: %+v", config) log.Printf("Packer config: %+v", config)
@ -65,12 +102,12 @@ func main() {
cacheDir, err = filepath.Abs(cacheDir) cacheDir, err = filepath.Abs(cacheDir)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err) fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err)
os.Exit(1) return 1
} }
if err := os.MkdirAll(cacheDir, 0755); err != nil { if err := os.MkdirAll(cacheDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err) fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err)
os.Exit(1) return 1
} }
log.Printf("Setting cache directory: %s", cacheDir) log.Printf("Setting cache directory: %s", cacheDir)
@ -100,8 +137,7 @@ func main() {
env, err := packer.NewEnvironment(envConfig) env, err := packer.NewEnvironment(envConfig)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Packer initialization error: \n\n%s\n", err) fmt.Fprintf(os.Stderr, "Packer initialization error: \n\n%s\n", err)
plugin.CleanupClients() return 1
os.Exit(1)
} }
setupSignalHandlers(env) setupSignalHandlers(env)
@ -109,12 +145,10 @@ func main() {
exitCode, err := env.Cli(args) exitCode, err := env.Cli(args)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
plugin.CleanupClients() return 1
os.Exit(1)
} }
plugin.CleanupClients() return exitCode
os.Exit(exitCode)
} }
// extractMachineReadable checks the args for the machine readable // extractMachineReadable checks the args for the machine readable
@ -178,3 +212,21 @@ func loadConfig() (*config, error) {
return &config, nil return &config, nil
} }
// logOutput determines where we should send logs (if anywhere).
func logOutput() (logOutput io.Writer, err error) {
logOutput = ioutil.Discard
if os.Getenv("PACKER_LOG") != "" {
logOutput = os.Stderr
if logPath := os.Getenv("PACKER_LOG_PATH"); logPath != "" {
var err error
logOutput, err = os.Create(logPath)
if err != nil {
return nil, err
}
}
}
return
}

60
panic.go Normal file
View File

@ -0,0 +1,60 @@
package main
import (
"fmt"
"github.com/mitchellh/panicwrap"
"io"
"os"
"strings"
)
// This is output if a panic happens.
const panicOutput = `
!!!!!!!!!!!!!!!!!!!!!!!!!!! PACKER CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!
Packer crashed! This is always indicative of a bug within Packer.
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 Packer[1] so that we can fix this.
!!!!!!!!!!!!!!!!!!!!!!!!!!! PACKER CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!
`
// panicHandler is what is called by panicwrap when a panic is encountered
// within Packer. 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) {
// Write 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))
}
}