The log file is saved under the working directory.
What this does correctly
- log_file and log_message() set up your logging function — nothing special here, just a helper that appends timestamped lines to a file.
- options(shiny.error = ...) registers a global hook. Whenever an error occurs anywhere in your app's reactive code and nothing catches it, Shiny calls this function before showing its default error UI to the user.
- geterrmessage() retrieves the text of whatever error condition was just raised — that's what gets written to the log.
Caveats to be aware of
- Only errors, not warnings or messages. shiny.error fires only for stop()-class errors. If your code uses warning() or message(), those won't be logged with just this snippet — you'd need the withCallingHandlers() addition from earlier.
- Only uncaught errors. If you (or a package you're using) wraps something in tryCatch() internally and handles the error there, shiny.error never fires, because the error never propagates up to Shiny's top level.
- The user still sees Shiny's default error box. This snippet only handles logging — it doesn't change what's shown in the UI. The reactive block that errored stops executing at that point, so any output$... assignment after the error line won't run.
- Global state. options(shiny.error = ...) is a single global setting for the whole app — you can't have different error-handling behavior for different parts of the app without additional logic inside that one function (e.g., checking session$token or similar).
- Placement matters for multi-file apps. If you're using the ui.R / server.R two-file structure (rather than a single app.R), put these lines in a global.R file, or at the top of server.R before the server <- function(...) definition — anywhere that runs once when the app starts, before any reactive code fires.