3

I want to create a custom log function, that would get used in other functions. I am having issues with the custom function where arguments don't seem to flow through to the inner log function. My custom log function is inspired by the logger package but I am planning to expand this usage a bit further (so logger doesn't quite meet my needs)

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  
  print(
    glue::glue("[{type} {Sys.time()}] ", ...)
  )
  
}

Next I am planning to use log_fc in various other custom functions, one example:

test_fc <- function(forecast) {

  log_fc(type = "INFO", "{forecast} is here")
  
  #print(forecast)
}

If I test this, I get the following error:

> test_fc(forecast = "d")
 Error in eval(parse(text = text, keep.source = FALSE), envir) : 
object 'forecast' not found

I am not sure why argument forecast is not being picked up by the inner test_fc function. TIA

sactyr
  • 172
  • 1
  • 1
  • 14

4 Answers4

2

You could use the .envir argument:

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  env <- new.env(parent=parent.frame())
  assign("type",type,env)
  print(
    glue::glue("[{type} {Sys.time()}] ", ...,.envir = env)
  )
}


  
test_fc <- function(forecast) {
    
    log_fc(type = "INFO", "{forecast} is here")
    
}

  
test_fc("My forecast")
#> [INFO 2022-12-18 12:44:11] My forecast is here
Waldi
  • 39,242
  • 6
  • 30
  • 78
2

There are two things going on.

First, the name forecast is never passed to log_fc. The paste solution never needs the name, it just needs the value, so it still works. You'd need something like

log_fc(type = "INFO", "{forecast} is here", forecast = forecast)

to get the name into log_fc.

The second issue is more complicated. It's a design decision in many tidyverse functions. They want to be able to have code like f(x = 3, y = x + 1) where the x in the second argument gets the value that was bound to it in the first argument.

Standard R evaluation rules would not do that; they would look for x in the environment where f was called, so f(y = x + 1, x = 3) would bind the same values in the function as putting the arguments in the other order.

The tidyverse implementation of this non-standard evaluation messes up R's internal handling of .... The workaround (described here: https://github.com/tidyverse/glue/issues/231) is to tell glue() to evaluate the arguments in a particular location. You need to change your log function to fix this.

One possible change is shown below. I think @Waldi's change is actually better, but I'll leave this one to show a different approach.

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  # Get all the arguments from ...
  args <- list(...)
  
  # The unnamed ones are messages, the named ones are substitutions
  named <- which(names(args) != "")
  
  # Put the named ones in their own environment
  e <- list2env(args[named])
  
  # Evaluate the substitutions in the new env
  print(
    glue::glue("[{type} {Sys.time()}] ", ..., .envir = e)
  )
}

test_fc <- function(forecast) {
  
  log_fc(type = "INFO", "{forecast} is here", forecast = forecast)

  }


test_fc(forecast = "d")
#> [INFO 2022-12-18 06:25:29] d is here

Created on 2022-12-18 with reprex v2.0.2

user2554330
  • 37,248
  • 4
  • 43
  • 90
  • user2554330 thank you, I have gone with @Waldi 's answer but your comment has given me plenty of insight – sactyr Dec 30 '22 at 11:10
0

The reason for this is that when your test_fc function connects to the log_fc function, the forecats variable wouldn't be able to be found, because it's not a global function; thus, you can't access it from the other function.

The way to fix this is by defining a global variable:

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  
  print(
    glue::glue("[{type} {Sys.time()}] ", ...)
  )
  
}

test_fc <- function(forecast) {

  forecast <<- forecast
  log_fc(type = "INFO", "{forecast} is here")
 
}

print(test_fc(forecast = "d"))

Output:

d is here
U13-Forward
  • 69,221
  • 14
  • 89
  • 114
  • That does seem to work when you define a global variable, but could I please know why with paste (instead of glue) we don't need a global variable? i.e. ``` log_fc_print <- function(type = c("INFO", "ERROR"), ...) { print( paste("[", type, Sys.time(), "]", ...) ) } test_fc_print <- function(forecast) { log_fc_print(type = "INFO", paste(forecast, "is here")) } ``` – sactyr Dec 17 '22 at 00:50
0

Since you're already using glue you could use another glue::glue in test_fc to accomplish the pass-through, such as:

log_fc <- function(type = c("INFO", "ERROR"), ...) {
  print(
    glue::glue("[{type} {Sys.time()}] ", ...)
  )
}

test_fc <- function(forecast) {
  log_fc(type = "INFO", glue::glue("{forecast} is here"))
}

which yields

> test_fc('arctic blast')
[INFO 2022-12-21 15:56:18] arctic blast is here
>
mrbcuda
  • 580
  • 7
  • 16