Я пишу код R, который вызывает другой код, который может завершиться неудачей. Если это так, я хочу напечатать трассировку стека (чтобы отследить, что пошло не так), а затем продолжить независимо. Однако функция traceback() предоставляет информацию о неперехваченных исключениях. Я могу получить результат, который я хочу, с помощью довольно сложной, насыщенной конструкции с участием tryCatch и dump.frames, но нет ли более простого способа сделать это?
Печать трассировки стека и продолжение после ошибки в R
Ответ 1
Я закончил тем, что написал журнал общего назначения, который выдает сообщения о ведомости, подобные 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:
try(withJavaLogging({
// Your code here...
}, stopIsFatal=FALSE))
Ответ 2
Я написал этот код около недели назад, чтобы помочь мне отслеживать ошибки, которые происходят в основном из неинтерактивных сеансов R. Он все еще немного грубо, но он печатает трассировку стека и продолжается. Дайте мне знать, если это полезно, мне было бы интересно, как вы сделаете это более информативным. Я также открываю более чистые способы получить эту информацию.
options(warn = 2, keep.source = TRUE, error =
quote({
cat("Environment:\n", file=stderr());
# 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
#
# 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.
n <- length(last.dump)
calls <- names(last.dump)
cat(paste(" ", 1L:n, ": ", calls, sep = ""), sep = "\n", file=stderr())
cat("\n", file=stderr())
if (!interactive()) {
q()
}
}))
PS: вам может не понадобиться предупреждение = 2 (предупреждения, преобразованные в ошибки)
Ответ 3
Если что-то, что срабатывает по опции (ошибка...), представляет интерес, вы также можете сделать это:
options(error=traceback)
Из того, что я могу сказать, он делает большую часть того, что Боб предложил решить, но имеет то преимущество, что он намного короче.
(Не стесняйтесь комбинировать с keep.source = TRUE, warn = 2 и т.д. по мере необходимости.)
Ответ 4
Вы пробовали
options(error=recover)
настройка? Chambers 'Software for Data Analysis' содержит полезные советы по отладке.
Ответ 5
нет номеров строк, но это ближайший я нашел до сих пор:
run = function() {
// Your code here...
}
withCallingHandlers(run(), error=function(e)cat(conditionMessage(e), sapply(sys.calls(),function(sc)deparse(sc)[1]), sep="\n "))
Ответ 6
Я думаю, что вам нужно будет использовать tryCatch()
. Вы можете делать все, что хотите, в функции tryCatch(), поэтому мне не понятно, почему вы рассматриваете это как сложное. Может быть, разместите свой пример кода?
Ответ 7
Это ответ на ответ @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)
, за которой следует исходный код функции журнала, определенный внутри функции withJavaLogging
. Я прокомментировал выше, что могу подавить этот нежелательный вывод, изменив trace = trace[length(trace):1]
на trace = trace[(length(trace) - 1):1]
Для удобства других, кто читает это, вот полная версия функции, которую я сейчас использую (переименован из JavaLogging в 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
Я написал решение, которое работает как 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])
Больше информации в моем ответе здесь: fooobar.com/info/298587/...