Skip to contents

Importance of Logging

Logging in FIMS is important because it allows developers to understand what is happening within a model. It is especially useful when problems arise. The FIMS logging system has been designed to handle common issues, such as undefined modules, dimension issues, and software errors that may occur when a model is not properly defined. This logging system is accessible from both R and C++ and messages are provided using a JSON format.

How FIMS Logging works

At run time, logging messages are stored in a C++ structure called LogEntry in inst/include/common/def.hpp. This structure contains useful information, such as information regarding when the LogEntry was created and what portion of the code initiated its creation. For details regarding the contents of a LogEntry see the doxygen documentation. All of the LogEntry(s) are stored in a log file that can be accessed from within your R environment or written to the disk. Additionally, this file can automatically be written to the disk when R fails to successfully communicate with C++.

Backend C++ Specification

The logging system (specified in inst/include/common/def.hpp) provides three useful macro functions for creating log entries, FIMS_INFO_LOG, FIMS_WARNING_LOG, and FIMS_ERROR_LOG. These macros take a single string value as an argument, the rest is handled internally. Therefore, the developer only needs to worry about specifying the message and the macros take care of capturing all the other elements of the log entry. For example, FIMS_INFO_LOG is used quite a bit in information.hpp to let the user know that items were initialized appropriately. Developers can specify the information contained within the string passed to the macro to be generic or specific to values stored inside the C++ code. See below for an example of a generic message that is completely specified by the user and a more specific message that uses information stored within C++ for part of the message. Also, note that the C++ function fims::to_string converts a numeric value to a string, making it additive to the message.

FIMS_INFO_LOG("Starting to initialize the fleet structures")
FIMS_INFO_LOG("Initializing fleet " + fims::to_string(f->id))

Below is a real-world example of a log entry that was created while running a FIMS model due to FIMS_INFO_LOG within selectivity in information.hpp. The log entry specifies the line of the file with the macro that led to the log entry. The screenshot below shows what the user would see if this log entry were invoked.

Additionally, if FIMS has been compiled with the -DFIMS_DEBUG pre-processing macro, output from the FIMS_DEBUG_LOG macro will also be available in the log file, allowing developers a more interactive developing experience. The output from this macro is turned off in the main branch, and thus, the macro is not available to the typical user to stop debugging statements from polluting the log file.

Using the Logging System in R

The FIMS Logging System is also available from R, with a caveat! Logging from R gives less information than logging from C++. When a log entry originates from R, file, routine, and line information are absent. Further implementations may rectify this issue. Below are examples of adding log entries from R.

