You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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)

关键修改点说明

  1. 保留调用栈上下文:
    在handle_errors_except_shiny中,用rlang::trace_back()捕获错误发生时的原始调用栈,并附加到错误对象中,确保能获取到用户代码的调用信息。

  2. 正确传递用户表达式:
    safe_observe改用rlang::expr和!!插入用户代码,替代bquote,保证用户代码在原始环境中执行,不会丢失调用栈上下文。

  3. 过滤无关堆栈:
    show_error_popup中用rlang::trace_filter移除Shiny内部、promises、tryCatch等无关调用,只保留用户自己的代码堆栈,例如点击按钮后控制台会输出:

    ▆
    1. ├─global handle_button()
    2. └─base::stop("测试错误:这是用户代码抛出的错误")
    

内容的提问来源于stack exchange,提问作者akraf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:18:09