【问题标题】:Log all warnings with futile.logger使用 futile.logger 记录所有警告
【发布时间】:2016-12-01 21:22:02
【问题描述】:

尝试使用futile.logger 记录所有错误和警告。

对这个处理错误有点满意:

library(futile.logger)
options(error = function() { flog.error(geterrmessage()) ; traceback() ; stop() })
log("a")
# Error in log("a") : argument non numérique pour une fonction mathématique
# ERROR [2016-12-01 21:12:07] Error in log("a") : argument non numérique pour une fonction mathématique
# 
# No traceback available 
# Erreur pendant l'emballage (wrapup) : 

有冗余,但我可以轻松地将stderrstdout 和日志文件分开,所以这不是问题。这当然不漂亮,还有一个额外的“总结”错误消息,不知何故由最终的stop() 引起,我不明白,所以我愿意接受建议。

我找不到类似的警告解决方案。我尝试了什么:

options(warn = 1L)
options(warning.expression = expression(flog.warn(last.warning)))
log(- 1)
# [1] NaN

但无济于事。

后续问题:是否有我在不知不觉中忽略的最佳实践?

【问题讨论】:

  • 关于warnings。这有帮助吗?
  • 您可以使用sink 捕获警告。如果你想要一个包含代码、输出和错误/警告的常规日志文件,那么我的建议是 R CMD BATCH 你的文件,这将创建一个 .ROut 文件。
  • 感谢相关指针,但我的问题更具体地说是关于拦截错误和警告以我的方式处理它们(即:使用 futile.logger 记录它们)
  • 您只想记录“未处理”的警告还是记录已处理的警告(与withCallingHandlerstryCatch 一起捕获)?然后就变得很困难了……
  • @Aurèle 我已经写了(一个很长的)答案,这也显示了我个人的最佳实践,即包装原始未更改代码以注入 R 脚本批处理作业的日志记录... HIH

标签: r logging


【解决方案1】:

怎么样:

options(warning.expression = 
        quote({
            if(exists("last.warning",baseenv()) && !is.null(last.warning)){
                txt = paste0(names(last.warning),collapse=" ")
                try(suppressWarnings(flog.warn(txt)))
                cat("Warning message:\n",txt,'\n',sep = "")
            } 
        }))

【讨论】:

  • 谢谢,您让我意识到expression()quote() 之间的混淆(尽管还不是100% 清楚)。另外,您的版本似乎比 options(warning.expression = quote(flog.warn(last.warning))) 更强大
  • 天真的问题:你的回答如何解决@RYoda 的第一条评论:“你只想记录“未处理”的警告还是也记录处理的警告(用 withCallingHandlers 和 tryCatch 捕获)?那么它会变得非常困难……”
  • options(warning.expression=...) 不会处理通过 tryCatch(log(-1),warning=function(w){cat('Hi\n')})withCallingHandlers 等其他方式处理的警告,而且就个人而言,我不想记录那些无论如何 b/c 你永远不会在交互式编码时看到它们。
  • 您是否对此进行了全面测试?在我看来is.null(last.warning) 会出错,除非发出警告,因为该对象根本不存在。应该是exists('last.warning')
  • 好点@RoyalTS。早些时候在 Windows 机器上对此进行了测试(尽管可能在我的 .Rprofile... 中引发了警告),但我收到了您在我的 Mac 上提到的错误(它没有 .Rprofile...)。相应调整...
【解决方案2】:

In 可以提供两个选项来记录 R 条件,例如带有 futile.logger 的警告并捕获所有警告,无论函数调用堆栈有多深:

  1. 使用withCallingHandlers 包装您的代码以获得基本解决方案
  2. 使用我的包 tryCatchLog 获取高级解决方案(出于合规性原因:我是作者)

为了解释解决方案,我创建了一个简单的 R 脚本,它会产生警告和错误:

# Store this using the file name "your_code_file.R"
# This could be your code...

f1 <- function(value) {
  print("f1() called")
  f2(value)                 # call another function to show what happens
  print("f1() returns")
}

f2 <- function(value) {
  print("f2() called")
  a <- log(-1)           # This throws a warning: "NaNs produced"
  print(paste("log(-1) =", a))
  b <- log(value)        # This throws an error if you pass a string as value
  print("f2() returns")
}

f1(1)  # produces a warning
f1("not a number") # produces a warning and an error

“按原样”执行(不记录)此代码会产生以下输出:

[1] "f1() called"
[1] "f2() called"
[1] "log(-1) = NaN"
[1] "f2() returns"
[1] "f1() returns"
[1] "f1() called"
[1] "f2() called"
[1] "log(-1) = NaN"
Error in log(value) : non-numeric argument to mathematical function
Calls: source -> withVisible -> eval -> eval -> f1 -> f2
In addition: Warning messages:
1: In log(-1) : NaNs produced
2: In log(-1) : NaNs produced

解决方案 1 (withCallingHandlers)

创建一个由 R 调用的新 R 文件并获取您的 未更改 (!) 原始 R 脚本:

# Store this using the file name "logging_injector_withCallingHandlers.R"
# Main function to inject logging of warnings without changing your original source code

library(futile.logger)

flog.threshold(INFO)

