From a959c4f0bef5794f7b7f4a62ed86455c11ac280f Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 23 Jul 2024 15:22:58 +0200 Subject: [PATCH] Log stacktraces --- .gitignore | 3 +++ core/src/main.jl | 39 +++++++++++++++++++++++++++------------ core/test/main_test.jl | 27 +++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 5dfadba8f..c43f491f5 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,6 @@ playground/output # Ruff .ruff_cache .teamcity/.idea/ + +# https://github.com/Deltares/Ribasim/issues/1652 +ribasim_qgis/tests/data/database.gpkg diff --git a/core/src/main.jl b/core/src/main.jl index c3e775606..0a2ae437c 100644 --- a/core/src/main.jl +++ b/core/src/main.jl @@ -40,23 +40,38 @@ function main(toml_path::AbstractString)::Cint @warn "The Ribasim version in the TOML config file does not match the used Ribasim CLI version." config.ribasim_version cli.ribasim_version end @info "Starting a Ribasim simulation." cli.ribasim_version starttime endtime - model = run(config) - - if successful_retcode(model) - log_bottlenecks(model; converged = true) - @info "The model finished successfully" - return 0 - else - log_bottlenecks(model; converged = false) - t = datetime_since(model.integrator.t, starttime) - retcode = model.integrator.sol.retcode - @error """The model exited at model time $t with return code $retcode. - See https://docs.sciml.ai/DiffEqDocs/stable/basics/solution/#retcodes""" + + try + model = run(config) + + if successful_retcode(model) + log_bottlenecks(model; converged = true) + @info "The model finished successfully." + return 0 + else + # OrdinaryDiffEq doesn't error on e.g. convergence failure, + # but we want a non-zero exit code in that case. + log_bottlenecks(model; converged = false) + t = datetime_since(model.integrator.t, starttime) + retcode = model.integrator.sol.retcode + @error """The model exited at model time $t with return code $retcode. + See https://docs.sciml.ai/DiffEqDocs/stable/basics/solution/#retcodes""" + return 1 + end + + catch + # Both validation errors that we throw and unhandled exceptions are caught here. + # To make it easier to find the cause, log the stacktrace to the terminal and log file. + stack = current_exceptions() + Base.invokelatest(Base.display_error, stack) + Base.invokelatest(Base.display_error, io, stack) return 1 end end end catch + # If it fails before we get to setup the logger, we can't log to a file. + # This happens if e.g. the config is invalid. Base.invokelatest(Base.display_error, current_exceptions()) return 1 end diff --git a/core/test/main_test.jl b/core/test/main_test.jl index 77db7b21b..788e9c7c3 100644 --- a/core/test/main_test.jl +++ b/core/test/main_test.jl @@ -1,7 +1,7 @@ - -@testitem "toml_path" begin +@testitem "main output" begin using IOCapture: capture import TOML + using Ribasim: Config, results_path model_path = normpath(@__DIR__, "../../generated_testmodels/basic/") toml_path = normpath(model_path, "ribasim.toml") @@ -26,3 +26,26 @@ @test occursin("version in the TOML config file does not match", output) @test occursin("Info: Convergence bottlenecks in descending order of severity:", output) end + +@testitem "main error logging" begin + using IOCapture: capture + import TOML + using Ribasim: Config, results_path + + model_path = normpath(@__DIR__, "../../generated_testmodels/invalid_edge_types/") + toml_path = normpath(model_path, "ribasim.toml") + + @test ispath(toml_path) + (; value, output) = capture() do + Ribasim.main(toml_path) + end + @test value == 1 + + # Stacktraces should be written to both the terminal and log file. + @test occursin("\nStacktrace:\n", output) + config = Config(toml_path) + log_path = results_path(config, "ribasim.log") + @test ispath(log_path) + log_str = read(log_path, String) + @test occursin("\nStacktrace:\n", log_str) +end