代码之家  ›  专栏  ›  技术社区  ›  Alice Purcell

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

  •  21
  • Alice Purcell  · 技术社区  · 15 年前

    我正在写一些R代码,调用其他可能失败的代码。如果是这样,我想打印一个堆栈跟踪(以跟踪出了什么问题),然后继续不管。但是,traceback()函数只提供有关未捕获异常的信息。我可以通过一个相当复杂、自然的结构(包括trycatch和dump.frames)得到我想要的结果,但是是否没有一种更简单的方法来做到这一点?

    8 回复  |  直到 6 年前
        1
  •  19
  •   flying sheep    6 年前

    我大约一周前编写了这段代码,以帮助我跟踪主要来自非交互式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
  •  10
  •   Alice Purcell    10 年前

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

    多谢 Man Group 获取分发此文件的权限!同时也要感谢鲍勃·奥尔布赖特,他的回答让我比我正在寻找的更具说服力。

    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标志。只有在发生错误时才会输出消息,以便为失败提供上下文。

    要实现原始目标(转储堆栈跟踪+继续),只需使用尝试:

    try(withJavaLogging({
      // Your code here...
    }, stopIsFatal=FALSE))
    
        3
  •  8
  •   Christian Hudon    13 年前

    如果对触发选项(错误…)感兴趣,也可以这样做:

    options(error=traceback)
    

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

    (可根据需要自由组合keep.source=true、warn=2等。)

        4
  •  5
  •   Dirk is no longer here    15 年前

    你试过了吗

     options(error=recover)
    

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

        5
  •  1
  •   user1133275    10 年前

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

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

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

        7
  •  0
  •   HaroldFinch    8 年前

    这是对上面@chrispy的回答的后续,他在这里提出了一个 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) 第行,后跟在中定义的记录器函数的源代码 使用JavaScript 功能。我在上面评论说,我可以通过改变 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
    
        8
  •  -1
  •   Community CDub    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