【问题标题】:Printing stack trace and continuing after error occurs in R打印堆栈跟踪并在 R 中发生错误后继续
【发布时间】:2009-12-29 15:07:14
【问题描述】:

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

【问题讨论】:

    标签: debugging r


    【解决方案1】:

    我大约一周前编写了这段代码,以帮助我追踪主要来自非交互式 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()
    }))
    

    PS:您可能不希望 warn=2(警告转换为错误)

    【讨论】:

    【解决方案2】:

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

    非常感谢Man Group 允许分发这个!还要感谢 Bob Albright,他的回答让我找到了我正在寻找的东西。

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

    要使用它,只需将它包裹在您的代码中:

    withJavaLogging({
      // Your code here...
    })
    

    为了在没有错误的情况下获得更安静的输出(对测试很有用!),请设置 silentSuccess 标志。只有在发生错误时才会输出消息,以提供失败的上下文。

    要达到最初的目标(dump stack trace + carry on),只需使用try:

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

    【讨论】:

    • 让我想起了 Hadley 的 evaluate 包,尽管我很确定它不会进行堆栈跟踪。不过,我还没有看到这里提到它,它肯定对不需要你在此处提供的整个机制的其他人有用。
    • 出色的工作!顺便说一句:通过limitedLabels(c(calls, attr(obj, "calls"))) 附加“呼叫”属性的目的是什么?当我检查attributes(obj) 时,我只找到一个名为“call”的属性(单数!)...
    • @RYoda Weird,这对我有用。再说一次,R 并不是地球上最一致的语言。
    • chrispy:这是一个最出色的解决方案。非常感谢!
    • @chrispy: (......被 5 分钟评论编辑规则烧毁,现在完成......)当我使用你的代码时,我遇到的唯一问题是我得到了一些非常难看的输出我的堆栈跟踪的开始。特别是,第一行是at .handleSimpleError(function (obj),然后是记录器内部函数的前几行。我不想看到,所以我发现我可以通过将trace = trace[length(trace):1] 更改为trace = trace[(length(trace) - 1):1] 来抑制它。我将跟进一个完整版本的新答案。
    【解决方案3】:

    如果对 option(error...) 触发的某些内容感兴趣,您也可以这样做:

    options(error=traceback)
    

    据我所知,它完成了 Bob 建议的解决方案的大部分工作,但具有更短的优势。

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

    【讨论】:

    • 不幸的是,我需要在之后继续,即在 try() 块中运行,所以它不会触发 option(error=...)。
    • 而且它(总是)不工作。这给了我“没有可用的回溯”,Bob 的解决方案给了我一个。
    【解决方案4】:

    你试过了吗

     options(error=recover)
    

    设置? Chambers 的“数据分析软件”对调试有一些有用的提示。

    【讨论】:

    • 我不想要交互式提示,我希望程序打印出堆栈跟踪并继续进行。
    • 您是仅使用 R 代码还是使用其他与 R 相关的语言?
    【解决方案5】:

    这是 Alice 在上面的回答中提出的 withJavaLogging 函数的后续内容。我评论说她的解决方案是鼓舞人心的,但对我来说,在堆栈跟踪开始时出现一些我不想看到的输出。

    为了说明,请考虑以下代码:

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

    如果我执行withJavaLogging( 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
    

    我不想看到at .handleSimpleError(function (obj) 行后面跟着withJavaLogging 函数中定义的记录器函数的源代码。我在上面评论说,我可以通过将 trace = trace[length(trace):1] 更改为 trace = trace[(length(trace) - 1):1] 来抑制不需要的输出

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

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

    如果我执行logFully( f1() ) 行,我会得到我想要的输出,这很简单

    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
    

    【讨论】:

      【解决方案6】:

      没有行号,但这是我目前找到的最接近的:

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

        • 与我使用的大多数其他语言相比复杂,例如在 Java 或 Python 中,从异常中打印堆栈跟踪是一种不费吹灰之力的单线器。
        • 我仍然不明白为什么您所描述的不仅仅是单行。唯一的困难是如果你试图抛出一个特定的异常类型,因为这并不容易得到支持。
        • 也许不是——如果是这样,请张贴你会怎么做! :)
        【解决方案8】:

        我编写了一个类似于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])
        

        我的答案中的更多信息: https://stackoverflow.com/a/40899766/1587132

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-29
          • 2011-05-25
          • 2017-02-19
          • 2015-07-27
          相关资源
          最近更新 更多