AW2
AW2

Reputation: 83

R: Errorhandling with tryCatchLog - create a customizable result code for the console and write a detailled traceback to log file

I have code that includes several initial checks of different parameter values. The code is part of a larger project involving several R scripts as well as calls from other environments. If a parameter value does not pass one of the checks, I want to

  1. Generate a customizable result code
  2. Skip the remaining code (which is not going to work anyhow if the parameters are wrong)
  3. Create a log entry with the line where the error was thrown (which tells me which test was not passed by the parameters)
  4. Print my customizable result code to the console (without a more detailed explanation / trace back from the error)

Otherwise, the remaining code should be run. If there are other errors (not thrown by me), I also need an error handling resulting in a customizable general result code (signalling that there was an error, but that it was not one thrown by me) and a more detailled log.

The result codes are part of the communication with a larger environment and just distinguishes between wrong parameter values (i.e., errors thrown by me) and other internal problems (that might occur later in the script).

I would like to use tryCatchLog because it allows me to log a detailed traceback including the script name (I am sourcing my own code) and the line number. I have not figured out, however, how to generate my own error code (currently I am doing this via the base function stop()) and pass this along using tryCatchLog (while also writing a log).

Example

In the following example, my parameter_check() throws an error via stop() with my result code "400". Using tryCatchLog I can catch the error and get a detailed error message including a traceback. However, I want to seperate my own error code (just "400"), which should be printed to the console, and a more detailed error message, which should go to a log file.

library(tryCatchLog)

parameter_check <- function(error) {
  if (error){
    stop("400")
    print("This line should not appear")
  }
}

print("Beginning")
tryCatchLog(parameter_check(error = TRUE),
            error = function(e) {print(e)}
            )

print("End")

Currently, the result is:

[1] "Beginn" ERROR [2021-12-08 11:43:38] 400 Compact call stack: 1 tryCatchLog(parameter_check(0), error = function(e) { 2 #3: stop("400") Full call stack: 1 tryCatchLog(parameter_check(0), error = function(e) { print(e) 2 tryCatch(withCallingHandlers(expr, condition =
cond.handler), ..., finall 3 tryCatchList(expr, classes, parentenv, handlers) 4 tryCatchOne(expr, names, parentenv, handlers[[1]]) 5 doTryCatch(return(expr), name, parentenv, handler) 6 withCallingHandlers(expr, condition = cond.handler) 7 parameter_check(0) 8 #3: stop("400") 9 .handleSimpleError(function (c) { if (inherits(c, "condition") <simpleError in parameter_check(0): 400>

I would like to get my own result code ("400") so that I can print it to the console while logging the complete error message in a file. Is there a way of doing it without writing code parsing the error message, etc.?

Solution with tryCatch

Based on the hint by R Yoda and this answers this is a solution with tryCatch and calling handlers.

### Parameters
log_file_location <- "./logs/log.txt"


### Defining functions
parameter_check_1 <- function(error) {
  if (error){
    stop("400")
  }
}

parameter_check_2 <- function(error) {
  if (error){
    stop("400")
  }
}

write_to_log <- function(file_location, message) {
  if (file.exists(file_location)) 
      {write(message, file_location, append = TRUE)}
      else 
      {write(message, file_location, append = FALSE)}
  
  
}
parameter_check <- function(){
  print("Beginning of parameter check")
  print("First check")
  parameter_check_1(error = TRUE)
  print("Second check")
  parameter_check_2(error = FALSE)
  print("End of parameter check")
  
}

main<- function() {
  print("Beginning of main function")
  log(-1) # throws warning
  log("error") # throws error
  print("End of main function")
}


### Setting parameters
result_code_no_error <- "200"
result_code_bad_request <- "400"
result_code_internal_error <- "500"

# initial value for result_code
result_code <- result_code_no_error


print("Beginning of program")

### Execute parameter check with tryCatch and calling handlers
# Error in parameter checking functions should result in result_code_bad_request
tryCatch(withCallingHandlers(parameter_check(), 
                             error = function(condition){},
                             warning = function(condition){
                                         write_to_log(log_file_location, condition$message)
                                         invokeRestart("muffleWarning")
                                         }
                             ),
         error = function(condition) { 
                    write_to_log(log_file_location, condition$message) 
                    result_code <<- result_code_bad_request
                   }
         )

### Execute main section with tryCatch and calling handlers
# Error in main section  should result in result_code_internal_error
# main section should only be excecuted if there is no error (internal or bad request) in the previous section

if (result_code == result_code_no_error) {
  tryCatch(withCallingHandlers(main(), 
                               error = function(condition){},
                               warning = function(condition){
                                           write_to_log(log_file_location, condition$message)
                                           invokeRestart("muffleWarning")
                                           }
                               ),
          error = function(condition) { 
                      write_to_log(log_file_location, condition$message) 
                      result_code <<- result_code_internal_error
                     }
          )
}


print("End of program")
print(result_code)

As explained in the vignette for tryCatchLog this has the disadvantage of not logging the precise location of the error. I am not passing on the error message from stop("400"), because all parameter checking functions are in one function call now, but this could be done using condition$message.

Upvotes: 1

Views: 491

Answers (1)

R Yoda
R Yoda

Reputation: 8750

The solution is (totally independent of using tryCatchLog or standard R tryCatch):

...
error = function(e) {print(e$message)}
..

Background (how R errors work): They create an object of type (error) condition:

e <- simpleError("400") # same "condition" object as created by stop("400")
str(e)
# List of 2
# $ message: chr "400"
# $ call   : NULL
# - attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
print(e$message)
[1] "400"

Upvotes: 2

Related Questions