Does make-file-logger guarantee log persistence during abrupt process termination?
0 reputation · 31 Jul 2021, 04:30 UTC
0 reputation · 31 Jul 2021, 04:30 UTC
When utilizing make-file-logger to capture deployment diagnostics, the underlying port employs default buffering. This means log entries are written to disk only when the buffer fills, the program terminates normally, or an explicit flush is triggered.
In scenarios where a deployment fails due to an uncaught exception or a runtime crash, there is a risk that the final, most critical error messages remain in the buffer and are never written to the file. While setting the buffering mode to 'line or manually calling logger-flush can mitigate this, these options may impact performance in high-throughput environments.
Which configuration provides the best balance between performance and reliability for post-mortem analysis? Does Racket provide a mechanism to ensure the final log buffer is flushed specifically during a fatal runtime crash without manual flushing after every call?
34775 reputation · 31 Jul 2021, 13:14 UTC
No. make-file-logger does not guarantee that buffered log entries reach disk if the process dies abruptly. The file port it creates uses block buffering by default, so entries are written only when the buffer fills, the port is explicitly flushed, or the port is closed during a normal shutdown. An uncaught exception that escapes the runtime's cleanup, a segfault, or SIGKILL will silently drop whatever is still in the buffer — typically the exact error messages you wanted for post-mortem analysis.
There is no built-in "flush on fatal crash" hook in the logging system itself. You have three realistic options, in increasing order of reliability:
'line buffering so each newline-terminated entry is flushed immediately. This survives uncaught exceptions and most signals, but not SIGKILL or a hard crash, and it adds per-line I/O overhead.flush-output on the logger's port (or logger-flush where available) before the custodian tears down. This catches normal exits and most exception-driven terminations, because Racket's runtime still gets to run cleanup code.log-fatal/log-error calls. This is usually the best balance for deployment diagnostics.(define log-port
(open-output-file "deploy.log" #:exists 'append))
(define logger (make-file-logger "deploy" log-port))
;; Flush when the custodian shuts down (normal exit, most exceptions)
(define shutdown-cust (make-custodian))
(parameterize ([current-custodian shutdown-cust])
(thread (lambda ()
(sync (custodian-shutdown-event shutdown-cust))
(flush-output log-port))))
;; For the messages that matter most:
(define (log-critical! msg)
(log-message logger 'fatal msg)
(flush-output log-port))The exact event API varies by Racket version; verify against your installed racket/logging and custodian docs rather than assuming this snippet compiles unchanged.
Do not trust the configuration without testing it against your real termination paths:
SIGTERM — with default buffering the tail is usually missing; with a shutdown handler or line buffering it should be present.SIGKILL (kill -9) — messages will be lost regardless of buffering mode or handlers, because no Racket code runs. No software-side flush can fix this.If your deployment process can be killed with SIGKILL or can crash below the runtime (segfault in a foreign library, OOM killer), the only robust answer is to move persistence out of the process: log to stdout and let a supervisor, systemd journal, or a separate collector own the file. For everything short of that, line buffering plus a custodian flush handler covers the realistic failure set at acceptable cost.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.