2009-12-29 41 views
17

Tôi đang viết một số mã R gọi mã khác có thể bị lỗi. Nếu có, tôi muốn in một dấu vết ngăn xếp (để theo dõi những gì đã đi sai), sau đó thực hiện bất kể. Tuy nhiên, hàm traceback() chỉ cung cấp thông tin về các trường hợp ngoại lệ chưa được nắm bắt. Tôi có thể nhận được kết quả tôi muốn thông qua một xây dựng khá phức tạp, natty liên quan đến tryCatch và dump.frames, nhưng không phải là một cách dễ dàng hơn để làm điều này?Theo dõi ngăn xếp in và tiếp tục sau khi xảy ra lỗi trong R

Trả lời

7

tôi đã kết thúc viết một logger có mục đích chung là tạo ra các thông điệp logging Java giống như khi tiêu chuẩn R Các phương thức "message", "warning" và "stop" được gọi. Nó bao gồm dấu thời gian và dấu vết ngăn xếp cho các cảnh báo và ở trên.

Rất cảm ơn Man Group để được phép phân phối điều này! Cũng xin cảm ơn Bob Albright, câu trả lời của tôi đã giúp tôi tìm hiểu về những gì tôi đang tìm kiếm.

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

Để sử dụng nó, chỉ cần quấn nó xung quanh mã của bạn:

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

Đối với một sản lượng yên tĩnh hơn trong trường hợp không sai sót, đặt cờ silentSuccess (hữu dụng cho các bài kiểm tra!). Tin nhắn sẽ chỉ được xuất nếu xảy ra lỗi, để cung cấp ngữ cảnh cho sự thất bại.

Để đạt được mục tiêu ban đầu (dump stack trace + mang về), chỉ cần sử dụng thử:

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

Nhắc tôi về gói 'evaluation' của Hadley, mặc dù tôi khá chắc chắn rằng không làm theo dõi ngăn xếp. Tuy nhiên, tôi chưa thấy nó được đề cập ở đây, và nó chắc chắn có thể hữu ích cho những người không cần toàn bộ cơ chế mà bạn cung cấp ở đây. – Aaron

+0

Công việc tuyệt vời! BTW: Mục đích của việc thêm thuộc tính "calls" qua 'restrictedLabels (c (gọi, attr (obj," calls "))) là gì? Khi tôi kiểm tra các thuộc tính '(obj)' tôi chỉ tìm thấy một thuộc tính có tên là "call" (số ít!) ... –

+0

@RYoda Weird, đã làm việc cho tôi khi tôi viết nó. Sau đó, một lần nữa, R không phải là ngôn ngữ nhất quán nhất trên hành tinh. –

0

Tôi nghĩ rằng bạn sẽ cần sử dụng tryCatch(). Bạn có thể làm bất cứ điều gì bạn muốn trong hàm tryCatch(), vì vậy nó không rõ ràng với tôi tại sao bạn đang xem nó phức tạp. Có thể đăng ví dụ mã của bạn?

+0

Complex so với hầu hết ot ngôn ngữ của cô ấy tôi sử dụng, ví dụ: Java hoặc Python, trong đó in dấu vết ngăn xếp từ một ngoại lệ là một bộ đệm không có bộ não. –

+0

Tôi vẫn không hiểu tại sao những gì bạn mô tả sẽ nhiều hơn một lớp lót. Khó khăn duy nhất là nếu bạn đang cố gắng ném một loại ngoại lệ cụ thể, bởi vì điều đó không được hỗ trợ. – Shane

+1

Có lẽ nó không phải - nếu có, xin vui lòng gửi như thế nào bạn sẽ làm điều đó! :) –

4

Các bạn đã thử các thiết lập

options(error=recover) 

? Phần mềm 'Phân tích dữ liệu của Chambers' có một số gợi ý hữu ích về gỡ lỗi.

+0

Tôi không muốn một lời nhắc tương tác, tôi muốn chương trình in ra dấu vết ngăn xếp và tiếp tục bất kể. –

+0

Bạn có đang làm việc với mã R hay chỉ với các ngôn ngữ khác mà bạn dán vào R? –

+0

Tôi đang làm việc với mã R chỉ –

18

Tôi đã viết mã này khoảng một tuần trước để giúp tôi theo dõi lỗi đến chủ yếu từ các phiên R không tương tác. Nó vẫn còn hơi thô, nhưng nó in một dấu vết ngăn xếp và tiếp tục. Hãy cho tôi biết nếu điều này là hữu ích, tôi muốn được quan tâm đến cách bạn sẽ làm cho điều này nhiều thông tin hơn. Tôi cũng mở ra những cách thức sạch hơn để có được thông tin này.

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: bạn có thể không muốn cảnh báo = 2 (cảnh báo chuyển thành lỗi)

+0

Tôi thích điều này. Để làm cho nó có nhiều thông tin hơn, bạn có thể gọi 'ls.str()' cho mỗi environmnent trong 'last.dump'. (Điều này có thể làm cho đầu ra khá dài mặc dù.) –

+0

Không hoàn toàn những gì tôi đã sau, nhưng nó ít nhất là địa chỉ in một dấu vết ngăn xếp. Cảm ơn! –

9

Nếu cái gì đó gây nên vào tùy chọn (lỗi ...) là quan tâm, bạn cũng có thể làm điều này:

Từ những gì tôi có thể nói, nó thực hiện hầu hết các giải pháp được đề xuất của Bob, nhưng có lợi thế là ngắn hơn nhiều.

(Hãy thoải mái kết hợp với keep.source = TRUE, cảnh báo = 2, vv khi cần thiết.)

+1

Thật không may, tôi cần phải tiếp tục sau đó, tức là chạy trong một khối try(), vì vậy nó sẽ không kích hoạt trên tùy chọn (error = ...). –

1

không có số dòng nhưng điều này là gần nhất tôi tìm thấy cho đến nay:

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

tôi đã viết một giải pháp hoạt động như try, ngoại trừ việc nó cũng trả về ngăn xếp cuộc gọi.

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

thêm thông tin trong câu trả lời của tôi ở đây: https://stackoverflow.com/a/40899766/1587132

0

Đây là một followup để trả lời @ chrispy của trên, nơi ông đã trình bày một hàm withJavaLogging. Tôi nhận xét rằng giải pháp của ông là truyền cảm hứng, nhưng đối với tôi, bị tàn phá bởi một số đầu ra ở đầu của dấu vết ngăn xếp mà tôi không muốn thấy.

Để minh họa, hãy xem xét mã này:

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

Nếu tôi thực thi các dòng withJavaLogging(f1()) tôi nhận được đầu ra

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 

Tôi không muốn để thấy rằng at .handleSimpleError(function (obj) dòng tiếp theo là mã nguồn của hàm logger được định nghĩa bên trong hàm withJavaLogging. Tôi đã nhận xét ở trên rằng tôi có thể ngăn chặn đầu ra không mong muốn đó bằng cách thay đổi trace = trace[length(trace):1] thành trace = trace[(length(trace) - 1):1]

Để có sự tiện lợi của bất kỳ ai khác đọc đây, đây là phiên bản hoàn chỉnh của chức năng mà tôi đang sử dụng (được đổi tên từ withJavaLogging to logFully) để phù hợp với sở thích đọc của tôi):

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

Nếu tôi thực thi các dòng logFully(f1()) tôi nhận được đầu ra tôi mong muốn, mà chỉ đơn giản là

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 
Các vấn đề liên quan