mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-08 06:34:02 -05:00
chore(otel): Rename shiny.otel.bind to shiny.otel.collect (#4321)
Co-authored-by: Barret Schloerke <barret@posit.co>
This commit is contained in:
@@ -184,7 +184,8 @@ Collate:
|
||||
'modules.R'
|
||||
'notifications.R'
|
||||
'otel-attr-srcref.R'
|
||||
'otel-bind.R'
|
||||
'otel-collect.R'
|
||||
'otel-enable.R'
|
||||
'otel-error.R'
|
||||
'otel-label.R'
|
||||
'otel-reactive-update.R'
|
||||
|
||||
2
NEWS.md
2
NEWS.md
@@ -4,7 +4,7 @@
|
||||
|
||||
* Added support for [OpenTelemetry](https://opentelemetry.io/) via [`{otel}`](https://otel.r-lib.org/index.html). By default, if `otel::is_tracing_enabled()` returns `TRUE`, then `{shiny}` will record all OpenTelemetery spans. See [`{otelsdk}`'s Collecting Telemetry Data](https://otelsdk.r-lib.org/reference/collecting.html) for more details on configuring OpenTelemetry.
|
||||
|
||||
* Supported values for `options(shiny.otel.bind)` (or `Sys.getenv("SHINY_OTEL_BIND")`):
|
||||
* Supported values for `options(shiny.otel.collect)` (or `Sys.getenv("SHINY_OTEL_COLLECT")`):
|
||||
* `"none"` - No Shiny OpenTelemetry tracing.
|
||||
* `"session"` - Adds session start/end spans.
|
||||
* `"reactive_update"` - Spans for any synchronous/asynchronous reactive update. (Includes `"session"` features).
|
||||
|
||||
@@ -506,7 +506,7 @@ bindCache.reactiveExpr <- function(x, ..., cache = "app") {
|
||||
rm(list = ".", envir = .GenericCallEnv, inherits = FALSE)
|
||||
}
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
res <- reactive(label = label, domain = domain, {
|
||||
cache <- resolve_cache_object(cache, domain)
|
||||
hybrid_chain(
|
||||
@@ -523,8 +523,8 @@ bindCache.reactiveExpr <- function(x, ..., cache = "app") {
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref)
|
||||
})
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
res <- bind_otel_reactive_expr(res)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
res <- enable_otel_reactive_expr(res)
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ bindEvent.reactiveExpr <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE
|
||||
|
||||
initialized <- FALSE
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
res <- reactive(label = label, domain = domain, ..stacktraceon = FALSE, {
|
||||
hybrid_chain(
|
||||
{
|
||||
@@ -244,8 +244,8 @@ bindEvent.reactiveExpr <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE
|
||||
})
|
||||
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
res <- bind_otel_reactive_expr(res)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
res <- enable_otel_reactive_expr(res)
|
||||
}
|
||||
|
||||
res
|
||||
@@ -343,8 +343,8 @@ bindEvent.Observer <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE,
|
||||
call_srcref <- get_call_srcref(-1)
|
||||
x$.otelAttrs <- append_otel_srcref_attrs(x$.otelAttrs, call_srcref)
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
x <- bind_otel_observe(x)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
x <- enable_otel_observe(x)
|
||||
}
|
||||
|
||||
invisible(x)
|
||||
|
||||
@@ -118,7 +118,7 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
private$func <- func
|
||||
|
||||
# Do not show these private reactive values in otel spans
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
private$rv_status <- reactiveVal("initial", label = "ExtendedTask$private$status")
|
||||
private$rv_value <- reactiveVal(NULL, label = "ExtendedTask$private$value")
|
||||
private$rv_error <- reactiveVal(NULL, label = "ExtendedTask$private$error")
|
||||
@@ -175,7 +175,7 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
private$invocation_queue$add(list(args = args, call = call))
|
||||
} else {
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
if (has_otel_collect("reactivity")) {
|
||||
private$otel_span <- start_otel_span(
|
||||
private$otel_span_label,
|
||||
attributes = private$otel_attrs
|
||||
|
||||
@@ -436,7 +436,7 @@ MockShinySession <- R6Class(
|
||||
if (!is.function(func))
|
||||
stop(paste("Unexpected", class(func), "output for", name))
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
obs <- observe({
|
||||
# We could just stash the promise, but we get an "unhandled promise error". This bypasses
|
||||
prom <- NULL
|
||||
|
||||
60
R/otel-collect.R
Normal file
60
R/otel-collect.R
Normal file
@@ -0,0 +1,60 @@
|
||||
otel_collect_choices <- c(
|
||||
"none",
|
||||
"session",
|
||||
"reactive_update",
|
||||
"reactivity",
|
||||
"all"
|
||||
)
|
||||
|
||||
# Check if the collect level is sufficient
|
||||
otel_collect_is_enabled <- function(
|
||||
impl_level,
|
||||
# Listen to option and fall back to the env var
|
||||
opt_collect_level = getOption("shiny.otel.collect", Sys.getenv("SHINY_OTEL_COLLECT", "all"))
|
||||
) {
|
||||
opt_collect_level <- as_otel_collect(opt_collect_level)
|
||||
|
||||
which(opt_collect_level == otel_collect_choices) >=
|
||||
which(impl_level == otel_collect_choices)
|
||||
}
|
||||
|
||||
# Check if tracing is enabled and if the collect level is sufficient
|
||||
has_otel_collect <- function(collect) {
|
||||
# Only check pkg author input iff loaded with pkgload
|
||||
if (IS_SHINY_LOCAL_PKG) {
|
||||
stopifnot(length(collect) == 1, any(collect == otel_collect_choices))
|
||||
}
|
||||
|
||||
otel_is_tracing_enabled() && otel_collect_is_enabled(collect)
|
||||
}
|
||||
|
||||
# Run expr with otel collection disabled
|
||||
with_no_otel_collect <- function(expr) {
|
||||
withr::with_options(
|
||||
list(
|
||||
shiny.otel.collect = "none"
|
||||
),
|
||||
expr
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
## -- Helpers -----------------------------------------------------
|
||||
|
||||
# shiny.otel.collect can be:
|
||||
# "none"; To do nothing / fully opt-out
|
||||
# "session" for session/start events
|
||||
# "reactive_update" (includes "session" features) and reactive_update spans
|
||||
# "reactivity" (includes "reactive_update" features) and spans for all reactive things
|
||||
# "all" - Anything that Shiny can do. (Currently equivalent to the "reactivity" level)
|
||||
|
||||
as_otel_collect <- function(collect = "all") {
|
||||
if (!is.character(collect)) {
|
||||
stop("`collect` must be a character vector.")
|
||||
}
|
||||
|
||||
# Match to collect enum
|
||||
collect <- match.arg(collect, otel_collect_choices, several.ok = FALSE)
|
||||
|
||||
return(collect)
|
||||
}
|
||||
@@ -1,67 +1,3 @@
|
||||
otel_bind_choices <- c(
|
||||
"none",
|
||||
"session",
|
||||
"reactive_update",
|
||||
"reactivity",
|
||||
"all"
|
||||
)
|
||||
|
||||
# Check if the bind level is sufficient
|
||||
otel_bind_is_enabled <- function(
|
||||
impl_level,
|
||||
# Listen to option and fall back to the env var
|
||||
opt_bind_level = getOption("shiny.otel.bind", Sys.getenv("SHINY_OTEL_BIND", "all"))
|
||||
) {
|
||||
opt_bind_level <- as_otel_bind(opt_bind_level)
|
||||
|
||||
which(opt_bind_level == otel_bind_choices) >=
|
||||
which(impl_level == otel_bind_choices)
|
||||
}
|
||||
|
||||
# Check if tracing is enabled and if the bind level is sufficient
|
||||
has_otel_bind <- function(bind) {
|
||||
# Only check pkg author input iff loaded with pkgload
|
||||
if (IS_SHINY_LOCAL_PKG) {
|
||||
stopifnot(length(bind) == 1, any(bind == otel_bind_choices))
|
||||
}
|
||||
|
||||
otel_is_tracing_enabled() && otel_bind_is_enabled(bind)
|
||||
}
|
||||
|
||||
# Run expr with otel binding disabled
|
||||
with_no_otel_bind <- function(expr) {
|
||||
withr::with_options(
|
||||
list(
|
||||
shiny.otel.bind = "none"
|
||||
),
|
||||
expr
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
## -- Helpers -----------------------------------------------------
|
||||
|
||||
# shiny.otel.bind can be:
|
||||
# "none"; To do nothing / fully opt-out
|
||||
# "session" for session/start events
|
||||
# "reactive_update" (includes "session" features) and reactive_update spans
|
||||
# "reactivity" (includes "reactive_update" features) and spans for all reactive things
|
||||
# "all" - Anything that Shiny can do. (Currently equivalent to the "reactivity" level)
|
||||
|
||||
as_otel_bind <- function(bind = "all") {
|
||||
if (!is.character(bind)) {
|
||||
stop("`bind` must be a character vector.")
|
||||
}
|
||||
|
||||
# Match to bind enum
|
||||
bind <- match.arg(bind, otel_bind_choices, several.ok = FALSE)
|
||||
|
||||
return(bind)
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
|
||||
# # Approach
|
||||
# Use flags on the reactive object to indicate whether to record OpenTelemetry spans.
|
||||
#
|
||||
@@ -75,7 +11,7 @@ as_otel_bind <- function(bind = "all") {
|
||||
#'
|
||||
#' @description
|
||||
#'
|
||||
#' `bind_otel_*()` methods add OpenTelemetry flags for [reactive()] expressions
|
||||
#' `enable_otel_*()` methods add OpenTelemetry flags for [reactive()] expressions
|
||||
#' and `render*` functions (like [renderText()], [renderTable()], ...).
|
||||
#'
|
||||
#' Wrapper to creating an active reactive OpenTelemetry span that closes when
|
||||
@@ -115,7 +51,7 @@ as_otel_bind <- function(bind = "all") {
|
||||
#' Dev note - Barret 2025-10:
|
||||
#' Typically, an OpenTelemetry span (`otel_span`) will inherit from the parent
|
||||
#' span. This works well and we can think of the hierarchy as a tree. With
|
||||
#' `options("shiny.otel.bind" = <value>)`, we are able to control with a sliding
|
||||
#' `options("shiny.otel.collect" = <value>)`, we are able to control with a sliding
|
||||
#' dial how much of the tree we are interested in: "none", "session",
|
||||
#' "reactive_update", "reactivity", and finally "all".
|
||||
#'
|
||||
@@ -152,18 +88,19 @@ as_otel_bind <- function(bind = "all") {
|
||||
#' create it themselves and let natural inheritance take over.
|
||||
#'
|
||||
#' Given this, I will imagine that app authors will set
|
||||
#' `options("shiny.otel.bind" = "reactive_update")` as their default behavior.
|
||||
#' `options("shiny.otel.collect" = "reactive_update")` as their default behavior.
|
||||
#' Enough to know things are happening, but not overwhelming from **everything**
|
||||
#' that is reactive.
|
||||
#'
|
||||
#' To _light up_ a specific area, users can call `withr::with_options(list("shiny.otel.bind" = "all"), { ... })`.
|
||||
#' To _light up_ a specific area, users can call `withr::with_options(list("shiny.otel.collect" = "all"), { ... })`.
|
||||
#'
|
||||
#' @param x The object to add caching to.
|
||||
#' @param ... Future parameter expansion.
|
||||
#' @noRd
|
||||
NULL
|
||||
|
||||
bind_otel_reactive_val <- function(x) {
|
||||
|
||||
enable_otel_reactive_val <- function(x) {
|
||||
|
||||
impl <- attr(x, ".impl", exact = TRUE)
|
||||
# Set flag for otel logging when setting the value
|
||||
@@ -174,7 +111,7 @@ bind_otel_reactive_val <- function(x) {
|
||||
x
|
||||
}
|
||||
|
||||
bind_otel_reactive_values <- function(x) {
|
||||
enable_otel_reactive_values <- function(x) {
|
||||
|
||||
impl <- .subset2(x, "impl")
|
||||
# Set flag for otel logging when setting values
|
||||
@@ -185,7 +122,7 @@ bind_otel_reactive_values <- function(x) {
|
||||
x
|
||||
}
|
||||
|
||||
bind_otel_reactive_expr <- function(x) {
|
||||
enable_otel_reactive_expr <- function(x) {
|
||||
|
||||
domain <- reactive_get_domain(x)
|
||||
|
||||
@@ -199,7 +136,7 @@ bind_otel_reactive_expr <- function(x) {
|
||||
x
|
||||
}
|
||||
|
||||
bind_otel_observe <- function(x) {
|
||||
enable_otel_observe <- function(x) {
|
||||
x$.isRecordingOtel <- TRUE
|
||||
x$.otelLabel <- otel_span_label_observer(x, domain = x$.domain)
|
||||
|
||||
@@ -209,7 +146,7 @@ bind_otel_observe <- function(x) {
|
||||
|
||||
|
||||
|
||||
bind_otel_shiny_render_function <- function(x) {
|
||||
enable_otel_shiny_render_function <- function(x) {
|
||||
|
||||
valueFunc <- force(x)
|
||||
otel_span_label <- NULL
|
||||
@@ -12,7 +12,7 @@
|
||||
#' @noRd
|
||||
otel_span_reactive_update_init <- function(..., domain) {
|
||||
|
||||
if (!has_otel_bind("reactive_update")) return()
|
||||
if (!has_otel_collect("reactive_update")) return()
|
||||
|
||||
# Ensure cleanup is registered only once per session
|
||||
if (is.null(domain$userData[["_otel_has_reactive_cleanup"]])) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#' @noRd
|
||||
otel_span_session_start <- function(expr, ..., domain) {
|
||||
|
||||
if (!has_otel_bind("session")) {
|
||||
if (!has_otel_collect("session")) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ otel_span_session_start <- function(expr, ..., domain) {
|
||||
|
||||
|
||||
otel_span_session_end <- function(expr, ..., domain) {
|
||||
if (!has_otel_bind("session")) {
|
||||
if (!has_otel_collect("session")) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
|
||||
@@ -248,8 +248,8 @@ reactiveVal <- function(value = NULL, label = NULL) {
|
||||
.impl = rv
|
||||
)
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
ret <- bind_otel_reactive_val(ret)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
ret <- enable_otel_reactive_val(ret)
|
||||
}
|
||||
|
||||
ret
|
||||
@@ -651,8 +651,8 @@ reactiveValues <- function(...) {
|
||||
|
||||
impl$mset(args)
|
||||
|
||||
# Add otel binding after `$mset()` so that we don't log the initial values
|
||||
# Add otel binding after `.label` so that any logging uses the correct label
|
||||
# Add otel collection after `$mset()` so that we don't log the initial values
|
||||
# Add otel collection after `.label` so that any logging uses the correct label
|
||||
values <- maybeAddReactiveValuesOtel(values)
|
||||
|
||||
values
|
||||
@@ -669,7 +669,7 @@ checkName <- function(x) {
|
||||
# @param values A ReactiveValues object
|
||||
# @param readonly Should this object be read-only?
|
||||
# @param ns A namespace function (either `identity` or `NS(namespace)`)
|
||||
# @param withOtel Should otel binding be attempted?
|
||||
# @param withOtel Should otel collection be attempted?
|
||||
.createReactiveValues <- function(values = NULL, readonly = FALSE,
|
||||
ns = identity, withOtel = TRUE) {
|
||||
|
||||
@@ -690,11 +690,11 @@ checkName <- function(x) {
|
||||
}
|
||||
|
||||
maybeAddReactiveValuesOtel <- function(x) {
|
||||
if (!has_otel_bind("reactivity")) {
|
||||
if (!has_otel_collect("reactivity")) {
|
||||
return(x)
|
||||
}
|
||||
|
||||
bind_otel_reactive_values(x)
|
||||
enable_otel_reactive_values(x)
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -1140,8 +1140,8 @@ reactive <- function(
|
||||
class = c("reactiveExpr", "reactive", "function")
|
||||
)
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
ret <- bind_otel_reactive_expr(ret)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
ret <- enable_otel_reactive_expr(ret)
|
||||
}
|
||||
|
||||
ret
|
||||
@@ -1590,8 +1590,8 @@ observe <- function(
|
||||
o$.otelAttrs <- otel_srcref_attributes(call_srcref)
|
||||
}
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
o <- bind_otel_observe(o)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
o <- enable_otel_observe(o)
|
||||
}
|
||||
|
||||
invisible(o)
|
||||
@@ -2011,7 +2011,7 @@ reactive_poll_impl <- function(
|
||||
re_finalized <- FALSE
|
||||
env <- environment()
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
cookie <- reactiveVal(
|
||||
isolate(checkFunc()),
|
||||
label = sprintf("%s %s cookie", fnName, label)
|
||||
@@ -2500,7 +2500,7 @@ observeEvent <- function(eventExpr, handlerExpr,
|
||||
)
|
||||
}
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
handler <- inject(observe(
|
||||
!!handlerQ,
|
||||
label = label,
|
||||
@@ -2524,8 +2524,8 @@ observeEvent <- function(eventExpr, handlerExpr,
|
||||
if (!is.null(call_srcref)) {
|
||||
o$.otelAttrs <- otel_srcref_attributes(call_srcref)
|
||||
}
|
||||
if (has_otel_bind("reactivity")) {
|
||||
o <- bind_otel_observe(o)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
o <- enable_otel_observe(o)
|
||||
}
|
||||
|
||||
invisible(o)
|
||||
@@ -2557,7 +2557,7 @@ eventReactive <- function(eventExpr, valueExpr,
|
||||
)
|
||||
}
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
value_r <- inject(reactive(!!valueQ, domain = domain, label = label))
|
||||
|
||||
r <- inject(bindEvent(
|
||||
@@ -2573,8 +2573,8 @@ eventReactive <- function(eventExpr, valueExpr,
|
||||
impl <- attr(r, "observable", exact = TRUE)
|
||||
impl$.otelAttrs <- otel_srcref_attributes(call_srcref)
|
||||
}
|
||||
if (has_otel_bind("reactivity")) {
|
||||
r <- bind_otel_reactive_expr(r)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
r <- enable_otel_reactive_expr(r)
|
||||
}
|
||||
|
||||
|
||||
@@ -2710,7 +2710,7 @@ debounce <- function(r, millis, priority = 100, domain = getDefaultReactiveDomai
|
||||
millis <- function() origMillis
|
||||
}
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
trigger <- reactiveVal(NULL, label = sprintf("debounce %s trigger", label))
|
||||
# the deadline for the timer to fire; NULL if not scheduled
|
||||
when <- reactiveVal(NULL, label = sprintf("debounce %s when", label))
|
||||
@@ -2781,7 +2781,7 @@ debounce <- function(r, millis, priority = 100, domain = getDefaultReactiveDomai
|
||||
er_impl$.otelAttrs <- append_otel_srcref_attrs(er_impl$.otelAttrs, call_srcref)
|
||||
})
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
# Force the value of er to be immediately cached upon creation. It's very hard
|
||||
# to explain why this observer is needed, but if you want to understand, try
|
||||
# commenting it out and studying the unit test failure that results.
|
||||
@@ -2814,7 +2814,7 @@ throttle <- function(r, millis, priority = 100, domain = getDefaultReactiveDomai
|
||||
millis <- function() origMillis
|
||||
}
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
trigger <- reactiveVal(0, label = sprintf("throttle %s trigger", label))
|
||||
# Last time we fired; NULL if never
|
||||
lastTriggeredAt <- reactiveVal(NULL, label = sprintf("throttle %s last triggered at", label))
|
||||
@@ -2837,7 +2837,7 @@ throttle <- function(r, millis, priority = 100, domain = getDefaultReactiveDomai
|
||||
pending(FALSE)
|
||||
}
|
||||
|
||||
with_no_otel_bind({
|
||||
with_no_otel_collect({
|
||||
# Responsible for tracking when f() changes.
|
||||
observeEvent(try(r(), silent = TRUE), {
|
||||
if (pending()) {
|
||||
|
||||
@@ -160,7 +160,7 @@ getShinyOption <- function(name, default = NULL) {
|
||||
# ' side devmode features. Currently the primary feature is the client-side
|
||||
# ' error console.}
|
||||
### end shiny.client_devmode
|
||||
#' \item{shiny.otel.bind (defaults to `Sys.getenv("SHINY_OTEL_BIND", "all")`)}{Determines how Shiny will
|
||||
#' \item{shiny.otel.collect (defaults to `Sys.getenv("SHINY_OTEL_COLLECT", "all")`)}{Determines how Shiny will
|
||||
#' interact with OpenTelemetry.
|
||||
#'
|
||||
#' Supported values:
|
||||
|
||||
@@ -1158,7 +1158,7 @@ ShinySession <- R6Class(
|
||||
attr(label, "srcfile") <- srcfile
|
||||
|
||||
# Do not bind this `observe()` call
|
||||
obs <- with_no_otel_bind(observe(..stacktraceon = FALSE, {
|
||||
obs <- with_no_otel_collect(observe(..stacktraceon = FALSE, {
|
||||
|
||||
private$sendMessage(recalculating = list(
|
||||
name = name, status = 'recalculating'
|
||||
|
||||
@@ -151,8 +151,8 @@ markRenderFunction <- function(
|
||||
otelAttrs = otelAttrs
|
||||
)
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
ret <- bind_otel_shiny_render_function(ret)
|
||||
if (has_otel_collect("reactivity")) {
|
||||
ret <- enable_otel_shiny_render_function(ret)
|
||||
}
|
||||
|
||||
ret
|
||||
|
||||
@@ -130,7 +130,7 @@ ragg package. See \code{\link[=plotPNG]{plotPNG()}} for more information.}
|
||||
Cairo package. See \code{\link[=plotPNG]{plotPNG()}} for more information.}
|
||||
\item{shiny.devmode (defaults to \code{NULL})}{Option to enable Shiny Developer Mode. When set,
|
||||
different default \code{getOption(key)} values will be returned. See \code{\link[=devmode]{devmode()}} for more details.}
|
||||
\item{shiny.otel.bind (defaults to \code{Sys.getenv("SHINY_OTEL_BIND", "all")})}{Determines how Shiny will
|
||||
\item{shiny.otel.collect (defaults to \code{Sys.getenv("SHINY_OTEL_COLLECT", "all")})}{Determines how Shiny will
|
||||
interact with OpenTelemetry.
|
||||
|
||||
Supported values:
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
test_that("otel_bind_is_enabled works with valid bind levels", {
|
||||
# Test with default "all" option
|
||||
expect_true(otel_bind_is_enabled("none"))
|
||||
expect_true(otel_bind_is_enabled("session"))
|
||||
expect_true(otel_bind_is_enabled("reactive_update"))
|
||||
expect_true(otel_bind_is_enabled("reactivity"))
|
||||
expect_true(otel_bind_is_enabled("all"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled respects hierarchy with 'none' option", {
|
||||
# With "none" option, nothing should be enabled
|
||||
expect_false(otel_bind_is_enabled("session", "none"))
|
||||
expect_false(otel_bind_is_enabled("reactive_update", "none"))
|
||||
expect_false(otel_bind_is_enabled("reactivity", "none"))
|
||||
expect_false(otel_bind_is_enabled("all", "none"))
|
||||
expect_true(otel_bind_is_enabled("none", "none"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled respects hierarchy with 'session' option", {
|
||||
# With "session" option, only "none" and "session" should be enabled
|
||||
expect_true(otel_bind_is_enabled("none", "session"))
|
||||
expect_true(otel_bind_is_enabled("session", "session"))
|
||||
expect_false(otel_bind_is_enabled("reactive_update", "session"))
|
||||
expect_false(otel_bind_is_enabled("reactivity", "session"))
|
||||
expect_false(otel_bind_is_enabled("all", "session"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled respects hierarchy with 'reactive_update' option", {
|
||||
# With "reactive_update" option, "none", "session", and "reactive_update" should be enabled
|
||||
expect_true(otel_bind_is_enabled("none", "reactive_update"))
|
||||
expect_true(otel_bind_is_enabled("session", "reactive_update"))
|
||||
expect_true(otel_bind_is_enabled("reactive_update", "reactive_update"))
|
||||
expect_false(otel_bind_is_enabled("reactivity", "reactive_update"))
|
||||
expect_false(otel_bind_is_enabled("all", "reactive_update"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled respects hierarchy with 'reactivity' option", {
|
||||
# With "reactivity" option, all except "all" should be enabled
|
||||
expect_true(otel_bind_is_enabled("none", "reactivity"))
|
||||
expect_true(otel_bind_is_enabled("session", "reactivity"))
|
||||
expect_true(otel_bind_is_enabled("reactive_update", "reactivity"))
|
||||
expect_true(otel_bind_is_enabled("reactivity", "reactivity"))
|
||||
expect_false(otel_bind_is_enabled("all", "reactivity"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled respects hierarchy with 'all' option", {
|
||||
# With "all" option (default), everything should be enabled
|
||||
expect_true(otel_bind_is_enabled("none", "all"))
|
||||
expect_true(otel_bind_is_enabled("session", "all"))
|
||||
expect_true(otel_bind_is_enabled("reactive_update", "all"))
|
||||
expect_true(otel_bind_is_enabled("reactivity", "all"))
|
||||
expect_true(otel_bind_is_enabled("all", "all"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled uses shiny.otel.bind option", {
|
||||
# Test that option is respected
|
||||
withr::with_options(
|
||||
list(shiny.otel.bind = "session"),
|
||||
{
|
||||
expect_true(otel_bind_is_enabled("none"))
|
||||
expect_true(otel_bind_is_enabled("session"))
|
||||
expect_false(otel_bind_is_enabled("reactive_update"))
|
||||
}
|
||||
)
|
||||
|
||||
withr::with_options(
|
||||
list(shiny.otel.bind = "reactivity"),
|
||||
{
|
||||
expect_true(otel_bind_is_enabled("reactive_update"))
|
||||
expect_true(otel_bind_is_enabled("reactivity"))
|
||||
expect_false(otel_bind_is_enabled("all"))
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled falls back to SHINY_OTEL_BIND env var", {
|
||||
# Remove option to test env var fallback
|
||||
withr::local_options(list(shiny.otel.bind = NULL))
|
||||
|
||||
# Test env var is respected
|
||||
withr::local_envvar(list(SHINY_OTEL_BIND = "session"))
|
||||
expect_true(otel_bind_is_enabled("none"))
|
||||
expect_true(otel_bind_is_enabled("session"))
|
||||
expect_false(otel_bind_is_enabled("reactive_update"))
|
||||
|
||||
withr::local_envvar(list(SHINY_OTEL_BIND = "none"))
|
||||
expect_true(otel_bind_is_enabled("none"))
|
||||
expect_false(otel_bind_is_enabled("session"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled option takes precedence over env var", {
|
||||
# Set conflicting option and env var
|
||||
withr::local_options(shiny.otel.bind = "session")
|
||||
withr::local_envvar(SHINY_OTEL_BIND = "all")
|
||||
|
||||
# Option should take precedence
|
||||
expect_true(otel_bind_is_enabled("session"))
|
||||
expect_false(otel_bind_is_enabled("reactive_update"))
|
||||
})
|
||||
|
||||
test_that("otel_bind_is_enabled defaults to 'all' when no option or env var", {
|
||||
# Remove both option and env var
|
||||
withr::local_options(list(shiny.otel.bind = NULL))
|
||||
withr::local_envvar(list(SHINY_OTEL_BIND = NA))
|
||||
|
||||
# Should default to "all"
|
||||
expect_true(otel_bind_is_enabled("all"))
|
||||
expect_true(otel_bind_is_enabled("reactivity"))
|
||||
expect_true(otel_bind_is_enabled("none"))
|
||||
})
|
||||
|
||||
# Tests for as_otel_bind()
|
||||
test_that("as_otel_bind validates and returns valid bind levels", {
|
||||
expect_equal(as_otel_bind("none"), "none")
|
||||
expect_equal(as_otel_bind("session"), "session")
|
||||
expect_equal(as_otel_bind("reactive_update"), "reactive_update")
|
||||
expect_equal(as_otel_bind("reactivity"), "reactivity")
|
||||
expect_equal(as_otel_bind("all"), "all")
|
||||
})
|
||||
|
||||
test_that("as_otel_bind uses default value", {
|
||||
expect_equal(as_otel_bind(), "all")
|
||||
})
|
||||
|
||||
test_that("as_otel_bind errors on invalid input types", {
|
||||
expect_error(as_otel_bind(123), "`bind` must be a character vector.")
|
||||
expect_error(as_otel_bind(NULL), "`bind` must be a character vector.")
|
||||
expect_error(as_otel_bind(TRUE), "`bind` must be a character vector.")
|
||||
expect_error(as_otel_bind(list("all")), "`bind` must be a character vector.")
|
||||
})
|
||||
|
||||
test_that("as_otel_bind errors on invalid bind levels", {
|
||||
expect_error(as_otel_bind("invalid"), "'arg' should be one of")
|
||||
expect_error(as_otel_bind("unknown"), "'arg' should be one of")
|
||||
expect_error(as_otel_bind(""), "'arg' should be one of")
|
||||
})
|
||||
|
||||
test_that("as_otel_bind errors on multiple values", {
|
||||
# match.arg with several.ok = FALSE should error on multiple values
|
||||
expect_error(as_otel_bind(c("all", "none")), "'arg' must be of length 1")
|
||||
expect_error(as_otel_bind(c("session", "reactivity")), "'arg' must be of length 1")
|
||||
})
|
||||
142
tests/testthat/test-otel-collect.R
Normal file
142
tests/testthat/test-otel-collect.R
Normal file
@@ -0,0 +1,142 @@
|
||||
test_that("otel_collect_is_enabled works with valid collect levels", {
|
||||
# Test with default "all" option
|
||||
expect_true(otel_collect_is_enabled("none"))
|
||||
expect_true(otel_collect_is_enabled("session"))
|
||||
expect_true(otel_collect_is_enabled("reactive_update"))
|
||||
expect_true(otel_collect_is_enabled("reactivity"))
|
||||
expect_true(otel_collect_is_enabled("all"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled respects hierarchy with 'none' option", {
|
||||
# With "none" option, nothing should be enabled
|
||||
expect_false(otel_collect_is_enabled("session", "none"))
|
||||
expect_false(otel_collect_is_enabled("reactive_update", "none"))
|
||||
expect_false(otel_collect_is_enabled("reactivity", "none"))
|
||||
expect_false(otel_collect_is_enabled("all", "none"))
|
||||
expect_true(otel_collect_is_enabled("none", "none"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled respects hierarchy with 'session' option", {
|
||||
# With "session" option, only "none" and "session" should be enabled
|
||||
expect_true(otel_collect_is_enabled("none", "session"))
|
||||
expect_true(otel_collect_is_enabled("session", "session"))
|
||||
expect_false(otel_collect_is_enabled("reactive_update", "session"))
|
||||
expect_false(otel_collect_is_enabled("reactivity", "session"))
|
||||
expect_false(otel_collect_is_enabled("all", "session"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled respects hierarchy with 'reactive_update' option", {
|
||||
# With "reactive_update" option, "none", "session", and "reactive_update" should be enabled
|
||||
expect_true(otel_collect_is_enabled("none", "reactive_update"))
|
||||
expect_true(otel_collect_is_enabled("session", "reactive_update"))
|
||||
expect_true(otel_collect_is_enabled("reactive_update", "reactive_update"))
|
||||
expect_false(otel_collect_is_enabled("reactivity", "reactive_update"))
|
||||
expect_false(otel_collect_is_enabled("all", "reactive_update"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled respects hierarchy with 'reactivity' option", {
|
||||
# With "reactivity" option, all except "all" should be enabled
|
||||
expect_true(otel_collect_is_enabled("none", "reactivity"))
|
||||
expect_true(otel_collect_is_enabled("session", "reactivity"))
|
||||
expect_true(otel_collect_is_enabled("reactive_update", "reactivity"))
|
||||
expect_true(otel_collect_is_enabled("reactivity", "reactivity"))
|
||||
expect_false(otel_collect_is_enabled("all", "reactivity"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled respects hierarchy with 'all' option", {
|
||||
# With "all" option (default), everything should be enabled
|
||||
expect_true(otel_collect_is_enabled("none", "all"))
|
||||
expect_true(otel_collect_is_enabled("session", "all"))
|
||||
expect_true(otel_collect_is_enabled("reactive_update", "all"))
|
||||
expect_true(otel_collect_is_enabled("reactivity", "all"))
|
||||
expect_true(otel_collect_is_enabled("all", "all"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled uses shiny.otel.collect option", {
|
||||
# Test that option is respected
|
||||
withr::with_options(
|
||||
list(shiny.otel.collect = "session"),
|
||||
{
|
||||
expect_true(otel_collect_is_enabled("none"))
|
||||
expect_true(otel_collect_is_enabled("session"))
|
||||
expect_false(otel_collect_is_enabled("reactive_update"))
|
||||
}
|
||||
)
|
||||
|
||||
withr::with_options(
|
||||
list(shiny.otel.collect = "reactivity"),
|
||||
{
|
||||
expect_true(otel_collect_is_enabled("reactive_update"))
|
||||
expect_true(otel_collect_is_enabled("reactivity"))
|
||||
expect_false(otel_collect_is_enabled("all"))
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled falls back to SHINY_OTEL_COLLECT env var", {
|
||||
# Remove option to test env var fallback
|
||||
withr::local_options(list(shiny.otel.collect = NULL))
|
||||
|
||||
# Test env var is respected
|
||||
withr::local_envvar(list(SHINY_OTEL_COLLECT = "session"))
|
||||
expect_true(otel_collect_is_enabled("none"))
|
||||
expect_true(otel_collect_is_enabled("session"))
|
||||
expect_false(otel_collect_is_enabled("reactive_update"))
|
||||
|
||||
withr::local_envvar(list(SHINY_OTEL_COLLECT = "none"))
|
||||
expect_true(otel_collect_is_enabled("none"))
|
||||
expect_false(otel_collect_is_enabled("session"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled option takes precedence over env var", {
|
||||
# Set conflicting option and env var
|
||||
withr::local_options(shiny.otel.collect = "session")
|
||||
withr::local_envvar(SHINY_OTEL_COLLECT = "all")
|
||||
|
||||
# Option should take precedence
|
||||
expect_true(otel_collect_is_enabled("session"))
|
||||
expect_false(otel_collect_is_enabled("reactive_update"))
|
||||
})
|
||||
|
||||
test_that("otel_collect_is_enabled defaults to 'all' when no option or env var", {
|
||||
# Remove both option and env var
|
||||
withr::local_options(list(shiny.otel.collect = NULL))
|
||||
withr::local_envvar(list(SHINY_OTEL_COLLECT = NA))
|
||||
|
||||
# Should default to "all"
|
||||
expect_true(otel_collect_is_enabled("all"))
|
||||
expect_true(otel_collect_is_enabled("reactivity"))
|
||||
expect_true(otel_collect_is_enabled("none"))
|
||||
})
|
||||
|
||||
# Tests for as_otel_collect()
|
||||
test_that("as_otel_collect validates and returns valid collect levels", {
|
||||
expect_equal(as_otel_collect("none"), "none")
|
||||
expect_equal(as_otel_collect("session"), "session")
|
||||
expect_equal(as_otel_collect("reactive_update"), "reactive_update")
|
||||
expect_equal(as_otel_collect("reactivity"), "reactivity")
|
||||
expect_equal(as_otel_collect("all"), "all")
|
||||
})
|
||||
|
||||
test_that("as_otel_collect uses default value", {
|
||||
expect_equal(as_otel_collect(), "all")
|
||||
})
|
||||
|
||||
test_that("as_otel_collect errors on invalid input types", {
|
||||
expect_error(as_otel_collect(123), "`collect` must be a character vector.")
|
||||
expect_error(as_otel_collect(NULL), "`collect` must be a character vector.")
|
||||
expect_error(as_otel_collect(TRUE), "`collect` must be a character vector.")
|
||||
expect_error(as_otel_collect(list("all")), "`collect` must be a character vector.")
|
||||
})
|
||||
|
||||
test_that("as_otel_collect errors on invalid collect levels", {
|
||||
expect_error(as_otel_collect("invalid"), "'arg' should be one of")
|
||||
expect_error(as_otel_collect("unknown"), "'arg' should be one of")
|
||||
expect_error(as_otel_collect(""), "'arg' should be one of")
|
||||
})
|
||||
|
||||
test_that("as_otel_collect errors on multiple values", {
|
||||
# match.arg with several.ok = FALSE should error on multiple values
|
||||
expect_error(as_otel_collect(c("all", "none")), "'arg' must be of length 1")
|
||||
expect_error(as_otel_collect(c("session", "reactivity")), "'arg' must be of length 1")
|
||||
})
|
||||
@@ -39,7 +39,7 @@ test_server_with_otel_error <- function(session, server, expr, sanitize = FALSE,
|
||||
|
||||
withr::with_options(
|
||||
list(
|
||||
shiny.otel.bind = "all",
|
||||
shiny.otel.collect = "all",
|
||||
shiny.otel.sanitize.errors = sanitize
|
||||
),
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Tests for label methods used in otel-bind.R
|
||||
# Tests for label methods used in otel-collect.R
|
||||
test_that("otel_span_label_reactive generates correct labels", {
|
||||
# Create mock reactive with observable attribute
|
||||
x_reactive <- reactive({ 42 })
|
||||
|
||||
@@ -46,7 +46,7 @@ test_server_with_otel <- function(session, server, expr, bind = "all", args = li
|
||||
stopifnot(inherits(session, "MockShinySession"))
|
||||
stopifnot(is.function(server))
|
||||
|
||||
withr::with_options(list(shiny.otel.bind = bind), {
|
||||
withr::with_options(list(shiny.otel.collect = bind), {
|
||||
info <- with_shiny_otel_record({
|
||||
# rlang quosure magic to capture and pass through `expr`
|
||||
testServer(server, {{ expr }}, args = args, session = session)
|
||||
|
||||
@@ -11,8 +11,8 @@ create_mock_otel_span <- function(name, attributes = NULL, ended = FALSE) {
|
||||
test_that("otel_span_reactive_update_init returns early when otel not enabled", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
# Convince has_otel_bind to return FALSE
|
||||
withr::local_options(list(shiny.otel.bind = "none"))
|
||||
# Convince has_otel_collect to return FALSE
|
||||
withr::local_options(list(shiny.otel.collect = "none"))
|
||||
|
||||
# Should return early without creating span
|
||||
result <- otel_span_reactive_update_init(domain = domain)
|
||||
@@ -37,10 +37,10 @@ test_that("otel_span_reactive_update_init sets up session cleanup on first call"
|
||||
)
|
||||
domain <- TestMockShinySession$new()
|
||||
|
||||
withr::local_options(list(shiny.otel.bind = "reactive_update"))
|
||||
withr::local_options(list(shiny.otel.collect = "reactive_update"))
|
||||
|
||||
local_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update",
|
||||
has_otel_collect = function(level) level == "reactive_update",
|
||||
start_otel_span = function(name, ..., attributes = NULL) create_mock_otel_span(name, attributes = attributes),
|
||||
otel_session_id_attrs = function(domain) list(session_id = "mock-session-id")
|
||||
)
|
||||
@@ -64,7 +64,7 @@ test_that("otel_span_reactive_update_init errors when span already exists", {
|
||||
domain$userData[["_otel_span_reactive_update"]] <- existing_otel_span
|
||||
|
||||
local_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update"
|
||||
has_otel_collect = function(level) level == "reactive_update"
|
||||
)
|
||||
|
||||
expect_error(
|
||||
@@ -94,7 +94,7 @@ test_that("otel_span_reactive_update_init doesn't setup cleanup twice", {
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- TRUE
|
||||
|
||||
local_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update",
|
||||
has_otel_collect = function(level) level == "reactive_update",
|
||||
start_otel_span = function(...) create_mock_otel_span("reactive_update")
|
||||
)
|
||||
|
||||
@@ -199,7 +199,7 @@ test_that("session cleanup callback works correctly", {
|
||||
mock_otel_span <- create_mock_otel_span("reactive_update")
|
||||
|
||||
with_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update",
|
||||
has_otel_collect = function(level) level == "reactive_update",
|
||||
start_otel_span = function(...) mock_otel_span,
|
||||
otel_session_id_attrs = function(domain) list(session_id = "test"),
|
||||
{
|
||||
|
||||
@@ -44,8 +44,8 @@ test_that("otel_span_session_start returns early when otel not enabled", {
|
||||
domain <- create_mock_session_domain()
|
||||
test_value <- "initial"
|
||||
|
||||
# Mock has_otel_bind to return FALSE
|
||||
withr::local_options(list(shiny.otel.bind = "none"))
|
||||
# Mock has_otel_collect to return FALSE
|
||||
withr::local_options(list(shiny.otel.collect = "none"))
|
||||
|
||||
result <- otel_span_session_start({
|
||||
test_value <- "modified"
|
||||
@@ -67,7 +67,7 @@ test_that("otel_span_session_start sets up session end callback", {
|
||||
test_value <- "initial"
|
||||
|
||||
# Mock dependencies
|
||||
withr::local_options(list(shiny.otel.bind = "session"))
|
||||
withr::local_options(list(shiny.otel.collect = "session"))
|
||||
|
||||
local_mocked_bindings(
|
||||
as_attributes = function(x) x,
|
||||
@@ -75,7 +75,7 @@ test_that("otel_span_session_start sets up session end callback", {
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "session",
|
||||
has_otel_collect = function(level) level == "session",
|
||||
otel_session_id_attrs = function(domain) list(session.id = domain$token),
|
||||
otel_session_attrs = function(domain) list(PATH_INFO = "/app"),
|
||||
with_otel_span = function(name, expr, attributes = NULL) {
|
||||
@@ -105,8 +105,8 @@ test_that("otel_span_session_end returns early when otel not enabled", {
|
||||
domain <- create_mock_session_domain()
|
||||
test_value <- "initial"
|
||||
|
||||
# Mock has_otel_bind to return FALSE
|
||||
withr::local_options(list(shiny.otel.bind = "none"))
|
||||
# Mock has_otel_collect to return FALSE
|
||||
withr::local_options(list(shiny.otel.collect = "none"))
|
||||
|
||||
result <- otel_span_session_end({
|
||||
test_value <- "modified"
|
||||
@@ -124,10 +124,10 @@ test_that("otel_span_session_end creates span when enabled", {
|
||||
test_value <- "initial"
|
||||
|
||||
# Mock dependencies
|
||||
withr::local_options(list(shiny.otel.bind = "session"))
|
||||
withr::local_options(list(shiny.otel.collect = "session"))
|
||||
|
||||
with_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "session",
|
||||
has_otel_collect = function(level) level == "session",
|
||||
otel_session_id_attrs = function(domain) list(session.id = domain$token),
|
||||
with_otel_span = function(name, expr, attributes = NULL) {
|
||||
span_created <<- TRUE
|
||||
@@ -252,7 +252,7 @@ test_that("integration test - session start with full request", {
|
||||
span_attributes <- NULL
|
||||
|
||||
# Mock dependencies
|
||||
withr::local_options(list(shiny.otel.bind = "session"))
|
||||
withr::local_options(list(shiny.otel.collect = "session"))
|
||||
|
||||
local_mocked_bindings(
|
||||
as_attributes = function(x) x,
|
||||
@@ -260,7 +260,7 @@ test_that("integration test - session start with full request", {
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "session",
|
||||
has_otel_collect = function(level) level == "session",
|
||||
otel_session_id_attrs = otel_session_id_attrs, # Use real function
|
||||
otel_session_attrs = otel_session_attrs, # Use real function
|
||||
with_otel_span = function(name, expr, attributes = NULL) {
|
||||
|
||||
Reference in New Issue
Block a user