| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package debug_test | |
| import ( | |
| "io" | |
| "log" | |
| "os" | |
| "os/exec" | |
| "runtime/debug" | |
| ) | |
| // ExampleSetCrashOutput_monitor shows an example of using | |
| // [debug.SetCrashOutput] to direct crashes to a "monitor" process, | |
| // for automated crash reporting. The monitor is the same executable, | |
| // invoked in a special mode indicated by an environment variable. | |
| func ExampleSetCrashOutput_monitor() { | |
| appmain() | |
| // This Example doesn't actually run as a test because its | |
| // purpose is to crash, so it has no "Output:" comment | |
| // within the function body. | |
| // | |
| // To observe the monitor in action, replace the entire text | |
| // of this comment with "Output:" and run this command: | |
| // | |
| // $ go test -run=ExampleSetCrashOutput_monitor runtime/debug | |
| // panic: oops | |
| // ...stack... | |
| // monitor: saved crash report at /tmp/10804884239807998216.crash | |
| } | |
| // appmain represents the 'main' function of your application. | |
| func appmain() { | |
| monitor() | |
| // Run the application. | |
| println("hello") | |
| panic("oops") | |
| } | |
| // monitor starts the monitor process, which performs automated | |
| // crash reporting. Call this function immediately within main. | |
| // | |
| // This function re-executes the same executable as a child process, | |
| // in a special mode. In that mode, the call to monitor will never | |
| // return. | |
| func monitor() { | |
| const monitorVar = "RUNTIME_DEBUG_MONITOR" | |
| if os.Getenv(monitorVar) != "" { | |
| // This is the monitor (child) process. | |
| log.SetFlags(0) | |
| log.SetPrefix("monitor: ") | |
| crash, err := io.ReadAll(os.Stdin) | |
| if err != nil { | |
| log.Fatalf("failed to read from input pipe: %v", err) | |
| } | |
| if len(crash) == 0 { | |
| // Parent process terminated without reporting a crash. | |
| os.Exit(0) | |
| } | |
| // Save the crash report securely in the file system. | |
| f, err := os.CreateTemp("", "*.crash") | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| if _, err := f.Write(crash); err != nil { | |
| log.Fatal(err) | |
| } | |
| if err := f.Close(); err != nil { | |
| log.Fatal(err) | |
| } | |
| log.Fatalf("saved crash report at %s", f.Name()) | |
| } | |
| // This is the application process. | |
| // Fork+exec the same executable in monitor mode. | |
| exe, err := os.Executable() | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| cmd := exec.Command(exe, "-test.run=^ExampleSetCrashOutput_monitor$") | |
| // Be selective in which variables we allow the child to inherit. | |
| // Depending on the application, some may be necessary, | |
| // while others (e.g. GOGC, GOMEMLIMIT) may be harmful; see #73490. | |
| cmd.Env = []string{monitorVar + "=1"} | |
| cmd.Stderr = os.Stderr | |
| cmd.Stdout = os.Stderr | |
| pipe, err := cmd.StdinPipe() | |
| if err != nil { | |
| log.Fatalf("StdinPipe: %v", err) | |
| } | |
| debug.SetCrashOutput(pipe.(*os.File), debug.CrashOptions{}) // (this conversion is safe) | |
| if err := cmd.Start(); err != nil { | |
| log.Fatalf("can't start monitor: %v", err) | |
| } | |
| // Now return and start the application proper... | |
| } | |