Debugging 在R中发生错误后打印堆栈跟踪并继续

Debugging 在R中发生错误后打印堆栈跟踪并继续,debugging,r,Debugging,R,我正在写一些R代码,调用其他可能失败的代码。如果是这样的话,我想打印一个堆栈跟踪(以跟踪出错的地方),然后继续。但是,traceback()函数只提供有关未捕获异常的信息。我可以通过涉及tryCatch和dump.frames的相当复杂、简洁的构造来获得我想要的结果,但是没有更简单的方法来做到这一点吗?我认为您需要使用tryCatch()。你可以在tryCatch()函数中做任何你想做的事情,所以我不清楚你为什么认为这很复杂。也许发布您的代码示例?您是否尝试过 options(error=re

我正在写一些R代码,调用其他可能失败的代码。如果是这样的话,我想打印一个堆栈跟踪(以跟踪出错的地方),然后继续。但是,traceback()函数只提供有关未捕获异常的信息。我可以通过涉及tryCatch和dump.frames的相当复杂、简洁的构造来获得我想要的结果,但是没有更简单的方法来做到这一点吗?

我认为您需要使用
tryCatch()
。你可以在tryCatch()函数中做任何你想做的事情,所以我不清楚你为什么认为这很复杂。也许发布您的代码示例?

您是否尝试过

 options(error=recover)

背景?钱伯斯的“数据分析软件”有一些关于调试的有用提示。

我大约一周前写了这段代码,以帮助我追踪主要来自非交互式R会话的错误。它仍然有点粗糙,但它会打印堆栈跟踪并继续。让我知道如果这是有用的,我很感兴趣的是你将如何使这更具信息性。我也愿意用更干净的方法来获取这些信息

options(warn = 2, keep.source = TRUE, error = quote({
  # Debugging in R
  #   http://www.stats.uwo.ca/faculty/murdoch/software/debuggingR/index.shtml
  #
  # Post-mortem debugging
  #   http://www.stats.uwo.ca/faculty/murdoch/software/debuggingR/pmd.shtml
  #
  # Relation functions:
  #   dump.frames
  #   recover
  # >>limitedLabels  (formatting of the dump with source/line numbers)
  #   sys.frame (and associated)
  #   traceback
  #   geterrmessage
  #
  # Output based on the debugger function definition.

  # TODO: setup option for dumping to a file (?)
  # Set `to.file` argument to write this to a file for post-mortem debugging    
  dump.frames()  # writes to last.dump
  n <- length(last.dump)
  if (n > 0) {
    calls <- names(last.dump)
    cat("Environment:\n", file = stderr())
    cat(paste0("  ", seq_len(n), ": ", calls), sep = "\n", file = stderr())
    cat("\n", file = stderr())
  }

  if (!interactive()) q()
}))
选项(warn=2,keep.source=TRUE,error=quote({
#R中的调试
#   http://www.stats.uwo.ca/faculty/murdoch/software/debuggingR/index.shtml
#
#事后调试
#   http://www.stats.uwo.ca/faculty/murdoch/software/debuggingR/pmd.shtml
#
#关系函数:
#转储帧
#恢复
#>>limitedLabels(使用源/行号格式化转储)
#sys.frame(和相关的)
#回溯
#geterrmessage
#
#基于调试器函数定义的输出。
#TODO:转储到文件的设置选项(?)
#设置`to.file`参数将其写入文件以进行后期调试
dump.frames()#写入last.dump
n(0){

调用如果对选项(错误…)触发的内容感兴趣,您也可以执行以下操作:

options(error=traceback)
据我所知,它实现了Bob建议的解决方案的大部分功能,但它的优点是更短


(根据需要,可以随意与keep.source=TRUE、warn=2等组合。)

我最终编写了一个通用记录器,当调用标准的R“message”、“warning”和“stop”方法时,它会生成类似Java的日志消息。它包括时间戳和警告及以上的堆栈跟踪

非常感谢您允许我发布这篇文章!也感谢鲍勃·奥尔布赖特,他的回答让我更了解我所寻找的东西

withJavaLogging = function(expr, silentSuccess=FALSE, stopIsFatal=TRUE) {
    hasFailed = FALSE
    messages = list()
    warnings = list()
    logger = function(obj) {
        # Change behaviour based on type of message
        level = sapply(class(obj), switch, debug="DEBUG", message="INFO", warning="WARN", caughtError = "ERROR",
                error=if (stopIsFatal) "FATAL" else "ERROR", "")
        level = c(level[level != ""], "ERROR")[1]
        simpleMessage = switch(level, DEBUG=,INFO=TRUE, FALSE)
        quashable = switch(level, DEBUG=,INFO=,WARN=TRUE, FALSE)

        # Format message
        time  = format(Sys.time(), "%Y-%m-%d %H:%M:%OS3")
        txt   = conditionMessage(obj)
        if (!simpleMessage) txt = paste(txt, "\n", sep="")
        msg = paste(time, level, txt, sep=" ")
        calls = sys.calls()
        calls = calls[1:length(calls)-1]
        trace = limitedLabels(c(calls, attr(obj, "calls")))
        if (!simpleMessage && length(trace) > 0) {
            trace = trace[length(trace):1]
            msg = paste(msg, "  ", paste("at", trace, collapse="\n  "), "\n", sep="")
        }

        # Output message
        if (silentSuccess && !hasFailed && quashable) {
            messages <<- append(messages, msg)
            if (level == "WARN") warnings <<- append(warnings, msg)
        } else {
            if (silentSuccess && !hasFailed) {
                cat(paste(messages, collapse=""))
                hasFailed <<- TRUE
            }
            cat(msg)
        }

        # Muffle any redundant output of the same message
        optionalRestart = function(r) { res = findRestart(r); if (!is.null(res)) invokeRestart(res) }
        optionalRestart("muffleMessage")
        optionalRestart("muffleWarning")
    }
    vexpr = withCallingHandlers(withVisible(expr),
            debug=logger, message=logger, warning=logger, caughtError=logger, error=logger)
    if (silentSuccess && !hasFailed) {
        cat(paste(warnings, collapse=""))
    }
    if (vexpr$visible) vexpr$value else invisible(vexpr$value)
}
要在没有错误的情况下获得更安静的输出(对测试很有用!),请设置silentSuccess标志。只有出现错误时才会输出消息,以提供失败的上下文

要实现最初的目标(转储堆栈跟踪+继续),只需使用try:

try(withJavaLogging({
  // Your code here...
}, stopIsFatal=FALSE))

没有行号,但这是迄今为止我发现的最接近的行号:

run = function() {
    // Your code here...
}
withCallingHandlers(run(), error=function(e)cat(conditionMessage(e), sapply(sys.calls(),function(sc)deparse(sc)[1]), sep="\n   ")) 

我编写了一个类似于
try
的解决方案,只是它还返回调用堆栈

tryStack <- function(
expr,
silent=FALSE
)
{
tryenv <- new.env()
out <- try(withCallingHandlers(expr, error=function(e)
  {
  stack <- sys.calls()
  stack <- stack[-(2:7)]
  stack <- head(stack, -2)
  stack <- sapply(stack, deparse)
  if(!silent && isTRUE(getOption("show.error.messages"))) 
    cat("This is the error stack: ", stack, sep="\n")
  assign("stackmsg", value=paste(stack,collapse="\n"), envir=tryenv)
  }), silent=silent)
if(inherits(out, "try-error")) out[2] <- tryenv$stackmsg
out
}

lower <- function(a) a+10
upper <- function(b) {plot(b, main=b) ; lower(b) }

d <- tryStack(upper(4))
d <- tryStack(upper("4"))
cat(d[2])

tryStack这是@chrispy在上面的回答的后续内容,他在回答中介绍了一个
with javalogging
函数。我评论说他的解决方案很有启发性,但对我来说,堆栈跟踪开始时的一些输出破坏了我不想看到的结果

说明,请考虑此代码:

f1 = function() {
        # line #2 of the function definition; add this line to confirm that the stack trace line number for this function is line #3 below
        catA("f2 = ", f2(), "\n", sep = "")
    }

    f2 = function() {
        # line #2 of the function definition; add this line to confirm that the stack trace line number for this function is line #4 below
        # line #3 of the function definition; add this line to confirm that the stack trace line number for this function is line #4 below
        stop("f2 always causes an error for testing purposes")
    }
如果我使用javalogging(f1())
执行行
,我将得到输出

2017-02-17 17:58:29.556 FATAL f2 always causes an error for testing purposes
      at .handleSimpleError(function (obj) 
    {
        level = sapply(class(obj), switch, debug = "DEBUG", message = "INFO", warning = "WARN", caughtError = "ERROR", error = if (stopIsFatal) 
            "FATAL"
        else "ERROR", "")
        level = c(level[level != ""], "ERROR")[1]
        simpleMessage = switch(level, DEBUG = , INFO = TRUE
      at #4: stop("f2 always causes an error for testing purposes")
      at f2()
      at catA.R#8: cat(...)
      at #3: catA("f2 = ", f2(), "\n", sep = "")
      at f1()
      at withVisible(expr)
      at #43: withCallingHandlers(withVisible(expr), debug = logger, message = logger, warning = logger, caughtError = logger, error = logger)
      at withJavaLogging(f1())
    Error in f2() : f2 always causes an error for testing purposes
我不想看到.handleSimpleError(function(obj)
行中的
后跟
withJavaLogging
函数中定义的记录器函数的源代码。我在上面评论说,我可以通过将
trace=trace[length(trace):1]
更改为
trace=trace[(length(trace))来抑制不希望的输出-1):1]

为了方便阅读本文的其他人,下面是我现在使用的函数的完整版本(从withJavaLogging重命名为logFully,并稍微重新格式化以适应我的可读性偏好):


与我使用的大多数其他语言(如Java或Python)相比,它非常复杂,在Java或Python中,从异常中打印堆栈跟踪是一个没有大脑的单行程序。我仍然不明白为什么您所描述的内容会比一行程序多得多。唯一的困难是,如果您试图抛出特定的异常类型,因为这不容易得到支持。也许这不是——如果是这样,请发布你将如何做!:)我不想要一个交互式提示,我希望程序打印出堆栈跟踪并继续运行。你是只使用R代码还是同时使用其他粘在R上的语言?我喜欢这样。为了让它更具信息性,你可以调用
ls.str()
对于
last.dump
中的每个环境(这可能会使输出相当长)不完全是我想要的,但它至少解决了打印堆栈跟踪的问题。谢谢!鼓舞人心的回答,谢谢!但我发现一些函数参数没有保存。你知道该怎么做吗?这让我想起了Hadley的
evaluate
包,尽管我很确定它不会进行堆栈跟踪。我在这里没有看到它然而,它肯定会对不需要您在这里提供的整个机制的其他人有用。出色的工作!顺便问一下:通过
limitedLabels(c(calls,attr(obj,calls)))
添加“calls”属性的目的是什么?当我检查
属性(obj)
时,我只找到一个名为“call”的属性(单数!)…@RYoda怪异,这在我写这本书的时候对我起了作用。再说一次,R不是这个星球上最一致的语言。chrispy:这是一个非常出色的解决方案。非常感谢!@chrispy:(…被5分钟的评论编辑规则烧坏了,现在完成…)当我使用你的代码时,我遇到的唯一问题是,我在堆栈跟踪的开头得到了一些非常糟糕的输出。特别是,第一行是
at.handleSimpleError(function(obj)
,后面是你的logger内部函数的前几行。我不想看到这一点,所以我发现我可以通过chan来抑制它
2017-02-17 17:58:29.556 FATAL f2 always causes an error for testing purposes
      at .handleSimpleError(function (obj) 
    {
        level = sapply(class(obj), switch, debug = "DEBUG", message = "INFO", warning = "WARN", caughtError = "ERROR", error = if (stopIsFatal) 
            "FATAL"
        else "ERROR", "")
        level = c(level[level != ""], "ERROR")[1]
        simpleMessage = switch(level, DEBUG = , INFO = TRUE
      at #4: stop("f2 always causes an error for testing purposes")
      at f2()
      at catA.R#8: cat(...)
      at #3: catA("f2 = ", f2(), "\n", sep = "")
      at f1()
      at withVisible(expr)
      at #43: withCallingHandlers(withVisible(expr), debug = logger, message = logger, warning = logger, caughtError = logger, error = logger)
      at withJavaLogging(f1())
    Error in f2() : f2 always causes an error for testing purposes
logFully = function(expr, silentSuccess = FALSE, stopIsFatal = TRUE) {
    hasFailed = FALSE
    messages = list()
    warnings = list()

    logger = function(obj) {
        # Change behaviour based on type of message
        level = sapply(
            class(obj),
            switch,
            debug = "DEBUG",
            message = "INFO",
            warning = "WARN",
            caughtError = "ERROR",
            error = if (stopIsFatal) "FATAL" else "ERROR",
            ""
        )
        level = c(level[level != ""], "ERROR")[1]
        simpleMessage = switch(level, DEBUG = TRUE, INFO = TRUE, FALSE)
        quashable = switch(level, DEBUG = TRUE, INFO = TRUE, WARN = TRUE, FALSE)

        # Format message
        time = format(Sys.time(), "%Y-%m-%d %H:%M:%OS3")
        txt = conditionMessage(obj)
        if (!simpleMessage) txt = paste(txt, "\n", sep = "")
        msg = paste(time, level, txt, sep = " ")
        calls = sys.calls()
        calls = calls[1:length(calls) - 1]
        trace = limitedLabels(c(calls, attr(obj, "calls")))
        if (!simpleMessage && length(trace) > 0) {
            trace = trace[(length(trace) - 1):1]
            msg = paste(msg, "  ", paste("at", trace, collapse = "\n  "), "\n", sep = "")
        }

        # Output message
        if (silentSuccess && !hasFailed && quashable) {
            messages <<- append(messages, msg)
            if (level == "WARN") warnings <<- append(warnings, msg)
        } else {
            if (silentSuccess && !hasFailed) {
                cat(paste(messages, collapse = ""))
                hasFailed <<- TRUE
            }
            cat(msg)
        }

        # Muffle any redundant output of the same message
        optionalRestart = function(r) { res = findRestart(r); if (!is.null(res)) invokeRestart(res) }
        optionalRestart("muffleMessage")
        optionalRestart("muffleWarning")
    }

    vexpr = withCallingHandlers( withVisible(expr), debug = logger, message = logger, warning = logger, caughtError = logger, error = logger )

    if (silentSuccess && !hasFailed) {
        cat(paste(warnings, collapse = ""))
    }

    if (vexpr$visible) vexpr$value else invisible(vexpr$value)
}
2017-02-17 18:05:05.778 FATAL f2 always causes an error for testing purposes
  at #4: stop("f2 always causes an error for testing purposes")
  at f2()
  at catA.R#8: cat(...)
  at #3: catA("f2 = ", f2(), "\n", sep = "")
  at f1()
  at withVisible(expr)
  at logFully.R#110: withCallingHandlers(withVisible(expr), debug = logger, message = logger, warning = logger, caughtError = logger, error = logger)
  at logFully(f1())
Error in f2() : f2 always causes an error for testing purposes