te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>r - How to display shiny error stack trace in popups instead of crashing app - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

r - How to display shiny error stack trace in popups instead of crashing app - Stack Overflow

programmeradmin3浏览0评论

I want to prevent my app from crashing in the case of an unforseen error. This happens when errors happen inside observers, so I created custom observer functions that handle errors by displaying the error message as a shiny popup. However, I can't seem to get the stack trace. I see for each error only calls that are internal to shiny.

This is probably because the custom observer quotes and then eval()s the code.. How can I get an error message that points me to code that I have written and possibly even avoid the shiny stack trace that is always the same?

This is what I have so far:

library(shiny)
library(shinyjs)


# Pass through silent shiny "errors" (used for skipping observers by req())
# and show popup if it is a "real" error.
handle_errors_except_shiny <- function(error){
  if(inherits(error, "shiny.silent.error")){
    stop(error)
  }else{
    show_error_popup(error)
  }
}

# like shiny::observe(), but display a notification in case of error
safe_observe <- function(x, env = parent.frame(), quoted = FALSE, ..., domain = getDefaultReactiveDomain()){
  if(!quoted){
    x <- substitute(x)
  }
  x <- bquote(tryCatch( .(x),  error = handle_errors_except_shiny ))
  
  observe(x=x, env=env, quoted=TRUE, domain=domain, ...)
}

# Can be used as error handler in tryCatch( error= ... ) within observers to prevent application crash but
# rather show a message
# 
# If the error message has the class "userError", a stack trace is not printed but 
# just the error text
# This allows to use this function to display errors that are meant to guide the user.
show_error_popup <- function(e, top_frame = NULL, bottom_frame = parent.frame()){

  stacktrace <- rlang::trace_back(top = top_frame, bottom = bottom_frame)
  showNotification(conditionMessage(e), type = "error", duration = NULL)

  # Print stack trace but it contains only shiny-internal functions :-(
  print(stacktrace)
}


ui <- fluidPage(
  actionButton("btn", "Click Me")
)

server <- function(input, output, session) {
  safe_observe({
    req(input$btn) # take dependency on button
    handle_button()
  })
}

handle_button <- function(){
  stop("test error")
  message("Button clicked\n")
}

shinyApp(ui, server)

Output has no reference to handle_button and is unnecessarily complex to help with debugging the app:

     ▆
  1. ├─base (local) `<fn>`(x)
  2. └─shiny:::print.shiny.appobj(x)
  3.   └─shiny::runApp(x) at shiny/R/shinyapp.R:565:3
  4.     ├─shiny::..stacktraceoff..(...) at shiny/R/runapp.R:388:3
  5.     ├─shiny::captureStackTraces(...) at shiny/R/runapp.R:388:3
  6.     │ └─promises::with_promise_domain(...) at shiny/R/conditions.R:125:3
  7.     │   └─domain$wrapSync(expr) at promises/R/domains.R:171:3
  8.     │     └─base::withCallingHandlers(expr, error = doCaptureStack) at shiny/R/conditions.R:185:7
  9.     ├─shiny:::..stacktracefloor..(serviceApp()) at shiny/R/runapp.R:391:9
 10.     └─shiny:::serviceApp() at shiny/R/runapp.R:391:9
 11.       └─shiny:::flushReact() at shiny/R/server.R:492:3
 12.         └─.getReactiveEnvironment()$flush() at shiny/R/react.R:200:3
 13.           └─ctx$executeFlushCallbacks() at shiny/R/react.R:180:9
 14.             └─base::lapply(...) at shiny/R/react.R:111:7
 15.               └─shiny (local) FUN(X[[i]], ...)
 16.                 └─shiny (local) flushCallback() at shiny/R/react.R:112:9
 17.                   ├─shiny:::hybrid_chain(...) at shiny/R/reactives.R:1204:9
 18.                   │ └─shiny (local) do() at shiny/R/utils.R:1558:5
 19.                   │   ├─base::tryCatch(...) at shiny/R/utils.R:1518:5
 20.                   │   │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
 21.                   │   │   └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
 22.                   │   │     └─base (local) doTryCatch(return(expr), name, parentenv, handler)
 23.                   │   ├─shiny::captureStackTraces(...) at shiny/R/utils.R:1520:9
 24.                   │   │ └─promises::with_promise_domain(...) at shiny/R/conditions.R:125:3
 25.                   │   │   └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 26.                   │   │     └─base::withCallingHandlers(expr, error = doCaptureStack) at shiny/R/conditions.R:185:7
 27.                   │   ├─base::withVisible(force(expr)) at shiny/R/utils.R:1521:11
 28.                   │   └─base::force(expr) at shiny/R/utils.R:1521:11
 29.                   ├─shiny:::shinyCallingHandlers(run()) at shiny/R/reactives.R:1207:15
 30.                   │ ├─base::withCallingHandlers(...) at shiny/R/utils.R:484:3
 31.                   │ └─shiny::captureStackTraces(expr) at shiny/R/utils.R:484:3
 32.                   │   └─promises::with_promise_domain(...) at shiny/R/conditions.R:125:3
 33.                   │     └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 34.                   │       └─base::withCallingHandlers(expr, error = doCaptureStack) at shiny/R/conditions.R:185:7
 35.                   └─shiny (local) run() at shiny/R/utils.R:484:3
 36.                     └─ctx$run(.func) at shiny/R/reactives.R:1234:7
 37.                       ├─promises::with_promise_domain(...) at shiny/R/react.R:54:7
 38.                       │ └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 39.                       ├─shiny::withReactiveDomain(...) at shiny/R/react.R:55:9
 40.                       │ └─promises::with_promise_domain(...) at shiny/R/reactive-domains.R:98:3
 41.                       │   └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 42.                       │     └─base::force(expr) at shiny/R/utils.R:1598:7
 43.                       └─env$runWith(self, func) at shiny/R/react.R:59:11
 44.                         └─shiny (local) contextFunc() at shiny/R/react.R:158:7
 45.                           ├─shiny::..stacktraceon..(`<observer>`(...)) at shiny/R/utils.R:1449:22
 46.                           └─shiny (local) `<observer>`(...) at shiny/R/conditions.R:522:21
 47.                             └─observe() at shiny/R/utils.R:1459:22
 48.                               └─base::tryCatch(...)
 49.                                 └─base (local) tryCatchList(expr, classes, parentenv, handlers)
 50.                                   └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
 51.                                     └─value[[3L]](cond)

