Quick Fix for Debugging Panic Failures in Go
So I was working on LiveAPI, a tool I built to generate interactive API docs by consuming a Git repository. Every generation gets tagged with a job_id, and everything goes well—until it doesn't. Sometimes, the server would crash. Not your classic panic("oops"). I'm talking about real crashes: slice index out of range, nil pointer dereference—stuff that doesn’t announce itself politely. What I Needed Something like: Catch panics Log the job ID that was last printed to stdout Capture the stack trace Save everything to a file for post-mortem ⚡️ The Hack I ended up redirecting os.Stdout into a buffer, searching for the last printed job ID using a regex, and then dumping all of that to a log file. Here’s how I did it: ✨ errorcodes/panic_logger.go var ( outputBuffer bytes.Buffer bufferLock sync.Mutex ) func init() { pipeReader, pipeWriter, err := os.Pipe() if err != nil { log.Fatal().Err(err).Msg("Failed to create pipe") } os.Stdout = pipeWriter go func() { scanner := bufio.NewScanner(pipeReader) for scanner.Scan() { line := scanner.Text() bufferLock.Lock() outputBuffer.WriteString(line + "\n") bufferLock.Unlock() fmt.Fprintln(os.Stderr, line) // still print to terminal } }() } This intercepts every fmt.Println() you do.

So I was working on LiveAPI, a tool I built to generate interactive API docs by consuming a Git repository.
Every generation gets tagged with a job_id
, and everything goes well—until it doesn't.
Sometimes, the server would crash.
Not your classic panic("oops")
.
I'm talking about real crashes: slice index out of range, nil pointer dereference—stuff that doesn’t announce itself politely.
What I Needed
Something like:
- Catch panics
- Log the job ID that was last printed to stdout
- Capture the stack trace
- Save everything to a file for post-mortem
⚡️ The Hack
I ended up redirecting os.Stdout
into a buffer, searching for the last printed job ID using a regex, and then dumping all of that to a log file.
Here’s how I did it:
✨ errorcodes/panic_logger.go
var (
outputBuffer bytes.Buffer
bufferLock sync.Mutex
)
func init() {
pipeReader, pipeWriter, err := os.Pipe()
if err != nil {
log.Fatal().Err(err).Msg("Failed to create pipe")
}
os.Stdout = pipeWriter
go func() {
scanner := bufio.NewScanner(pipeReader)
for scanner.Scan() {
line := scanner.Text()
bufferLock.Lock()
outputBuffer.WriteString(line + "\n")
bufferLock.Unlock()
fmt.Fprintln(os.Stderr, line) // still print to terminal
}
}()
}
This intercepts every fmt.Println()
you do.