# Injecting the logging of errors and warnings:
tryCatch(withCallingHandlers({
  source("your_code_file.R")     # call your unchanged code by sourcing it!
}, error = function(e) {
     call.stack <- sys.calls()   # "sys.calls" within "withCallingHandlers" is like a traceback!
     log.message <- e$message
     flog.error(log.message)     # let's ignore the call.stack for now since it blows-up the output
}, warning = function(w) {
     call.stack <- sys.calls()   # "sys.calls" within "withCallingHandlers" is like a traceback!
     log.message <- w$message
     flog.warn(log.message)      # let's ignore the call.stack for now since it blows-up the output
     invokeRestart("muffleWarning")  # avoid warnings to "bubble up" to being printed at the end by the R runtime
})
 , error = function(e) {
  flog.info("Logging injector: The called user code had errors...")
})

如果你执行这个包装代码,R 输出是:

$ Rscript logging_injector_withCallingHandlers.R
NULL
[1] "f1() called"
[1] "f2() called"
WARN [2017-06-08 22:35:53] NaNs produced
[1] "log(-1) = NaN"
[1] "f2() returns"
[1] "f1() returns"
[1] "f1() called"
[1] "f2() called"
WARN [2017-06-08 22:35:53] NaNs produced
[1] "log(-1) = NaN"
ERROR [2017-06-08 22:35:53] non-numeric argument to mathematical function
INFO [2017-06-08 22:35:53] Logging injector: The called user code had errors...

如你所见

  • 警告已记录
  • 也可以输出调用堆栈(我已禁用此功能以避免淹没此答案)

参考:https://stackoverflow.com/a/19446931/4468078

解决方案2 - 包tryCatchLog(我是作者)

方案一有一些缺点,主要是:

  • 堆栈跟踪(“traceback”)不包含文件名和行号
  • 堆栈跟踪中充斥着您不想看到的内部函数调用(相信我,或者使用您的非平凡的 R 脚本尝试它 ;-)

我开发了一个包,将上面的withCallingHandlers逻辑封装在一个函数中,并添加了额外的功能,而不是一次又一次地复制和粘贴上面的代码sn-p

  • 记录错误、警告和消息
  • 通过使用对源文件名和行号的引用记录堆栈跟踪来识别错误和警告的来源
  • 支持post-mortem分析错误后通过创建一个包含全局环境(工作空间)的所有变量和调用的每个函数(通过dump.frames)的转储文件 - 对于您无法在服务器上直接调试的批处理作业非常有用重现错误!

要使用tryCatchLog 包装上述 R 脚本文件,请创建一个包装文件

# Store this using the file name "logging_injector_tryCatchLog.R"
# Main function to inject logging of warnings without changing your original source code

# install.packages("devtools")
# library(devtools)
# install_github("aryoda/tryCatchLog")
library(tryCatchLog)
library(futile.logger)

flog.threshold(INFO)

tryCatchLog({
  source("your_code_file.R")     # call your unchanged code by sourcing it!
  #, dump.errors.to.file = TRUE  # Saves a dump of the workspace and the call stack named dump_<YYYYMMDD_HHMMSS>.rda
})

并通过Rscript 执行它以获得这个(缩短的!)结果:

# $ Rscript -e "options(keep.source = TRUE); source('logging_injector_tryCatchLog.R')" > log.txt
[1] "f1() called"
[1] "f2() called"
WARN [2017-06-08 23:13:31] NaNs produced
Compact call stack:
  1 source("logging_injector_tryCatchLog.R")
  2 logging_injector_tryCatchLog.R#12: tryCatchLog({
  3 logging_injector_tryCatchLog.R#13: source("your_code_file.R")
  4 your_code_file.R#18: f1(1)
  5 your_code_file.R#6: f2(value)
  6 your_code_file.R#12: .signalSimpleWarning("NaNs produced", quote(log(-1)))
Full call stack:
  1 source("logging_injector_tryCatchLog.R")
  2 withVisible(eval(ei, envir))

  ...
<a lot of logging output omitted here...>

正如您在调用堆栈级别 6 中清楚地看到的那样,源代码文件名和行号 (#12) 与引发警告的源代码 sn-p 一起记录为警告源:

6 your_code_file.R#12 .signalSimpleWarning("NaNs generated", quote(log(-1)))

【讨论】:

    【解决方案3】:

    您应该使用 futile.logger 的方式显示在其documentation 中。下面是一个简单的示例,说明它通常是如何使用的,以及如何设置阈值。

    # set log file to write to
    flog.appender (appender.file ("mylog.log"))
    # set log threshold, this ensures only logs at or above this threshold are written.
    flog.threshold(WARN)
    
    flog.info("DOES NOT LOG")
    flog.warn ("Logged!")
    

    【讨论】:

    • 谢谢@calder.ty。我了解文档中示例的 futile.logger 的典型用法。但我的问题更多是关于拦截由其他函数(不是我自己的函数)触发的错误和警告,以便我可以按自己的方式处理它们(即使用 futile.logger 记录它们)。
    猜你喜欢
    • 1970-01-01
    • 2016-12-14
    • 2017-06-30
    • 1970-01-01
    • 1970-01-01
    • 2013-01-27
    • 1970-01-01
    • 2010-11-29
    • 1970-01-01
    相关资源
    最近更新 更多