I want to prevent my app from crashing in the case of an unforseen error. This happens when errors happen inside observers, so I created custom observer functions that handle errors by displaying the error message as a shiny popup. However, I can't seem to get the stack trace. I see for each error only calls that are internal to shiny.

This is probably because the custom observer quotes and then eval()s the code.. How can I get an error message that points me to code that I have written and possibly even avoid the shiny stack trace that is always the same?

This is what I have so far:

library(shiny)
library(shinyjs)


# Pass through silent shiny "errors" (used for skipping observers by req())
# and show popup if it is a "real" error.
handle_errors_except_shiny <- function(error){
  if(inherits(error, "shiny.silent.error")){
    stop(error)
  }else{
    show_error_popup(error)
  }
}

# like shiny::observe(), but display a notification in case of error
safe_observe <- function(x, env = parent.frame(), quoted = FALSE, ..., domain = getDefaultReactiveDomain()){
  if(!quoted){
    x <- substitute(x)
  }
  x <- bquote(tryCatch( .(x),  error = handle_errors_except_shiny ))
  
  observe(x=x, env=env, quoted=TRUE, domain=domain, ...)
}

# Can be used as error handler in tryCatch( error= ... ) within observers to prevent application crash but
# rather show a message
# 
# If the error message has the class "userError", a stack trace is not printed but 
# just the error text
# This allows to use this function to display errors that are meant to guide the user.
show_error_popup <- function(e, top_frame = NULL, bottom_frame = parent.frame()){

  stacktrace <- rlang::trace_back(top = top_frame, bottom = bottom_frame)
  showNotification(conditionMessage(e), type = "error", duration = NULL)

  # Print stack trace but it contains only shiny-internal functions :-(
  print(stacktrace)
}


ui <- fluidPage(
  actionButton("btn", "Click Me")
)

server <- function(input, output, session) {
  safe_observe({
    req(input$btn) # take dependency on button
    handle_button()
  })
}

handle_button <- function(){
  stop("test error")
  message("Button clicked\n")
}

shinyApp(ui, server)

