15

Suppose I have a bunch of R code in a script and I want to log all the R code which is run from the .GlobalEnv to a flat file or a database together with the errors and warning messages. I could write a simple logme function as follows or make it a bit more complex to also fetch the errors by changing options(error = mylogginfunction)

mylogfile <- tempfile()
logme <- function(x){
  mode <- "at"
  if(!file.exists(mylogfile)){
    mode <- "wt"
  }
  myconn <- file(mylogfile, mode)
  writeLines(x, myconn)
  close(myconn)
  invisible()
}

logme(sprintf("%s: started some yadayada, ", Sys.time()))
x <- 10
x * 7
logme(sprintf("%s: done with yadayada", Sys.time()))

## Get the log
cat(readLines(mylogfile))

The log prints out: 2015-05-14 17:24:31: started some yadayada, 2015-05-14 17:24:31: done with yadayada

But what I would like to have is that the logfile writes down the expressions which were executed without me having to write a wrapper around each statement. I would like the log to look like. 2015-05-14 17:24:31: started some yadayada, x <- 10, x * 7 2015-05-14 17:24:31: done with yadayada

So my question is, how do I fetch what is being executed by R so that I can store the executed expressions in a log/database. And without having to write a function call before each expression (as in myhandler(x <- 10); myhandler(x * 10)). Any help on this?

  • The `R CMD BATCH filename.R` command line produces a `.Rout` file with what seems you are asking. – nicola May 14 '15 at 16:01
  • I'm not interested in the .Rout or what is piped by Rscript myscript > mylog.log. I'm interested in the expressions. –  May 14 '15 at 16:04
  • The `R CMD BATCH` writes also the expressions. Did you try it? – nicola May 14 '15 at 16:07
  • Yes, I have tried R CMD BATCH. But I need the expressions which are run so that I can store them in a logging database 1 by 1 as they are run by the R intepreter. –  May 14 '15 at 16:14
  • I don't think to really get it. However, what about writing another `.R` file that reads line by line your input file, write the command somewhere (through `cat` or saving the expression on a db), execute it (through `eval(parse(text=))`) and goes on to the next line? – nicola May 14 '15 at 16:21
  • Thats a way to go which I also had in mind but my question was more to have the running expressions in the same process available as where the R commands are run. –  May 14 '15 at 16:27
  • Use `sink`? [This answer](http://stackoverflow.com/a/9636800/1412059) mentions some packages that might also be helpful. – Roland May 18 '15 at 12:53
  • How would sink help me to get the R expressions which are run? sink diverts R **output** to a connection. –  May 18 '15 at 21:23
  • 3
    Are you looking for Sweave or knitr then? – Roland May 19 '15 at 07:21
  • Yes, but without having to type Sweave or knit. –  May 20 '15 at 07:53

3 Answers3

4

For catching input commands you could use addTaskCallback

mylogfile <- tempfile()
addTaskCallback(
    function(...) { 
        expr <- deparse(as.expression(...)[[1]]) # it could handled better...
        cat(expr, file=mylogfile, append=TRUE, sep="\n")
        # or cat(sprintf("[%s] %s", Sys.time(), expr),...) if you want timestamps
        TRUE
    }
    ,name="logger"
)

x <- 10
x * 7

removeTaskCallback("logger")

Then result is:

cat(readLines(mylogfile), sep="\n")
... addTaskCallback definition ...
x <- 10
x * 7

But what you get is parsed expression, which means that line

x+1;b<-7;b==2

will be logged as

x + 1
b <- 7
b == 2

In addition:

  • output will not be logged, in particular message or warning shown in console
  • in case of error logging will not be triggered, so you need separate function to handle it
Marek
  • 49,472
  • 15
  • 99
  • 121
0

This is probably to simple to work in every case, but you can try with this:

Define myhandler as:

myhandler <- function(x, file = stdout()) {
  expr <- substitute(x)
  for(e_line in as.list(expr)) {
    cat( file = file, as.character(Sys.time()), capture.output(e_line), "\n")
    eval(e_line, envir = parent.frame())
  }
}

Use it with your code inside the brackets:

myhandler({

  a <- 1
  a <- a + 1
  print(a)

})

Result:

# 2015-05-14 18:46:34 `{` 
# 2015-05-14 18:46:34 a <- 1 
# 2015-05-14 18:46:34 a <- a + 1 
# 2015-05-14 18:46:34 print(a) 
# [1] 2
bergant
  • 7,122
  • 1
  • 20
  • 24
  • Thank you very much for your answer. It's close to what I had in mind, but I tried to exclude this answer explicitely in my question where it states 'without having to write a function call before each expression'. And this solution just puts all the expressions in 1 block. I'm looking for a solution where I don't need the myhandler before all the code. –  May 14 '15 at 17:04
0

I confess that I don't really get what "to have the running expressions in the same process available as where the R commands are run" means when we chatted a bit in the comments. However, I expanded what I had in mind. You can create a logGenerator.R file with the following lines:

logGenerator<-function(sourcefile,log) {
  ..zz <- file(log, open = "at")
  sink(..zz)
  sink(..zz, type = "message")
  on.exit({
    sink(type="message")
    sink()
    close(..zz)
  })
  ..x<-parse(sourcefile)
  for (..i in 1:length(..x)) {
    cat(as.character(Sys.time()),"\n")
    cat(as.character(..x[..i]),"\n")
    ..y<-eval(..x[..i])
  }
}

This function takes as arguments the source file and the log file names. This script will take an R file and will log the time at which each instruction is executed. Then it records the expression on the same log file. Every output directed to the stdout() and the error messages are directed to the log file. You obviously don't have to modify in any way your source file.

nicola
  • 24,005
  • 3
  • 35
  • 56
  • Nicola, thanks for the answer. It's the same as given by bergant basically. I'm looking for a solution where I don't need to pass a sourcefile. Is this possible? –  May 20 '15 at 08:09
  • How do you think you pass the expressions you want to evaluate and store if not in a sourcefile? Sorry for the answer, but I don't think I really get your constraints and what is your workflow. – nicola May 20 '15 at 09:00
  • The workflow is `logme(sprintf("%s: started some yadayada, ", Sys.time())) x <- 10 x * 7 logme(sprintf("%s: done with yadayada", Sys.time())) ` Which should result a log file stating '2015-05-14 17:24:31: started some yadayada, x <- 10, x * 7 2015-05-14 17:24:31: done with yadayada' –  May 20 '15 at 09:53
  • I'm not being clear. What do you mean by "I don't need to pass a sourcefile"? I guess my function produces the output you are asking. – nicola May 20 '15 at 09:56
  • I'm looking for a solution where I don't have to write logGenerator('myRscript.R', 'mylog.log') but only `logme("mylog.log") and next logme(sprintf("%s: started some yadayada, ", Sys.time())) x <- 10 x * 7 logme(sprintf("%s: done with yadayada", Sys.time()))` which generates the desired output of the question. So no wrapper around my code. –  May 20 '15 at 10:10