log_info("info entry from R script")
log_warning("warning entry from R script")
error <- log_error("error entry from R script")
## before call.after call.
get_log_errors()
## [1] "[\n{\n\"timestamp\" : \"Fri Jan 17 15:11:09 2025\",\n\"level\" : \"error\",\n\"message\" : \"error entry from R script\",\n\"id\" : \"0\",\n\"user\" : \"runner\",\n\"wd\" : \"/home/runner/work/FIMS/FIMS/vignettes\",\n\"file\" : \"/home/runner/work/FIMS/FIMS/vignettes/R_env\",\n\"routine\" : \"traceback: 41: .External(list(name = \\\"InternalFunction_invoke\\\", address = <pointer: 0x55f07488e2f0>, \\n        dll = list(name = \\\"Rcpp\\\", path = \\\"/home/runner/work/_temp/Library/Rcpp/libs/Rcpp.so\\\", \\n            dynamicLookup = TRUE, handle = <pointer: 0x55f077785ac0>, \\n            info = <pointer: 0x55f0748796a0>, forceSymbols = FALSE), \\n        numParameters = -1L), <pointer: 0x55f07c7f8bb0>, ...)\\n40: log_error(\\\"error entry from R script\\\")\\n39: eval(expr, envir)\\n38: eval(expr, envir)\\n37: withVisible(eval(expr, envir))\\n36: withCallingHandlers(code, message = function (cnd) \\n    {\\n        watcher$capture_plot_and_output()\\n        if (on_message$capture) {\\n            watcher$push(cnd)\\n        }\\n        if (on_message$silence) {\\n            invokeRestart(\\\"muffleMessage\\\")\\n        }\\n    }, warning = function (cnd) \\n    {\\n        if (getOption(\\\"warn\\\") >= 2 || getOption(\\\"warn\\\") < 0) {\\n            return()\\n        }\\n        watcher$capture_plot_and_output()\\n        if (on_warning$capture) {\\n            cnd <- sanitize_call(cnd)\\n            watcher$push(cnd)\\n        }\\n        if (on_warning$silence) {\\n            invokeRestart(\\\"muffleWarning\\\")\\n        }\\n    }, error = function (cnd) \\n    {\\n        watcher$capture_plot_and_output()\\n        cnd <- sanitize_call(cnd)\\n        watcher$push(cnd)\\n        switch(on_error, continue = invokeRestart(\\\"eval_continue\\\"), \\n            stop = invokeRestart(\\\"eval_stop\\\"), error = NULL)\\n    })\\n35: eval(call)\\n34: eval(call)\\n33: with_handlers({\\n        for (expr in tle$exprs) {\\n            ev <- withVisible(eval(expr, envir))\\n            watcher$capture_plot_and_output()\\n            watcher$print_value(ev$value, ev$visible, envir)\\n        }\\n        TRUE\\n    }, handlers)\\n32: doWithOneRestart(return(expr), restart)\\n31: withOneRestart(expr, restarts[[1L]])\\n30: withRestartList(expr, restarts[-nr])\\n29: doWithOneRestart(return(expr), restart)\\n28: withOneRestart(withRestartList(expr, restarts[-nr]), restarts[[nr]])\\n27: withRestartList(expr, restarts)\\n26: withRestarts(with_handlers({\\n        for (expr in tle$exprs) {\\n            ev <- withVisible(eval(expr, envir))\\n            watcher$capture_plot_and_output()\\n            watcher$print_value(ev$value, ev$visible, envir)\\n        }\\n        TRUE\\n    }, handlers), eval_continue = function() TRUE, eval_stop = function() FALSE)\\n25: evaluate::evaluate(...)\\n24: evaluate(code, envir = env, new_device = FALSE, keep_warning = if (is.numeric(options$warning)) TRUE else options$warning, \\n        keep_message = if (is.numeric(options$message)) TRUE else options$message, \\n        stop_on_error = if (is.numeric(options$error)) options$error else {\\n            if (options$error && options$include) \\n                0L\\n            else 2L\\n        }, output_handler = knit_handlers(options$render, options))\\n23: in_dir(input_dir(), expr)\\n22: in_input_dir(evaluate(code, envir = env, new_device = FALSE, \\n        keep_warning = if (is.numeric(options$warning)) TRUE else options$warning, \\n        keep_message = if (is.numeric(options$message)) TRUE else options$message, \\n        stop_on_error = if (is.numeric(options$error)) options$error else {\\n            if (options$error && options$include) \\n                0L\\n            else 2L\\n        }, output_handler = knit_handlers(options$render, options)))\\n21: eng_r(options)\\n20: block_exec(params)\\n19: call_block(x)\\n18: process_group(group)\\n17: withCallingHandlers(if (tangle) process_tangle(group) else process_group(group), \\n        error = function(e) if (xfun::pkg_available(\\\"rlang\\\", \\\"1.0.0\\\")) rlang::entrace(e))\\n16: xfun:::handle_error(withCallingHandlers(if (tangle) process_tangle(group) else process_group(group), \\n        error = function(e) if (xfun::pkg_available(\\\"rlang\\\", \\\"1.0.0\\\")) rlang::entrace(e)), \\n        function(loc) {\\n            setwd(wd)\\n            write_utf8(res, output %n% stdout())\\n            paste0(\\\"\\nQuitting from lines \\\", loc)\\n        }, if (labels[i] != \\\"\\\") sprintf(\\\" [%s]\\\", labels[i]), get_loc)\\n15: process_file(text, output)\\n14: knitr::knit(knit_input, knit_output, envir = envir, quiet = quiet)\\n13: rmarkdown::render(envir = globalenv(), ...)\\n12: (function (..., seed = NULL) \\n    {\\n        if (!is.null(seed)) {\\n            set.seed(seed)\\n            if (requireNamespace(\\\"htmlwidgets\\\", quietly = TRUE)) {\\n                htmlwidgets::setWidgetIdSeed(seed)\\n            }\\n        }\\n        options(knitr.graphics.rel_path = FALSE)\\n        rmarkdown::render(envir = globalenv(), ...)\\n    })(input = base::quote(\\\"/home/runner/work/FIMS/FIMS/vignettes/fims-logging.Rmd\\\"), \\n        output_file = base::quote(\\\"fims-logging.html\\\"), output_dir = base::quote(\\\"/tmp/RtmpCeq3yy/file1c86119bb609/articles\\\"), \\n        intermediates_dir = base::quote(\\\"/tmp/RtmpCeq3yy\\\"), encoding = base::quote(\\\"UTF-8\\\"), \\n        seed = base::quote(1014L), output_format = base::quote(list(\\n            knitr = list(opts_knit = NULL, opts_chunk = list(dev = \\\"ragg_png\\\", \\n                dpi = 96L, fig.width = 7.29166666666667, fig.height = 4.50659250103008, \\n                fig.retina = 2L, dev.args = list(bg = NA), fig.ext = \\\"png\\\", \\n                other.parameters = list()), knit_hooks = NULL, opts_hooks = NULL, \\n                opts_template = NULL), pandoc = list(to = \\\"html\\\", \\n                from = \\\"markdown+autolink_bare_uris+tex_math_single_backslash\\\", \\n                args = c(\\\"--standalone\\\", \\\"--section-divs\\\", \\\"--template\\\", \\n                \\\"/tmp/RtmpCeq3yy/pkgdown-rmd-template-1c8654c0a0ce.html\\\", \\n                \\\"--highlight-style\\\", \\\"pygments\\\"), keep_tex = FALSE, \\n                latex_engine = \\\"pdflatex\\\", ext = NULL, convert_fun = NULL, \\n                lua_filters = c(\\\"/home/runner/work/_temp/Library/rmarkdown/rmarkdown/lua/pagebreak.lua\\\", \\n                \\\"/home/runner/work/_temp/Library/rmarkdown/rmarkdown/lua/latex-div.lua\\\"\\n                )), keep_md = FALSE, clean_supporting = FALSE, df_print = \\\"default\\\", \\n            pre_knit = function (...) \\n            {\\n                options(width = width)\\n                if (is.function(old_pre)) {\\n                    old_pre(...)\\n                }\\n            }, post_knit = function (...) \\n            {\\n                op(base(...), overlay(...))\\n            }, pre_processor = function (...) \\n            {\\n                op(base(...), overlay(...))\\n            }, intermediates_generator = function (original_input, \\n                intermediates_dir) \\n            {\\n                copy_render_intermediates(original_input, intermediates_dir, \\n                    !self_contained)\\n            }, post_processor = function (metadata, input_file, output_file, \\n                ...) \\n            {\\n                original_output_file <- output_file\\n                output_file <- overlay(metadata, input_file, output_file, \\n                    ...)\\n                if (!is.null(attr(output_file, \\\"post_process_original\\\"))) \\n                    base(metadata, input_file, original_output_file, \\n                      ...)\\n                base(metadata, input_file, output_file, ...)\\n            }, file_scope = NULL, on_exit = function () \\n            {\\n                if (is.function(base)) \\n                    base()\\n                if (is.function(overlay)) \\n                    overlay()\\n            })), output_options = base::quote(NULL), quiet = base::quote(TRUE))\\n11: (function (what, args, quote = FALSE, envir = parent.frame()) \\n    {\\n        if (!is.list(args)) \\n            stop(\\\"second argument must be a list\\\")\\n        if (quote) \\n            args <- lapply(args, enquote)\\n        .Internal(do.call(what, args, envir))\\n    })(base::quote(function (..., seed = NULL) \\n    {\\n        if (!is.null(seed)) {\\n            set.seed(seed)\\n            if (requireNamespace(\\\"htmlwidgets\\\", quietly = TRUE)) {\\n                htmlwidgets::setWidgetIdSeed(seed)\\n            }\\n        }\\n        options(knitr.graphics.rel_path = FALSE)\\n        rmarkdown::render(envir = globalenv(), ...)\\n    }), base::quote(list(input = \\\"/home/runner/work/FIMS/FIMS/vignettes/fims-logging.Rmd\\\", \\n        output_file = \\\"fims-logging.html\\\", output_dir = \\\"/tmp/RtmpCeq3yy/file1c86119bb609/articles\\\", \\n        intermediates_dir = \\\"/tmp/RtmpCeq3yy\\\", encoding = \\\"UTF-8\\\", \\n        seed = 1014L, output_format = list(knitr = list(opts_knit = NULL, \\n            opts_chunk = list(dev = \\\"ragg_png\\\", dpi = 96L, fig.width = 7.29166666666667, \\n                fig.height = 4.50659250103008, fig.retina = 2L, dev.args = list(\\n                    bg = NA), fig.ext = \\\"png\\\", other.parameters = list()), \\n            knit_hooks = NULL, opts_hooks = NULL, opts_template = NULL), \\n            pandoc = list(to = \\\"html\\\", from = \\\"markdown+autolink_bare_uris+tex_math_single_backslash\\\", \\n                args = c(\\\"--standalone\\\", \\\"--section-divs\\\", \\\"--template\\\", \\n                \\\"/tmp/RtmpCeq3yy/pkgdown-rmd-template-1c8654c0a0ce.html\\\", \\n                \\\"--highlight-style\\\", \\\"pygments\\\"), keep_tex = FALSE, \\n                latex_engine = \\\"pdflatex\\\", ext = NULL, convert_fun = NULL, \\n                lua_filters = c(\\\"/home/runner/work/_temp/Library/rmarkdown/rmarkdown/lua/pagebreak.lua\\\", \\n                \\\"/home/runner/work/_temp/Library/rmarkdown/rmarkdown/lua/latex-div.lua\\\"\\n                )), keep_md = FALSE, clean_supporting = FALSE, df_print = \\\"default\\\", \\n            pre_knit = function (...) \\n            {\\n                options(width = width)\\n                if (is.function(old_pre)) {\\n                    old_pre(...)\\n                }\\n            }, post_knit = function (...) \\n            {\\n                op(base(...), overlay(...))\\n            }, pre_processor = function (...) \\n            {\\n                op(base(...), overlay(...))\\n            }, intermediates_generator = function (original_input, \\n                intermediates_dir) \\n            {\\n                copy_render_intermediates(original_input, intermediates_dir, \\n                    !self_contained)\\n            }, post_processor = function (metadata, input_file, output_file, \\n                ...) \\n            {\\n                original_output_file <- output_file\\n                output_file <- overlay(metadata, input_file, output_file, \\n                    ...)\\n                if (!is.null(attr(output_file, \\\"post_process_original\\\"))) \\n                    base(metadata, input_file, original_output_file, \\n                      ...)\\n                base(metadata, input_file, output_file, ...)\\n            }, file_scope = NULL, on_exit = function () \\n            {\\n                if (is.function(base)) \\n                    base()\\n                if (is.function(overlay)) \\n                    overlay()\\n            }), output_options = NULL, quiet = TRUE)), envir = base::quote(<environment>), \\n        quote = base::quote(TRUE))\\n10: base::do.call(base::do.call, base::c(base::readRDS(\\\"/tmp/RtmpCeq3yy/callr-fun-1c864dbc3823\\\"), \\n        base::list(envir = .GlobalEnv, quote = TRUE)), envir = .GlobalEnv, \\n        quote = TRUE)\\n9: base::saveRDS(base::do.call(base::do.call, base::c(base::readRDS(\\\"/tmp/RtmpCeq3yy/callr-fun-1c864dbc3823\\\"), \\n       base::list(envir = .GlobalEnv, quote = TRUE)), envir = .GlobalEnv, \\n       quote = TRUE), file = \\\"/tmp/RtmpCeq3yy/callr-res-1c8669ed55d6\\\", \\n       compress = FALSE)\\n8: base::withCallingHandlers({\\n       NULL\\n       base::saveRDS(base::do.call(base::do.call, base::c(base::readRDS(\\\"/tmp/RtmpCeq3yy/callr-fun-1c864dbc3823\\\"), \\n           base::list(envir = .GlobalEnv, quote = TRUE)), envir = .GlobalEnv, \\n           quote = TRUE), file = \\\"/tmp/RtmpCeq3yy/callr-res-1c8669ed55d6\\\", \\n           compress = FALSE)\\n       base::flush(base::stdout())\\n       base::flush(base::stderr())\\n       NULL\\n       base::invisible()\\n   }, error = function(e) {\\n       {\\n           callr_data <- base::as.environment(\\\"tools:callr\\\")$`__callr_data__`\\n           err <- callr_data$err\\n           if (FALSE) {\\n               base::assign(\\\".Traceback\\\", base::.traceback(4), envir = callr_data)\\n               utils::dump.frames(\\\"__callr_dump__\\\")\\n               base::assign(\\\".Last.dump\\\", .GlobalEnv$`__callr_dump__`, \\n                   envir = callr_data)\\n               base::rm(\\\"__callr_dump__\\\", envir = .GlobalEnv)\\n           }\\n           e <- err$process_call(e)\\n           e2 <- err$new_error(\\\"error in callr subprocess\\\")\\n           class <- base::class\\n           class(e2) <- base::c(\\\"callr_remote_error\\\", class(e2))\\n           e2 <- err$add_trace_back(e2)\\n           cut <- base::which(e2$trace$scope == \\\"global\\\")[1]\\n           if (!base::is.na(cut)) {\\n               e2$trace <- e2$trace[-(1:cut), ]\\n           }\\n           base::saveRDS(base::list(\\\"error\\\", e2, e), file = base::paste0(\\\"/tmp/RtmpCeq3yy/callr-res-1c8669ed55d6\\\", \\n               \\\".error\\\"))\\n       }\\n   }, interrupt = function(e) {\\n       {\\n           callr_data <- base::as.environment(\\\"tools:callr\\\")$`__callr_data__`\\n           err <- callr_data$err\\n           if (FALSE) {\\n               base::assign(\\\".Traceback\\\", base::.traceback(4), envir = callr_data)\\n               utils::dump.frames(\\\"__callr_dump__\\\")\\n               base::assign(\\\".Last.dump\\\", .GlobalEnv$`__callr_dump__`, \\n                   envir = callr_data)\\n               base::rm(\\\"__callr_dump__\\\", envir = .GlobalEnv)\\n           }\\n           e <- err$process_call(e)\\n           e2 <- err$new_error(\\\"error in callr subprocess\\\")\\n           class <- base::class\\n           class(e2) <- base::c(\\\"callr_remote_error\\\", class(e2))\\n           e2 <- err$add_trace_back(e2)\\n           cut <- base::which(e2$trace$scope == \\\"global\\\")[1]\\n           if (!base::is.na(cut)) {\\n               e2$trace <- e2$trace[-(1:cut), ]\\n           }\\n           base::saveRDS(base::list(\\\"error\\\", e2, e), file = base::paste0(\\\"/tmp/RtmpCeq3yy/callr-res-1c8669ed55d6\\\", \\n               \\\".error\\\"))\\n       }\\n   }, callr_message = function(e) {\\n       base::try(base::signalCondition(e))\\n   })\\n7: doTryCatch(return(expr), name, parentenv, handler)\\n6: tryCatchOne(expr, names, parentenv, handlers[[1L]])\\n5: tryCatchList(expr, names[-nh], parentenv, handlers[-nh])\\n4: doTryCatch(return(expr), name, parentenv, handler)\\n3: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), \\n       names[nh], parentenv, handlers[[nh]])\\n2: tryCatchList(expr, classes, parentenv, handlers)\\n1: base::tryCatch(base::withCallingHandlers({\\n       NULL\\n       base::saveRDS(base::do.call(base::do.call, base::c(base::readRDS(\\\"/tmp/RtmpCeq3yy/callr-fun-1c864dbc3823\\\"), \\n           base::list(envir = .GlobalEnv, quote = TRUE)), envir = .GlobalEnv, \\n           quote = TRUE), file = \\\"/tmp/RtmpCeq3yy/callr-res-1c8669ed55d6\\\", \\n           compress = FALSE)\\n       base::flush(base::stdout())\\n       base::flush(base::stderr())\\n       NULL\\n       base::invisible()\\n   }, error = function(e) {\\n       {\\n           callr_data <- base::as.environment(\\\"tools:callr\\\")$`__callr_data__`\\n           err <- callr_data$err\\n           if (FALSE) {\\n               base::assign(\\\".Traceback\\\", base::.traceback(4), envir = callr_data)\\n               utils::dump.frames(\\\"__callr_dump__\\\")\\n               base::assign(\\\".Last.dump\\\", .GlobalEnv$`__callr_dump__`, \\n                   envir = callr_data)\\n               base::rm(\\\"__callr_dump__\\\", envir = .GlobalEnv)\\n           }\\n           e <- err$process_call(e)\\n           e2 <- err$new_error(\\\"error in callr subprocess\\\")\\n           class <- base::class\\n           class(e2) <- base::c(\\\"callr_remote_error\\\", class(e2))\\n           e2 <- err$add_trace_back(e2)\\n           cut <- base::which(e2$trace$scope == \\\"global\\\")[1]\\n           if (!base::is.na(cut)) {\\n               e2$trace <- e2$trace[-(1:cut), ]\\n           }\\n           base::saveRDS(base::list(\\\"error\\\", e2, e), file = base::paste0(\\\"/tmp/RtmpCeq3yy/callr-res-1c8669ed55d6\\\", \\n               \\\".error\\\"))\\n       }\\n   }, interrupt = function(e) {\\n       {\\n           callr_data <- base::as.environment(\\\"tools:callr\\\")$`__callr_data__`\\n           err <- callr_data$err\\n           if (FALSE) {\\n               base::assign(\\\".Traceback\\\", base::.traceback(4), envir = callr_data)\\n               utils::dump.frames(\\\"__callr_dump__\\\")\\n               base::assign(\\\".Last.dump\\\", .GlobalEnv$`__callr_dump__`, \\n                   envir = callr_data)\\n               base::rm(\\\"__callr_dump__\\\", envir = .GlobalEnv)\\n           }\\n           e <- err$process_call(e)\\n           e2 <- err$new_error(\\\"error in callr subprocess\\\")\\n           class <- base::class\\n           class(e2) <- base::c(\\\"callr_remote_error\\\", class(e2))\\n           e2 <- err$add_trace_back(e2)\\n           cut <- base::which(e2$trace$scope == \\\"global\\\")[1]\\n           if (!base::is.na(cut)) {\\n               e2$trace <- e2$trace[-(1:cut), ]\\n           }\\n           base::saveRDS(base::list(\\\"error\\\", e2, e), file = base::paste0(\\\"/tmp/RtmpCeq3yy/callr-res-1c8669ed55d6\\\", \\n               \\\".error\\\"))\\n       }\\n   }, callr_message = function(e) {\\n       base::try(base::signalCondition(e))\\n   }), error = function(e) {\\n       NULL\\n       if (FALSE) {\\n           base::try(base::stop(e))\\n       }\\n       else {\\n           base::invisible()\\n       }\\n   }, interrupt = function(e) {\\n       NULL\\n       if (FALSE) {\\n           e\\n       }\\n       else {\\n           base::invisible()\\n       }\\n   })\\n\",\n\"line\" : \"-1\"\n}\n]"

