如何在Shiny弹窗中显示错误堆栈跟踪而非导致应用崩溃
解决Shiny自定义Observer中错误堆栈跟踪缺失用户代码的问题
问题根源
你的safe_observe通过bquote包装tryCatch后再传递给shiny::observe,导致用户代码的执行上下文被Shiny内部环境覆盖。同时show_error_popup中用parent.frame()获取的调用栈上下文错误,最终只能捕获到Shiny内部的调用记录,看不到handle_button这类用户代码的堆栈。
解决方案
通过以下修改,保留用户代码的调用栈上下文,并过滤掉无关的Shiny内部调用:
修改后的完整代码
library(shiny) library(rlang) # 处理Shiny静默错误,其他错误弹出通知 handle_errors_except_shiny <- function(error){ if(inherits(error, "shiny.silent.error")){ stop(error) }else{ # 捕获错误发生时的调用栈并附加到错误对象 error$call_stack <- trace_back() show_error_popup(error) } } # 安全Observer:错误时弹出通知,保留用户代码堆栈 safe_observe <- function(x, env = parent.frame(), quoted = FALSE, ..., domain = getDefaultReactiveDomain()){ if(!quoted){ x <- substitute(x) } # 用rlang表达式保留用户代码的执行上下文 wrapped_code <- expr( tryCatch( !!x, error = handle_errors_except_shiny ) ) observe(x = wrapped_code, env = env, quoted = TRUE, domain = domain, ...) } # 弹出错误通知并打印过滤后的用户代码堆栈 show_error_popup <- function(e){ showNotification(conditionMessage(e), type = "error", duration = NULL) # 过滤堆栈:移除Shiny内部、promises、tryCatch等无关调用 filtered_stack <- trace_filter(e$call_stack, function(frame) { call_str <- deparse(frame$call) !grepl("shiny:::", call_str) && !grepl("promises:::", call_str) && !grepl("base::tryCatch", call_str) && !grepl("handle_errors_except_shiny", call_str) }) print(filtered_stack) } ui <- fluidPage( actionButton("btn", "点击触发错误") ) server <- function(input, output, session) { safe_observe({ req(input$btn) handle_button() }) } handle_button <- function(){ stop("测试错误:这是用户代码抛出的错误") message("按钮点击成功\n") } shinyApp(ui, server)
关键修改点说明
保留调用栈上下文:
在handle_errors_except_shiny中,用rlang::trace_back()捕获错误发生时的原始调用栈,并附加到错误对象中,确保能获取到用户代码的调用信息。正确传递用户表达式:
safe_observe改用rlang::expr和!!插入用户代码,替代bquote,保证用户代码在原始环境中执行,不会丢失调用栈上下文。过滤无关堆栈:
show_error_popup中用rlang::trace_filter移除Shiny内部、promises、tryCatch等无关调用,只保留用户自己的代码堆栈,例如点击按钮后控制台会输出:▆ 1. ├─global handle_button() 2. └─base::stop("测试错误:这是用户代码抛出的错误")
内容的提问来源于stack exchange,提问作者akraf
相关产品推荐
相关产品推荐

