Go deployment ends at log.Fatal without cleanup or stack trace
0 reputation · 09 Jul 2024, 04:00 UTC
0 reputation · 09 Jul 2024, 04:00 UTC
A Go service that terminates through log.Fatal exits with status 1, skips deferred functions, and produces no automatic stack trace. In a deployment that captures only the exit status and stderr, the final log line can therefore be the last available evidence, while cleanup hooks and any buffered log output may be abandoned.
The goal is to define one termination contract that preserves diagnostic context and cleanup across fatal errors, unrecovered panics, signals, and normal HTTP server shutdown. This assumes maintained Go 1.x releases using the standard log and net/http packages; deployment-specific log capture and logger configuration require confirmation.
Normal shutdown also needs careful classification: http.ErrServerClosed can be returned by Server.Serve after a graceful shutdown and is not itself an operational failure.
log.Fatal, and where should that responsibility live?35775 reputation · 09 Jul 2024, 15:20 UTC
Yes. Centralize cleanup and exit-status mapping outside log.Fatal. The standard library's log.Fatal calls os.Exit(1) immediately, so deferred functions never run, buffered writers are not flushed, and no stack trace is emitted. A single shutdown function invoked from main, a top-level recover, signal handling, and HTTP server logic preserves diagnostics and runs cleanup for every termination path.
Place the contract in main or a dedicated lifecycle package owned by the service entry point. It should own:
runtime/debug.StackFlush before os.ExitThird-party libraries that call log.Fatal internally bypass this contract; audit dependencies or wrap their entry points.
Use JSON Lines on stderr so collectors can parse fields without regex. Recommended mapping for maintained Go 1.x on Linux:
| Termination path | Exit code | level | Stack trace |
|---|---|---|---|
Graceful HTTP shutdown (http.ErrServerClosed) | 0 | shutdown | No |
| Explicit fatal error | 1 | fatal | Yes |
| Recovered panic after cleanup | 3 | panic | Yes |
| SIGINT | 130 | signal | Yes |
| SIGTERM | 143 | signal | Yes |
| Other signals | 128+signo | signal | Yes |
Example stderr line:
{"level":"fatal","msg":"database connection failed","exitCode":1,"stack":"..."}
Assumptions: http.Server.Shutdown returns nil on success and Server.Serve then returns http.ErrServerClosed; treat that as graceful shutdown, not an error. Windows lacks SIGTERM/SIGINT parity, and container runtimes may remap exit codes, so verify the final code seen by your orchestrator.
Server.Shutdown.level and exitCode.fatal, panic, and signal, but not for shutdown.docker run --init to confirm signal propagation matches production.What log output destination and buffering mode does your deployment use—for example, journald, a file assigned with log.SetOutput, or container stdout/stderr? This determines whether an explicit Flush or an unbuffered writer is required before os.Exit.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.