In the above example of an error level log entry, notice the R stack trace in the routine field. Sometimes this may contain useful information, but most of the time the stack trace is to large to capture the root cause of the error. For that reason, it’s advised to make the error message as detailed as possible when calling log_error(x) from R. Note that the formatting is better when written to a file rather than printed to the screen as is done here.

FIMS Logging Functions in R

There are several exported logging functions in the FIMS packages. To find out more about each of the following functions, use methods::show() to view the documentation.

ls("package:FIMS") |>
  grep(pattern = "_log|log_", value = TRUE) |>
  cli::cli_bullets()
## get_log
## get_log_errors
## get_log_module
## get_log_warnings
## log_error
## log_info
## log_warning
## set_log_throw_on_error
# Get documentation for log_error
methods::show(log_error)
## internal C++ function <0x55f07c7f8bb0>
##     docstring : Adds a error entry to the log from the R environment.
##     signature : void log_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)

Example of FIMS logging from R

Assuming a model has already been defined in the object default_parameters, below is an example of using the logging system with FIMS in R.

fit <- default_parameters |>
  initialize_fims(data = data_4_model) |>
  fit_fims(optimize = TRUE)
##  Starting optimization ...
##  Restarting optimizer 3 times to improve gradient.
##  Maximum gradient went from 0.00298 to 0.00117 after 3 steps.
##  Finished optimization
##  Finished sdreport
##  FIMS model version: 0.3.0.1
##  Total run time was 0.7899 seconds
##  Number of parameters: total=79, fixed_effects=79, and random_effects=0
##  Maximum gradient= 0.00117
##  Negative log likelihood (NLL):
##  Marginal NLL= 2146.10345
##  Total NLL= 2146.10345
##  Terminal SB=
# get the log as a string in JSON format and parse into a list
log_str <- as.character(get_log())
write(log_str, "log.json")
log_json <- jsonlite::fromJSON(log_str)

# get log warnings only
log_warnings_str <- get_log_warnings()

# get log errors only
log_errors_str <- get_log_errors()

# get log entries from the information module
information_log <- get_log_module("information")

Another useful option is set_throw_on_error(). If this option is set to TRUE, FIMS will abort immediately when an error occurs and if write_log(TRUE) was previously called, the log file will be written before FIMS terminates the session. Below is an example of how to throw on error.

set_log_throw_on_error(TRUE)
log_error("throwing now!")