Output has no reference to handle_button and is unnecessarily complex to help with debugging the app:

     ▆
  1. ├─base (local) `<fn>`(x)
  2. └─shiny:::print.shiny.appobj(x)
  3.   └─shiny::runApp(x) at shiny/R/shinyapp.R:565:3
  4.     ├─shiny::..stacktraceoff..(...) at shiny/R/runapp.R:388:3
  5.     ├─shiny::captureStackTraces(...) at shiny/R/runapp.R:388:3
  6.     │ └─promises::with_promise_domain(...) at shiny/R/conditions.R:125:3
  7.     │   └─domain$wrapSync(expr) at promises/R/domains.R:171:3
  8.     │     └─base::withCallingHandlers(expr, error = doCaptureStack) at shiny/R/conditions.R:185:7
  9.     ├─shiny:::..stacktracefloor..(serviceApp()) at shiny/R/runapp.R:391:9
 10.     └─shiny:::serviceApp() at shiny/R/runapp.R:391:9
 11.       └─shiny:::flushReact() at shiny/R/server.R:492:3
 12.         └─.getReactiveEnvironment()$flush() at shiny/R/react.R:200:3
 13.           └─ctx$executeFlushCallbacks() at shiny/R/react.R:180:9
 14.             └─base::lapply(...) at shiny/R/react.R:111:7
 15.               └─shiny (local) FUN(X[[i]], ...)
 16.                 └─shiny (local) flushCallback() at shiny/R/react.R:112:9
 17.                   ├─shiny:::hybrid_chain(...) at shiny/R/reactives.R:1204:9
 18.                   │ └─shiny (local) do() at shiny/R/utils.R:1558:5
 19.                   │   ├─base::tryCatch(...) at shiny/R/utils.R:1518:5
 20.                   │   │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
 21.                   │   │   └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
 22.                   │   │     └─base (local) doTryCatch(return(expr), name, parentenv, handler)
 23.                   │   ├─shiny::captureStackTraces(...) at shiny/R/utils.R:1520:9
 24.                   │   │ └─promises::with_promise_domain(...) at shiny/R/conditions.R:125:3
 25.                   │   │   └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 26.                   │   │     └─base::withCallingHandlers(expr, error = doCaptureStack) at shiny/R/conditions.R:185:7
 27.                   │   ├─base::withVisible(force(expr)) at shiny/R/utils.R:1521:11
 28.                   │   └─base::force(expr) at shiny/R/utils.R:1521:11
 29.                   ├─shiny:::shinyCallingHandlers(run()) at shiny/R/reactives.R:1207:15
 30.                   │ ├─base::withCallingHandlers(...) at shiny/R/utils.R:484:3
 31.                   │ └─shiny::captureStackTraces(expr) at shiny/R/utils.R:484:3
 32.                   │   └─promises::with_promise_domain(...) at shiny/R/conditions.R:125:3
 33.                   │     └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 34.                   │       └─base::withCallingHandlers(expr, error = doCaptureStack) at shiny/R/conditions.R:185:7
 35.                   └─shiny (local) run() at shiny/R/utils.R:484:3
 36.                     └─ctx$run(.func) at shiny/R/reactives.R:1234:7
 37.                       ├─promises::with_promise_domain(...) at shiny/R/react.R:54:7
 38.                       │ └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 39.                       ├─shiny::withReactiveDomain(...) at shiny/R/react.R:55:9
 40.                       │ └─promises::with_promise_domain(...) at shiny/R/reactive-domains.R:98:3
 41.                       │   └─domain$wrapSync(expr) at promises/R/domains.R:171:3
 42.                       │     └─base::force(expr) at shiny/R/utils.R:1598:7
 43.                       └─env$runWith(self, func) at shiny/R/react.R:59:11
 44.                         └─shiny (local) contextFunc() at shiny/R/react.R:158:7
 45.                           ├─shiny::..stacktraceon..(`<observer>`(...)) at shiny/R/utils.R:1449:22
 46.                           └─shiny (local) `<observer>`(...) at shiny/R/conditions.R:522:21
 47.                             └─observe() at shiny/R/utils.R:1459:22
 48.                               └─base::tryCatch(...)
 49.                                 └─base (local) tryCatchList(expr, classes, parentenv, handlers)
 50.                                   └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
 51.                                     └─value[[3L]](cond)
Share Improve this question edited 2 days ago akraf asked 2 days ago akrafakraf 3,23526 silver badges49 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

I found that shiny has internal functions to handle this problem. You get what you want by changing the following functions:

# like shiny::observe(), but display a notification in case of error
safe_observe <- function(x, env = parent.frame(), quoted = FALSE, ..., domain = getDefaultReactiveDomain()){
  if(!quoted){
    x <- substitute(x)
  }

  # >>>>>> add captureStackTraces() here <<<<<<<<<<<<<<<<<<<<<<<<<<<<
  x <- bquote(tryCatch( captureStackTraces(.(x)),  error = handle_errors_except_shiny ))
  
  observe(x=x, env=env, quoted=TRUE, domain=domain, ...)
}

# Can be used as error handler in tryCatch( error= ... ) within observers to prevent application crash but
# rather show a message
# 
# If the error message has the class "userError", a stack trace is not printed but 
# just the error text
# This allows to use this function to display errors that are meant to guide the user.
show_error_popup <- function(e, top_frame = NULL, bottom_frame = parent.frame()){
  stacktrace <- rlang::trace_back(top = top_frame, bottom = bottom_frame)

  # >>> The function prints the stack trace to the console, you want to capture it <<<<<
  t <- capture.output(printStackTrace(e),type = "message")

  # >>> convert the formatting from terminal color codes to HTML <<<<<<<< 
  t <- paste(cli::ansi_html(t),collapse="\n")
  showNotification(HTML(paste0("<div>",conditionMessage(e),"</div><pre>", t, "</pre>")),
                   type = "error", duration = NULL)
}

This shows handle_button in the stack trace as desired.

发布评论

评论列表(0)

  1. 暂无评论