获取运行R代码时运行的所有R代码

15

假设我有一堆 R 代码在脚本中,并且我想记录从 .GlobalEnv 运行的所有 R 代码以及错误和警告消息到一个平面文件或数据库中。我可以编写一个简单的 logme 函数,如下所示,或者通过更改 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))

日志输出为: 2015-05-14 17:24:31:开始一些yadayada,2015-05-14 17:24:31:完成yadayada

但我想要的是日志文件记录执行的表达式,而不必在每个语句周围编写包装器。 我希望日志看起来像这样。 2015-05-14 17:24:31:开始一些yadayada, x < -10,x * 7 2015-05-14 17:24:31:完成yadayada

所以我的问题是,如何获取R正在执行的内容,以便可以将执行的表达式存储在日志/数据库中,而无需在每个表达式之前编写函数调用(例如myhandler(x < -10); myhandler(x * 10))。 对此有什么帮助吗?


是的,我已经尝试过R CMD BATCH。但是我需要运行的表达式,以便我可以将它们逐个存储在日志记录数据库中,就像它们被R解释器运行一样。 - user1600826
我不认为真正理解了。不过,写另一个 .R 文件如何?该文件逐行读取输入文件,在某处编写命令(通过 cat 或将表达式保存在数据库中),执行它(通过 eval(parse(text=)))并继续下一行呢? - nicola
使用 sink这个答案提到了一些可能有用的包。 - Roland
sink如何帮助我获取运行的R表达式?sink将R的输出重定向到一个连接。 - user1600826
3
你是在找Sweave还是knitr呢? - Roland
显示剩余5条评论
3个回答

4

你可以使用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")

然后的结果是:
cat(readLines(mylogfile), sep="\n")
... addTaskCallback definition ...
x <- 10
x * 7

但是你得到的是解析后的表达式,这意味着那行代码
x+1;b<-7;b==2

将被记录为

x + 1
b <- 7
b == 2

此外:
  • 输出不会被记录,尤其是在控制台中显示的 messagewarning
  • 如果出现 error ,则不会触发日志记录,因此您需要单独的函数来处理它

0

这可能过于简单,无法适用于所有情况,但您可以尝试使用以下方法:

将myhandler定义为:

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())
  }
}

将其与括号内的代码一起使用:

myhandler({

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

})

结果:

# 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

非常感谢您的回答。它接近我所想要的,但是我在问题中明确排除了这个答案,其中指出“不需要在每个表达式之前编写函数调用”。而这个解决方案只是将所有表达式放在一个块中。我正在寻找一种解决方案,在所有代码之前都不需要myhandler。 - user1600826

0

我承认在我们在评论中聊天时,我并不真正理解“使运行表达式与运行R命令的进程相同”是什么意思。然而,我扩展了我的想法。您可以创建一个logGenerator.R文件,并添加以下行:

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])
  }
}

此函数的参数为源文件和日志文件名。该脚本将获取一个R文件,并记录每个指令执行的时间,然后在同一日志文件中记录表达式。所有输出都指向stdout(),错误消息则指向日志文件。您显然不需要以任何方式修改源文件。


Nicola,感谢您的回答。基本上与bergant给出的答案相同。我正在寻找一种无需传递源文件的解决方案。这可行吗? - user1600826
如果不是在源文件中,您认为如何传递要评估和存储的表达式?抱歉回答不好,但我不认为我真正理解您的限制和工作流程。 - nicola
工作流程是logme(sprintf("%s: 开始一些yadayada,", Sys.time())) x <- 10 x * 7 logme(sprintf("%s: 完成yadayada", Sys.time()))这应该会生成一个日志文件,其中包含“2015-05-14 17:24:31: 开始一些yadayada,x <- 10,x * 7 2015-05-14 17:24:31: 完成yadayada”的声明。 - user1600826
我表达不清楚。你说的“我不需要传递源文件”是什么意思?我猜我的函数可以产生你所要求的输出。 - nicola
我正在寻找一种解决方案,不需要编写 logGenerator('myRscript.R', 'mylog.log'),而只需要使用 logme("mylog.log") 和下一个 logme(sprintf("%s: started some yadayada, ", Sys.time())) 即可生成所需的输出。因此,不需要在我的代码周围添加包装器。 - user1600826

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接