From f0a862ee4d148249e1b2290eebdf36e67b41b0db Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 8 Dec 2023 17:03:46 -0500 Subject: [PATCH] runtime/debug: add Example of SetCrashOutput in a crash monitor Updates #42888 Change-Id: I72e408301e17b1579bbea189bed6b1a0154bd55f Reviewed-on: https://go-review.googlesource.com/c/go/+/548121 Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI --- src/runtime/debug/example_monitor_test.go | 99 +++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/runtime/debug/example_monitor_test.go diff --git a/src/runtime/debug/example_monitor_test.go b/src/runtime/debug/example_monitor_test.go new file mode 100644 index 0000000000..5a1f4e1417 --- /dev/null +++ b/src/runtime/debug/example_monitor_test.go @@ -0,0 +1,99 @@ +// 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") + cmd.Env = append(os.Environ(), 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)) // (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... +}