mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 16:08:19 -05:00
Merge branch 'main' into rc-v1.12.0
This commit is contained in:
@@ -91,7 +91,7 @@ Imports:
|
||||
lifecycle (>= 0.2.0),
|
||||
mime (>= 0.3),
|
||||
otel,
|
||||
promises (>= 1.4.0),
|
||||
promises (>= 1.5.0),
|
||||
R6 (>= 2.0),
|
||||
rlang (>= 0.4.10),
|
||||
sourcetools,
|
||||
|
||||
@@ -387,14 +387,13 @@ importFrom(lifecycle,is_present)
|
||||
importFrom(promises,"%...!%")
|
||||
importFrom(promises,"%...>%")
|
||||
importFrom(promises,as.promise)
|
||||
importFrom(promises,hybrid_then)
|
||||
importFrom(promises,is.promise)
|
||||
importFrom(promises,is.promising)
|
||||
importFrom(promises,local_ospan_promise_domain)
|
||||
importFrom(promises,promise)
|
||||
importFrom(promises,new_promise_domain)
|
||||
importFrom(promises,promise_reject)
|
||||
importFrom(promises,promise_resolve)
|
||||
importFrom(promises,then)
|
||||
importFrom(promises,with_ospan_async)
|
||||
importFrom(promises,with_ospan_promise_domain)
|
||||
importFrom(promises,with_promise_domain)
|
||||
importFrom(rlang,"%||%")
|
||||
importFrom(rlang,"fn_body<-")
|
||||
importFrom(rlang,"fn_fmls<-")
|
||||
|
||||
@@ -520,8 +520,7 @@ bindCache.reactiveExpr <- function(x, ..., cache = "app") {
|
||||
|
||||
local({
|
||||
impl <- attr(res, "observable", exact = TRUE)
|
||||
impl$.otelAttrs <- x_otel_attrs
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(impl$.otelAttrs, call_srcref)
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref)
|
||||
})
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
|
||||
@@ -240,8 +240,7 @@ bindEvent.reactiveExpr <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE
|
||||
|
||||
local({
|
||||
impl <- attr(res, "observable", exact = TRUE)
|
||||
impl$.otelAttrs <- x_otel_attrs
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(impl$.otelAttrs, call_srcref)
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref)
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -134,7 +134,9 @@ getCallCategories <- function(calls) {
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
captureStackTraces <- function(expr) {
|
||||
promises::with_promise_domain(createStackTracePromiseDomain(),
|
||||
# Use `promises::` as it shows up in the stack trace
|
||||
promises::with_promise_domain(
|
||||
createStackTracePromiseDomain(),
|
||||
expr
|
||||
)
|
||||
}
|
||||
@@ -184,7 +186,7 @@ createStackTracePromiseDomain <- function() {
|
||||
# These are actually stateless, we wouldn't have to create a new one each time
|
||||
# if we didn't want to. They're pretty cheap though.
|
||||
|
||||
d <- promises::new_promise_domain(
|
||||
d <- new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
# Subscription time
|
||||
@@ -278,7 +280,7 @@ withLogErrors <- function(expr,
|
||||
result <- captureStackTraces(expr)
|
||||
|
||||
# Handle expr being an async operation
|
||||
if (promises::is.promise(result)) {
|
||||
if (is.promise(result)) {
|
||||
result <- promises::catch(result, function(cond) {
|
||||
# Don't print shiny.silent.error (i.e. validation errors)
|
||||
if (cnd_inherits(cond, "shiny.silent.error")) {
|
||||
|
||||
@@ -136,25 +136,13 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
call_srcref,
|
||||
defaultLabel = "<anonymous>"
|
||||
)
|
||||
private$otel_label <- otel_label_extended_task(label, domain = domain)
|
||||
private$otel_label_add_to_queue <- otel_label_extended_task_add_to_queue(label, domain = domain)
|
||||
private$otel_span_label <- otel_span_label_extended_task(label, domain = domain)
|
||||
private$otel_log_label_add_to_queue <- otel_log_label_extended_task_add_to_queue(label, domain = domain)
|
||||
|
||||
private$otel_attrs <- c(
|
||||
otel_srcref_attributes(call_srcref),
|
||||
otel_session_id_attrs(domain)
|
||||
) %||% list()
|
||||
|
||||
set_rv_label <- function(rv, suffix) {
|
||||
impl <- attr(rv, ".impl", exact = TRUE)
|
||||
impl$.otelLabel <- otel_label_extended_task_set_reactive_val(
|
||||
label,
|
||||
suffix,
|
||||
domain = domain
|
||||
)
|
||||
}
|
||||
set_rv_label(private$rv_status, "status")
|
||||
set_rv_label(private$rv_value, "value")
|
||||
set_rv_label(private$rv_error, "error")
|
||||
},
|
||||
#' @description
|
||||
#' Starts executing the long-running operation. If this `ExtendedTask` is
|
||||
@@ -175,7 +163,7 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
private$invocation_queue$size() > 0
|
||||
) {
|
||||
otel_log(
|
||||
private$otel_label_add_to_queue,
|
||||
private$otel_log_add_to_queue_label,
|
||||
severity = "debug",
|
||||
attributes = c(
|
||||
private$otel_attrs,
|
||||
@@ -188,11 +176,11 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
} else {
|
||||
|
||||
if (has_otel_bind("reactivity")) {
|
||||
private$ospan <- create_shiny_ospan(
|
||||
private$otel_label,
|
||||
private$otel_span <- start_otel_span(
|
||||
private$otel_span_label,
|
||||
attributes = private$otel_attrs
|
||||
)
|
||||
otel::local_active_span(private$ospan)
|
||||
otel::local_active_span(private$otel_span)
|
||||
}
|
||||
|
||||
private$do_invoke(args, call = call)
|
||||
@@ -262,33 +250,34 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
rv_error = NULL,
|
||||
invocation_queue = NULL,
|
||||
|
||||
otel_label = NULL,
|
||||
otel_span_label = NULL,
|
||||
otel_log_label_add_to_queue = NULL,
|
||||
otel_attrs = list(),
|
||||
otel_label_add_to_queue = NULL,
|
||||
ospan = NULL,
|
||||
otel_span = NULL,
|
||||
|
||||
do_invoke = function(args, call = NULL) {
|
||||
private$rv_status("running")
|
||||
private$rv_value(NULL)
|
||||
private$rv_error(NULL)
|
||||
|
||||
p <- promises::promise_resolve(
|
||||
p <- promise_resolve(
|
||||
maskReactiveContext(do.call(private$func, args))
|
||||
)
|
||||
|
||||
p <- promises::then(
|
||||
p,
|
||||
onFulfilled = function(value, .visible) {
|
||||
if (is_ospan(private$ospan)) {
|
||||
private$ospan$end(status_code = "ok")
|
||||
private$ospan <- NULL
|
||||
if (is_otel_span(private$otel_span)) {
|
||||
|
||||
private$otel_span$end(status_code = "ok")
|
||||
private$otel_span <- NULL
|
||||
}
|
||||
private$on_success(list(value = value, visible = .visible))
|
||||
},
|
||||
onRejected = function(error) {
|
||||
if (is_ospan(private$ospan)) {
|
||||
private$ospan$end(status_code = "error")
|
||||
private$ospan <- NULL
|
||||
if (is_otel_span(private$otel_span)) {
|
||||
private$otel_span$end(status_code = "error")
|
||||
private$otel_span <- NULL
|
||||
}
|
||||
private$on_error(error, call = call)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
createGraphicsDevicePromiseDomain <- function(which = dev.cur()) {
|
||||
force(which)
|
||||
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
function(...) {
|
||||
|
||||
@@ -442,15 +442,15 @@ MockShinySession <- R6Class(
|
||||
prom <- NULL
|
||||
tryCatch({
|
||||
v <- private$withCurrentOutput(name, func(self, name))
|
||||
if (!promises::is.promise(v)){
|
||||
if (!is.promise(v)){
|
||||
# Make our sync value into a promise
|
||||
prom <- promises::promise(function(resolve, reject){ resolve(v) })
|
||||
prom <- promise_resolve(v)
|
||||
} else {
|
||||
prom <- v
|
||||
}
|
||||
}, error=function(e){
|
||||
# Error running value()
|
||||
prom <<- promises::promise(function(resolve, reject){ reject(e) })
|
||||
prom <<- promise_reject(e)
|
||||
})
|
||||
|
||||
private$outs[[name]]$promise <- hybrid_chain(
|
||||
@@ -718,7 +718,7 @@ MockShinySession <- R6Class(
|
||||
stop("Nested calls to withCurrentOutput() are not allowed.")
|
||||
}
|
||||
|
||||
promises::with_promise_domain(
|
||||
with_promise_domain(
|
||||
createVarPromiseDomain(private, "currentOutputName", name),
|
||||
expr
|
||||
)
|
||||
|
||||
@@ -41,23 +41,17 @@ get_call_srcref <- function(which_offset = 0) {
|
||||
}
|
||||
|
||||
|
||||
append_otel_attrs <- function(attrs, new_attrs) {
|
||||
if (is.null(new_attrs)) {
|
||||
return(attrs)
|
||||
}
|
||||
|
||||
attrs[names(new_attrs)] <- new_attrs
|
||||
|
||||
attrs
|
||||
}
|
||||
|
||||
append_otel_srcref_attrs <- function(attrs, call_srcref) {
|
||||
if (is.null(call_srcref)) {
|
||||
return(attrs)
|
||||
}
|
||||
|
||||
srcref_attrs <- otel_srcref_attributes(call_srcref)
|
||||
attrs <- append_otel_attrs(attrs, srcref_attrs)
|
||||
if (is.null(srcref_attrs)) {
|
||||
return(attrs)
|
||||
}
|
||||
|
||||
attrs[names(srcref_attrs)] <- srcref_attrs
|
||||
|
||||
attrs
|
||||
}
|
||||
|
||||
@@ -1,41 +1,3 @@
|
||||
# - OpenTelemetry -----------------------------------
|
||||
# * Integration locations:
|
||||
# * √ Server:
|
||||
# * Start reactive_update when reactive busy count > 0
|
||||
# * End reactive_update when reactive busy count == 0
|
||||
# * √ Reactives: val, values, expr, render fn, observe
|
||||
# * Combinations:
|
||||
# * √ debounce() / throttle()
|
||||
# * bindCache()
|
||||
# * √ bindEvent()
|
||||
# * X - bindProgress()
|
||||
# * Special functions:
|
||||
# * ExtendedTask()
|
||||
# * Extended task links to submission reactive
|
||||
# * Reactive update that gets result links to the extended task
|
||||
# * √ observeEvent()
|
||||
# * √ eventReactive()
|
||||
# * TODO: Not recording updates within the span!!
|
||||
# * Maybe enhance all `withReactiveDomain()` calls?
|
||||
# * Global options:
|
||||
# * √ shiny.otel.bind:
|
||||
# * "all", "none" - all or nothing
|
||||
# * "session" - Adds session start/end events
|
||||
# * "reactive_update" - Spans for any reactive update. (Includes `"session"` features).
|
||||
# * "reactivity" - Spans for all reactive things. (Includes `"reactive_update"` features).
|
||||
# * Private methods:
|
||||
# * bind_otel_*() - Methods that binds the reactive object to OpenTelemetry spans
|
||||
# * Note: When adding otel to an object, prepend a class of `FOO.otel`. Then add a dispatch method for `bindOtel.FOO.otel()` that declares the object already has been bound.
|
||||
# * with_no_otel_bind(expr) - Will not bind any reactives created within `expr` to OpenTelemetry spans.
|
||||
|
||||
# - TODO: -----------------------------------
|
||||
# * Span status for success/failure (render function and regular reactive exprs?)
|
||||
# * Error handling is not an "exception" for fatal logs
|
||||
# * Connect `user.id` to be their user name: https://opentelemetry.io/docs/specs/semconv/registry/attributes/user/
|
||||
# * Tests with otel recording
|
||||
|
||||
# ------------------------------------------
|
||||
|
||||
otel_bind_choices <- c(
|
||||
"none",
|
||||
"session",
|
||||
@@ -151,8 +113,8 @@ as_otel_bind <- function(bind = "all") {
|
||||
#' @section Span management and performance:
|
||||
#'
|
||||
#' Dev note - Barret 2025-10:
|
||||
#' Typically, an OpenTelemetry span (ospan) will inherit from the parent span.
|
||||
#' This works well and we can think of the hierarchy as a tree. With
|
||||
#' 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
|
||||
#' dial how much of the tree we are interested in: "none", "session",
|
||||
#' "reactive_update", "reactivity", and finally "all".
|
||||
@@ -230,7 +192,7 @@ bind_otel_reactive_expr <- function(x) {
|
||||
impl <- attr(x, "observable", exact = TRUE)
|
||||
impl$.isRecordingOtel <- TRUE
|
||||
# Covers both reactive and reactive.event
|
||||
impl$.otelLabel <- ospan_label_reactive(x, domain = impl$.domain)
|
||||
impl$.otelLabel <- otel_span_label_reactive(x, domain = impl$.domain)
|
||||
|
||||
class(x) <- c("reactiveExpr.otel", class(x))
|
||||
|
||||
@@ -239,7 +201,7 @@ bind_otel_reactive_expr <- function(x) {
|
||||
|
||||
bind_otel_observe <- function(x) {
|
||||
x$.isRecordingOtel <- TRUE
|
||||
x$.otelLabel <- ospan_label_observer(x, domain = x$.domain)
|
||||
x$.otelLabel <- otel_span_label_observer(x, domain = x$.domain)
|
||||
|
||||
class(x) <- c("Observer.otel", class(x))
|
||||
invisible(x)
|
||||
@@ -250,32 +212,32 @@ bind_otel_observe <- function(x) {
|
||||
bind_otel_shiny_render_function <- function(x) {
|
||||
|
||||
valueFunc <- force(x)
|
||||
span_label <- NULL
|
||||
ospan_attrs <- NULL
|
||||
otel_span_label <- NULL
|
||||
otel_span_attrs <- NULL
|
||||
|
||||
renderFunc <- function(...) {
|
||||
# Dynamically determine the span label given the current reactive domain
|
||||
if (is.null(span_label)) {
|
||||
if (is.null(otel_span_label)) {
|
||||
domain <- getDefaultReactiveDomain()
|
||||
span_label <<-
|
||||
ospan_label_render_function(x, domain = domain)
|
||||
ospan_attrs <<- c(
|
||||
otel_span_label <<-
|
||||
otel_span_label_render_function(x, domain = domain)
|
||||
otel_span_attrs <<- c(
|
||||
attr(x, "otelAttrs"),
|
||||
otel_session_id_attrs(domain)
|
||||
)
|
||||
}
|
||||
|
||||
with_shiny_ospan_async(
|
||||
span_label,
|
||||
with_otel_span(
|
||||
otel_span_label,
|
||||
{
|
||||
promises::hybrid_then(
|
||||
hybrid_then(
|
||||
valueFunc(...),
|
||||
on_failure = set_ospan_error_status_and_throw,
|
||||
on_failure = set_otel_exception_status_and_throw,
|
||||
# Must save the error object
|
||||
tee = FALSE
|
||||
)
|
||||
},
|
||||
attributes = ospan_attrs
|
||||
attributes = otel_span_attrs
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
|
||||
has_seen_ospan_error <- function(cnd) {
|
||||
isTRUE(cnd$.shiny_error_seen)
|
||||
has_seen_otel_exception <- function(cnd) {
|
||||
!is.null(cnd$.shiny_otel_exception)
|
||||
}
|
||||
|
||||
set_ospan_error_as_seen <- function(cnd) {
|
||||
cnd$.shiny_error_seen <- TRUE
|
||||
mark_otel_exception_as_seen <- function(cnd) {
|
||||
cnd$.shiny_otel_exception <- TRUE
|
||||
cnd
|
||||
}
|
||||
|
||||
set_ospan_error_status_and_throw <- function(cnd) {
|
||||
cnd <- set_ospan_error_status(cnd)
|
||||
set_otel_exception_status_and_throw <- function(cnd) {
|
||||
cnd <- set_otel_exception_status(cnd)
|
||||
|
||||
# Rethrow the (possibly updated) error
|
||||
signalCondition(cnd)
|
||||
}
|
||||
|
||||
set_ospan_error_status <- function(cnd) {
|
||||
set_otel_exception_status <- function(cnd) {
|
||||
if (inherits(cnd, "shiny.custom.error")) {
|
||||
# No-op
|
||||
} else if (inherits(cnd, "shiny.output.cancel")) {
|
||||
@@ -30,12 +30,12 @@ set_ospan_error_status <- function(cnd) {
|
||||
|
||||
# Only record the exception once at the original point of failure,
|
||||
# not every reactive expression that it passes through
|
||||
if (!has_seen_ospan_error(cnd)) {
|
||||
if (!has_seen_otel_exception(cnd)) {
|
||||
span$record_exception(
|
||||
# Record a sanitized error if sanitization is enabled
|
||||
get_otel_error_obj(cnd)
|
||||
)
|
||||
cnd <- set_ospan_error_as_seen(cnd)
|
||||
cnd <- mark_otel_exception_as_seen(cnd)
|
||||
}
|
||||
|
||||
# Record the error status on the span for any context touching this error
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# observe mymod:<anonymous>
|
||||
# observe <anonymous>
|
||||
# observe mylabel (edited)
|
||||
# observe mylabel
|
||||
|
||||
# -- Reactives --------------------------------------------------------------
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#' @noRd
|
||||
NULL
|
||||
|
||||
ospan_label_reactive <- function(x, ..., domain) {
|
||||
otel_span_label_reactive <- function(x, ..., domain) {
|
||||
fn_name <- otel_label_with_modifiers(
|
||||
x,
|
||||
"reactive",
|
||||
@@ -30,12 +30,12 @@ ospan_label_reactive <- function(x, ..., domain) {
|
||||
)
|
||||
|
||||
label <- attr(x, "observable", exact = TRUE)[[".label"]]
|
||||
ospan_label <- otel_label_upgrade(label, domain = domain)
|
||||
otel_span_label <- otel_label_upgrade(label, domain = domain)
|
||||
|
||||
sprintf("%s %s", fn_name, ospan_label)
|
||||
sprintf("%s %s", fn_name, otel_span_label)
|
||||
}
|
||||
|
||||
ospan_label_render_function <- function(x, ..., domain) {
|
||||
otel_span_label_render_function <- function(x, ..., domain) {
|
||||
fn_name <- otel_label_with_modifiers(
|
||||
x,
|
||||
"output",
|
||||
@@ -44,12 +44,12 @@ ospan_label_render_function <- function(x, ..., domain) {
|
||||
)
|
||||
|
||||
label <- getCurrentOutputInfo(session = domain)$name %||% "<unknown>"
|
||||
ospan_label <- otel_label_upgrade(label, domain = domain)
|
||||
otel_span_label <- otel_label_upgrade(label, domain = domain)
|
||||
|
||||
sprintf("%s %s", fn_name, ospan_label)
|
||||
sprintf("%s %s", fn_name, otel_span_label)
|
||||
}
|
||||
|
||||
ospan_label_observer <- function(x, ..., domain) {
|
||||
otel_span_label_observer <- function(x, ..., domain) {
|
||||
fn_name <- otel_label_with_modifiers(
|
||||
x,
|
||||
"observe",
|
||||
@@ -57,21 +57,21 @@ ospan_label_observer <- function(x, ..., domain) {
|
||||
event_class = "Observer.event"
|
||||
)
|
||||
|
||||
ospan_label <- otel_label_upgrade(x$.label, domain = domain)
|
||||
otel_span_label <- otel_label_upgrade(x$.label, domain = domain)
|
||||
|
||||
sprintf("%s %s", fn_name, ospan_label)
|
||||
sprintf("%s %s", fn_name, otel_span_label)
|
||||
}
|
||||
|
||||
# -- Set reactive value(s) ----------------------------------------------------
|
||||
|
||||
otel_label_set_reactive_val <- function(label, ..., domain) {
|
||||
otel_log_label_set_reactive_val <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"Set reactiveVal %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
|
||||
otel_label_set_reactive_values <- function(label, key, ..., domain) {
|
||||
otel_log_label_set_reactive_values <- function(label, key, ..., domain) {
|
||||
sprintf(
|
||||
"Set reactiveValues %s$%s",
|
||||
otel_label_upgrade(label, domain = domain),
|
||||
@@ -81,23 +81,16 @@ otel_label_set_reactive_values <- function(label, key, ..., domain) {
|
||||
|
||||
# -- ExtendedTask -------------------------------------------------------------
|
||||
|
||||
otel_label_extended_task <- function(label, suffix = NULL, ..., domain) {
|
||||
otel_span_label_extended_task <- function(label, suffix = NULL, ..., domain) {
|
||||
sprintf(
|
||||
"ExtendedTask %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
otel_label_extended_task_add_to_queue <- function(label, ..., domain) {
|
||||
otel_log_label_extended_task_add_to_queue <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"%s add to queue",
|
||||
otel_label_extended_task(label, domain = domain)
|
||||
)
|
||||
}
|
||||
otel_label_extended_task_set_reactive_val <- function(label, name, ..., domain) {
|
||||
sprintf(
|
||||
"Set %s %s",
|
||||
otel_label_extended_task(label, domain = domain),
|
||||
name
|
||||
"ExtendedTask %s add to queue",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,79 +1,59 @@
|
||||
OSPAN_REACTIVE_UPDATE_NAME <- "reactive_update"
|
||||
|
||||
# * `session$userData[["_otel_reactive_update_ospan"]]` - The active reactive update span (or `NULL`)
|
||||
# * `session$userData[["_otel_has_reactive_cleanup"]]` - Whether the reactive span cleanup has been set
|
||||
|
||||
has_reactive_ospan_cleanup <- function(domain) {
|
||||
isTRUE(domain$userData[["_otel_has_reactive_cleanup"]])
|
||||
}
|
||||
set_reactive_ospan_cleanup <- function(domain) {
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- TRUE
|
||||
}
|
||||
# * `session$userData[["_otel_span_reactive_update"]]` - The active reactive update span (or `NULL`)
|
||||
|
||||
|
||||
reactive_update_ospan_is_active <- function(domain) {
|
||||
isTRUE(domain$userData[["_otel_reactive_update_is_active"]])
|
||||
}
|
||||
set_reactive_ospan_is_active <- function(domain) {
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- TRUE
|
||||
}
|
||||
clear_reactive_ospan_is_active <- function(domain) {
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- NULL
|
||||
}
|
||||
|
||||
#' Create a `reactive_update` OpenTelemetry span
|
||||
#' Start a `reactive_update` OpenTelemetry span and store it
|
||||
#'
|
||||
#' Used when a reactive expression is updated
|
||||
#' Will only start the span iff the otel tracing is enabled
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @return Invisibly returns.
|
||||
#' @seealso `end_reactive_update_ospan()`
|
||||
#' @seealso `otel_span_reactive_update_teardown()`
|
||||
#' @noRd
|
||||
create_reactive_update_ospan <- function(..., domain) {
|
||||
otel_span_reactive_update_init <- function(..., domain) {
|
||||
|
||||
if (!has_otel_bind("reactive_update")) return()
|
||||
|
||||
if (!has_reactive_ospan_cleanup(domain)) {
|
||||
# Clean up any dangling reactive span
|
||||
# Ensure cleanup is registered only once per session
|
||||
if (is.null(domain$userData[["_otel_has_reactive_cleanup"]])) {
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- TRUE
|
||||
|
||||
# Clean up any dangling reactive spans on an unplanned exit
|
||||
domain$onSessionEnded(function() {
|
||||
if (has_reactive_ospan_cleanup(domain)) {
|
||||
end_reactive_update_ospan(domain = domain)
|
||||
}
|
||||
otel_span_reactive_update_teardown(domain = domain)
|
||||
})
|
||||
set_reactive_ospan_cleanup(domain)
|
||||
}
|
||||
|
||||
prev_ospan <- domain$userData[["_otel_reactive_update_ospan"]]
|
||||
if (is_ospan(prev_ospan)) {
|
||||
# Safety check
|
||||
if (is_otel_span(domain$userData[["_otel_span_reactive_update"]])) {
|
||||
stop("Reactive update span already exists")
|
||||
}
|
||||
|
||||
reactive_update_ospan <- create_shiny_ospan(
|
||||
OSPAN_REACTIVE_UPDATE_NAME,
|
||||
...,
|
||||
# options = list(
|
||||
# parent = NA # Always start a new root span
|
||||
# ),
|
||||
attributes = otel_session_id_attrs(domain)
|
||||
)
|
||||
domain$userData[["_otel_span_reactive_update"]] <-
|
||||
start_otel_span(
|
||||
"reactive_update",
|
||||
...,
|
||||
attributes = otel_session_id_attrs(domain)
|
||||
)
|
||||
|
||||
domain$userData[["_otel_reactive_update_ospan"]] <- reactive_update_ospan
|
||||
return(invisible())
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' End a `reactive_update` OpenTelemetry span
|
||||
#' End a `reactive_update` OpenTelemetry span and remove it from the session
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @return Invisibly returns.
|
||||
#' @seealso `create_reactive_update_ospan()`
|
||||
#' @seealso `otel_span_reactive_update_init()`
|
||||
#' @noRd
|
||||
end_reactive_update_ospan <- function(..., domain) {
|
||||
otel_span_reactive_update_teardown <- function(..., domain) {
|
||||
ospan <- domain$userData[["_otel_span_reactive_update"]]
|
||||
|
||||
reactive_update_ospan <- domain$userData[["_otel_reactive_update_ospan"]]
|
||||
if (is_ospan(reactive_update_ospan)) {
|
||||
otel::end_span(reactive_update_ospan)
|
||||
domain$userData[["_otel_reactive_update_ospan"]] <- NULL
|
||||
if (is_otel_span(ospan)) {
|
||||
otel::end_span(ospan)
|
||||
domain$userData[["_otel_span_reactive_update"]] <- NULL
|
||||
}
|
||||
|
||||
invisible()
|
||||
}
|
||||
|
||||
|
||||
@@ -85,42 +65,47 @@ end_reactive_update_ospan <- function(..., domain) {
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @noRd
|
||||
with_reactive_update_active_ospan <- function(expr, ..., domain) {
|
||||
reactive_update_ospan <- domain$userData[["_otel_reactive_update_ospan"]]
|
||||
with_otel_span_reactive_update <- function(expr, ..., domain) {
|
||||
ospan <- domain$userData[["_otel_span_reactive_update"]]
|
||||
|
||||
if (!is_ospan(reactive_update_ospan)) {
|
||||
if (!is_otel_span(ospan)) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
# Given the reactive span is started before and ended when exec count is 0,
|
||||
# we only need to wrap the expr in the span context
|
||||
otel::with_active_span(reactive_update_ospan, {force(expr)})
|
||||
# Given the reactive update span is started before and ended when exec count
|
||||
# is 0, we only need to wrap the expr in the span context
|
||||
otel::with_active_span(ospan, {force(expr)})
|
||||
}
|
||||
|
||||
|
||||
#' Run expr within `reactive_update` ospan if not already active
|
||||
#' Run expr within `reactive_update` otel span if not already active
|
||||
#'
|
||||
#' If the reactive update ospan is not already active, run the expression
|
||||
#' within the reactive update ospan context. This ensures that nested calls
|
||||
#' If the reactive update otel span is not already active, run the expression
|
||||
#' within the reactive update otel span context. This ensures that nested calls
|
||||
#' to reactive expressions do not attempt to re-enter the same span.
|
||||
#'
|
||||
#' This method is used within Context `run()` and running an Output's observer
|
||||
#' implementation
|
||||
#' @param expr The expression to executed within the span
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @noRd
|
||||
maybe_with_reactive_update_active_ospan <- function(expr, ..., domain) {
|
||||
if (!reactive_update_ospan_is_active(domain)) {
|
||||
set_reactive_ospan_is_active(domain)
|
||||
maybe_with_otel_span_reactive_update <- function(expr, ..., domain) {
|
||||
if (is.null(domain$userData[["_otel_reactive_update_is_active"]])) {
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- TRUE
|
||||
|
||||
promises::hybrid_then(
|
||||
# When the expression is done promising, clear the active flag
|
||||
hybrid_then(
|
||||
{
|
||||
with_reactive_update_active_ospan(domain = domain, expr)
|
||||
with_otel_span_reactive_update(domain = domain, expr)
|
||||
},
|
||||
on_success = function(value) {
|
||||
clear_reactive_ospan_is_active(domain)
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- NULL
|
||||
},
|
||||
on_failure = function(e) {
|
||||
clear_reactive_ospan_is_active(domain)
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- NULL
|
||||
},
|
||||
# Return the value before the callbacks
|
||||
tee = TRUE
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -10,33 +10,31 @@
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain
|
||||
#' @noRd
|
||||
use_session_start_ospan_async <- function(expr, ..., domain) {
|
||||
otel_span_session_start <- function(expr, ..., domain) {
|
||||
|
||||
if (!has_otel_bind("session")) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
id_attrs <- otel_session_id_attrs(domain)
|
||||
|
||||
# Wrap the server initialization
|
||||
with_shiny_ospan_async(
|
||||
with_otel_span(
|
||||
"session_start",
|
||||
expr,
|
||||
attributes = otel::as_attributes(c(
|
||||
id_attrs,
|
||||
otel_session_id_attrs(domain),
|
||||
otel_session_attrs(domain)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
with_session_end_ospan_async <- function(expr, ..., domain) {
|
||||
otel_span_session_end <- function(expr, ..., domain) {
|
||||
if (!has_otel_bind("session")) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
id_attrs <- otel_session_id_attrs(domain)
|
||||
with_shiny_ospan_async(
|
||||
with_otel_span(
|
||||
"session_end",
|
||||
expr,
|
||||
attributes = id_attrs
|
||||
@@ -48,25 +46,33 @@ with_session_end_ospan_async <- function(expr, ..., domain) {
|
||||
|
||||
# Occurs when the websocket connection is established
|
||||
otel_session_attrs <- function(domain) {
|
||||
# TODO: Future: Posit Connect integration
|
||||
# > we are still trying to identify all of the information we want to track/expose
|
||||
#
|
||||
# * `POSIT_PRODUCT` (Fallback to RSTUDIO_PRODUCT) for host environment
|
||||
# * `CONNECT_SERVER` envvar to get the `session.address`.
|
||||
# * `CONNECT_CONTENT_GUID` for the consistent app distinguisher
|
||||
# * Maybe `CONNECT_CONTENT_JOB_KEY`?
|
||||
# * Maybe `user.id` to be their user name: https://opentelemetry.io/docs/specs/semconv/registry/attributes/user/
|
||||
attrs <- list(
|
||||
PATH_INFO =
|
||||
server.path =
|
||||
sub(
|
||||
"/websocket/$", "/",
|
||||
domain[["request"]][["PATH_INFO"]] %||% ""
|
||||
),
|
||||
HTTP_HOST = domain[["request"]][["HTTP_HOST"]] %||% "",
|
||||
HTTP_ORIGIN = domain[["request"]][["HTTP_ORIGIN"]] %||% "",
|
||||
server.address = domain[["request"]][["HTTP_HOST"]] %||% "",
|
||||
server.origin = domain[["request"]][["HTTP_ORIGIN"]] %||% "",
|
||||
## Currently, Shiny does not expose QUERY_STRING when connecting the websocket
|
||||
# so we do not provide it here.
|
||||
# QUERY_STRING = domain[["request"]][["QUERY_STRING"]] %||% "",
|
||||
SERVER_PORT = domain[["request"]][["SERVER_PORT"]] %||% NA_integer_
|
||||
server.port = domain[["request"]][["SERVER_PORT"]] %||% NA_integer_
|
||||
)
|
||||
# Safely convert SERVER_PORT to integer
|
||||
# If conversion fails, leave as-is (string or empty)
|
||||
# This avoids warnings/errors if SERVER_PORT is not a valid integer
|
||||
server_port <- suppressWarnings(as.integer(attrs$SERVER_PORT))
|
||||
server_port <- suppressWarnings(as.integer(attrs$server.port))
|
||||
if (!is.na(server_port)) {
|
||||
attrs$SERVER_PORT <- server_port
|
||||
attrs$server.port <- server_port
|
||||
}
|
||||
|
||||
attrs
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
#' @importFrom promises
|
||||
#' with_ospan_async
|
||||
#' with_ospan_promise_domain
|
||||
#' local_ospan_promise_domain
|
||||
NULL
|
||||
|
||||
# Used by otel to identify the tracer and logger for this package
|
||||
# https://github.com/r-lib/otel/blob/afc31bc1f4bd177870d44b051ada1d9e4e685346/R/tracer-name.R#L33-L49
|
||||
# DO NOT CHANGE THIS VALUE without understanding the implications for existing telemetry data!
|
||||
otel_tracer_name <- "co.posit.r-package.shiny"
|
||||
|
||||
with_shiny_ospan_async <- function(name, expr, ..., attributes = NULL) {
|
||||
with_ospan_async(name, expr, ..., attributes = attributes, tracer = get_tracer())
|
||||
#' Create and use a Shiny OpenTelemetry span
|
||||
#'
|
||||
#' If otel is disabled, the span will not be created,
|
||||
#' however the expression will still be evaluated.
|
||||
#' @param name Span name
|
||||
#' @param expr Expression to evaluate within the span
|
||||
#' @param ... Ignored
|
||||
#' @param attributes Optional span attributes
|
||||
#' @return The result of evaluating `expr`
|
||||
#' @noRd
|
||||
with_otel_span <- function(name, expr, ..., attributes = NULL) {
|
||||
promises::with_otel_span(name, expr, ..., attributes = attributes, tracer = shiny_otel_tracer())
|
||||
}
|
||||
|
||||
create_shiny_ospan <- function(name, ...) {
|
||||
otel::start_span(name, ..., tracer = get_tracer())
|
||||
|
||||
#' Start a Shiny OpenTelemetry span
|
||||
#'
|
||||
#' @param name Span name
|
||||
#' @param ... Additional arguments passed to `otel::start_span()`
|
||||
#' @return An OpenTelemetry span
|
||||
#' @noRd
|
||||
start_otel_span <- function(name, ...) {
|
||||
otel::start_span(name, ..., tracer = shiny_otel_tracer())
|
||||
}
|
||||
|
||||
|
||||
# # TODO: Set attributes on the current active span
|
||||
# # 5. Set attributes on the current active span
|
||||
# set_ospan_attrs(status = 200L)
|
||||
# set_otel_span_attrs(status = 200L)
|
||||
|
||||
|
||||
# -- Helpers --------------------------------------------------------------
|
||||
|
||||
|
||||
is_ospan <- function(x) {
|
||||
is_otel_span <- function(x) {
|
||||
inherits(x, "otel_span")
|
||||
}
|
||||
|
||||
@@ -32,26 +47,38 @@ testthat__is_testing <- function() {
|
||||
identical(Sys.getenv("TESTTHAT"), "true")
|
||||
}
|
||||
|
||||
#' Log a message using the Shiny OpenTelemetry logger
|
||||
#'
|
||||
#' @param msg The log message
|
||||
#' @param ... Additional attributes to add to the log record
|
||||
#' @param severity The log severity level (default: "info")
|
||||
#' @param logger The OpenTelemetry logger to use (default: Shiny otel logger)
|
||||
#' @return Invisibly returns.
|
||||
#' @noRd
|
||||
otel_log <- function(
|
||||
msg,
|
||||
...,
|
||||
severity = "info",
|
||||
logger = get_ospan_logger()
|
||||
logger = shiny_otel_logger()
|
||||
) {
|
||||
otel::log(msg, ..., severity = severity, logger = logger)
|
||||
}
|
||||
|
||||
otel_is_tracing_enabled <- function(tracer = get_tracer()) {
|
||||
#' Check if OpenTelemetry tracing is enabled
|
||||
#'
|
||||
#' @param tracer The OpenTelemetry tracer to check (default: Shiny otel tracer)
|
||||
#' @return `TRUE` if tracing is enabled, `FALSE` otherwise
|
||||
#' @noRd
|
||||
otel_is_tracing_enabled <- function(tracer = shiny_otel_tracer()) {
|
||||
otel::is_tracing_enabled(tracer)
|
||||
}
|
||||
otel_get_logger <- function() {
|
||||
otel::get_logger()
|
||||
}
|
||||
otel_get_tracer <- function() {
|
||||
otel::get_tracer()
|
||||
}
|
||||
|
||||
get_ospan_logger <- local({
|
||||
#' Shiny OpenTelemetry logger
|
||||
#'
|
||||
#' Used for logging OpenTelemetry events via `otel_log()`
|
||||
#' @return An OpenTelemetry logger
|
||||
#' @noRd
|
||||
shiny_otel_logger <- local({
|
||||
logger <- NULL
|
||||
|
||||
# For internal testing purposes only
|
||||
@@ -64,7 +91,7 @@ get_ospan_logger <- local({
|
||||
return(logger)
|
||||
}
|
||||
|
||||
this_logger <- otel_get_logger()
|
||||
this_logger <- otel::get_logger()
|
||||
|
||||
if (testthat__is_testing()) {
|
||||
# Don't cache the logger in unit tests. It interferes with logger provider
|
||||
@@ -78,9 +105,17 @@ get_ospan_logger <- local({
|
||||
|
||||
|
||||
|
||||
# Inspired by httr2:::get_tracer().
|
||||
# Using local scope avoids an environment object lookup on each call.
|
||||
get_tracer <- local({
|
||||
#' Shiny OpenTelemetry tracer
|
||||
#'
|
||||
#' Used for creating OpenTelemetry spans via `with_otel_span()` and
|
||||
#' `start_otel_span()`
|
||||
#'
|
||||
#' Inspired by httr2:::get_tracer().
|
||||
#' @return An OpenTelemetry tracer
|
||||
#' @noRd
|
||||
shiny_otel_tracer <- local({
|
||||
# Using local scope avoids an environment object lookup on each call.
|
||||
|
||||
tracer <- NULL
|
||||
|
||||
# For internal testing purposes only
|
||||
@@ -93,7 +128,7 @@ get_tracer <- local({
|
||||
return(tracer)
|
||||
}
|
||||
|
||||
this_tracer <- otel_get_tracer()
|
||||
this_tracer <- otel::get_tracer()
|
||||
|
||||
if (testthat__is_testing()) {
|
||||
# Don't cache the tracer in unit tests. It interferes with tracer provider
|
||||
|
||||
15
R/react.R
15
R/react.R
@@ -31,7 +31,7 @@ ctx_otel_info_obj <- function(
|
||||
)
|
||||
}
|
||||
|
||||
with_context_ospan_async <- function(otel_info, expr, domain) {
|
||||
with_otel_span_context <- function(otel_info, expr, domain) {
|
||||
if (!otel_is_tracing_enabled()) {
|
||||
return(force(expr))
|
||||
}
|
||||
@@ -43,16 +43,16 @@ with_context_ospan_async <- function(otel_info, expr, domain) {
|
||||
# Always set the reactive update span as active
|
||||
# This ensures that any spans created within the reactive context
|
||||
# are at least children of the reactive update span
|
||||
maybe_with_reactive_update_active_ospan(domain = domain, {
|
||||
maybe_with_otel_span_reactive_update(domain = domain, {
|
||||
if (isRecordingOtel) {
|
||||
with_shiny_ospan_async(
|
||||
with_otel_span(
|
||||
otelLabel,
|
||||
{
|
||||
# Works with both sync and async expressions
|
||||
# Needed for both observer and reactive contexts
|
||||
promises::hybrid_then(
|
||||
hybrid_then(
|
||||
expr,
|
||||
on_failure = set_ospan_error_status_and_throw,
|
||||
on_failure = set_otel_exception_status_and_throw,
|
||||
# Must upgrade the error object
|
||||
tee = FALSE
|
||||
)
|
||||
@@ -114,9 +114,10 @@ Context <- R6Class(
|
||||
run = function(func) {
|
||||
"Run the provided function under this context."
|
||||
|
||||
# Use `promises::` as it shows up in the stack trace
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
withReactiveDomain(.domain, {
|
||||
with_context_ospan_async(.otel_info, domain = .domain, {
|
||||
with_otel_span_context(.otel_info, domain = .domain, {
|
||||
captureStackTraces({
|
||||
env <- .getReactiveEnvironment()
|
||||
rLog$enter(.reactId, id, .reactType, .domain)
|
||||
@@ -296,7 +297,7 @@ wrapForContext <- function(func, ctx) {
|
||||
}
|
||||
|
||||
reactivePromiseDomain <- function() {
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
|
||||
|
||||
@@ -97,9 +97,11 @@ getDefaultReactiveDomain <- function() {
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
withReactiveDomain <- function(domain, expr) {
|
||||
# TODO: Integrate `promises:::with_otel_active_span_promise_domain(expr)`
|
||||
|
||||
promises::with_promise_domain(createVarPromiseDomain(.globals, "domain", domain), expr)
|
||||
# Use `promises::` as it shows up in the stack trace
|
||||
promises::with_promise_domain(
|
||||
createVarPromiseDomain(.globals, "domain", domain),
|
||||
expr
|
||||
)
|
||||
}
|
||||
|
||||
#
|
||||
|
||||
@@ -92,7 +92,7 @@ ReactiveVal <- R6Class(
|
||||
|
||||
domain <- getDefaultReactiveDomain()
|
||||
rLog$define(private$reactId, value, private$label, type = "reactiveVal", domain)
|
||||
.otelLabel <<- otel_label_set_reactive_val(private$label, domain = domain)
|
||||
.otelLabel <<- otel_log_label_set_reactive_val(private$label, domain = domain)
|
||||
},
|
||||
get = function() {
|
||||
private$dependents$register()
|
||||
@@ -446,10 +446,14 @@ ReactiveValues <- R6Class(
|
||||
}
|
||||
|
||||
if ((!is.null(domain)) && .isRecordingOtel) {
|
||||
# Do not include updates to input or clientData unless _some_ reactivity has occured
|
||||
if (has_reactive_ospan_cleanup(domain) || !(.label == "input" || .label == "clientData")) {
|
||||
if (
|
||||
# Any reactiveValues (other than input or clientData) are fair game
|
||||
!(.label == "input" || .label == "clientData") ||
|
||||
# Do not include updates to input or clientData unless _some_ reactivity has occured
|
||||
!is.null(domain$userData[["_otel_has_reactive_cleanup"]])
|
||||
) {
|
||||
otel_log(
|
||||
otel_label_set_reactive_values(.label, key, domain = domain),
|
||||
otel_log_label_set_reactive_values(.label, key, domain = domain),
|
||||
severity = "info",
|
||||
attributes = c(.otelAttrs, otel_session_id_attrs(domain))
|
||||
)
|
||||
@@ -1019,11 +1023,11 @@ Observable <- R6Class(
|
||||
error = function(cond) {
|
||||
if (.isRecordingOtel) {
|
||||
# `cond` is too early in the stack to be updated by `ctx`'s
|
||||
# `with_context_ospan_async()` where it calls
|
||||
# `set_ospan_error_status_and_throw()` on eval error.
|
||||
# `with_otel_span_context()` where it calls
|
||||
# `set_otel_exception_status_and_throw()` on eval error.
|
||||
# So we mark it as seen here.
|
||||
# When the error is re-thrown later, it won't be a _new_ error
|
||||
cond <- set_ospan_error_as_seen(cond)
|
||||
cond <- mark_otel_exception_as_seen(cond)
|
||||
}
|
||||
|
||||
# If an error occurs, we want to propagate the error, but we also
|
||||
@@ -2209,7 +2213,7 @@ isolate <- function(expr) {
|
||||
reactId <- rLog$noReactId
|
||||
}
|
||||
|
||||
# Do not track ospans for `isolate()`
|
||||
# Do not track otel spans for `isolate()`
|
||||
ctx <- Context$new(getDefaultReactiveDomain(), '[isolate]', type='isolate', reactId = reactId)
|
||||
on.exit(ctx$invalidate())
|
||||
# Matching ..stacktraceon../..stacktraceoff.. pair
|
||||
|
||||
@@ -253,7 +253,7 @@ drawPlot <- function(name, session, func, width, height, alt, pixelratio, res, .
|
||||
|
||||
hybrid_chain(
|
||||
hybrid_chain(
|
||||
promises::with_promise_domain(domain, {
|
||||
with_promise_domain(domain, {
|
||||
hybrid_chain(
|
||||
func(),
|
||||
function(value) {
|
||||
|
||||
@@ -98,7 +98,7 @@ runApp <- function(
|
||||
# * While this could be done at a lower level, it allows for _anything_ within
|
||||
# shiny's control to allow for the opportunity to have otel active spans be
|
||||
# reactivated upon promise domain restoration
|
||||
local_ospan_promise_domain()
|
||||
promises::local_otel_promise_domain()
|
||||
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
|
||||
@@ -274,7 +274,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
args <- argsForServerFunc(serverFunc, shinysession)
|
||||
|
||||
withReactiveDomain(shinysession, {
|
||||
use_session_start_ospan_async(domain = shinysession, {
|
||||
otel_span_session_start(domain = shinysession, {
|
||||
|
||||
do.call(
|
||||
# No corresponding ..stacktraceoff; the server func is pure
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
#' @importFrom lifecycle deprecated is_present
|
||||
#' @importFrom grDevices dev.set dev.cur
|
||||
#' @importFrom fastmap fastmap
|
||||
#' @importFrom promises %...!%
|
||||
#' @importFrom promises %...>%
|
||||
#' @importFrom promises
|
||||
#' promise promise_resolve promise_reject is.promising
|
||||
#' then
|
||||
#' as.promise
|
||||
#' %...!% %...>%
|
||||
#' as.promise is.promising is.promise
|
||||
#' promise_resolve promise_reject
|
||||
#' hybrid_then
|
||||
#' with_promise_domain new_promise_domain
|
||||
#' @importFrom rlang
|
||||
#' quo enquo enquo0 as_function get_expr get_env new_function enquos
|
||||
#' eval_tidy expr pairlist2 new_quosure enexpr as_quosure is_quosure inject
|
||||
|
||||
15
R/shiny.R
15
R/shiny.R
@@ -428,7 +428,7 @@ ShinySession <- R6Class(
|
||||
stop("Nested calls to withCurrentOutput() are not allowed.")
|
||||
}
|
||||
|
||||
promises::with_promise_domain(
|
||||
with_promise_domain(
|
||||
createVarPromiseDomain(private, "currentOutputName", name),
|
||||
expr
|
||||
)
|
||||
@@ -1058,7 +1058,7 @@ ShinySession <- R6Class(
|
||||
|
||||
# For fatal errors, always log.
|
||||
# For non-fatal errors, only log if we haven't seen this error before.
|
||||
if (close || !has_seen_ospan_error(e)) {
|
||||
if (close || !has_seen_otel_exception(e)) {
|
||||
otel_log(
|
||||
if (close) "Fatal error" else "Unhandled error",
|
||||
severity = if (close) "fatal" else "error",
|
||||
@@ -1086,7 +1086,7 @@ ShinySession <- R6Class(
|
||||
}
|
||||
# ..stacktraceon matches with the top-level ..stacktraceoff..
|
||||
withReactiveDomain(self, {
|
||||
with_session_end_ospan_async(domain = self, {
|
||||
otel_span_session_end(domain = self, {
|
||||
private$closedCallbacks$invoke(onError = printError, ..stacktraceon = TRUE)
|
||||
})
|
||||
})
|
||||
@@ -1167,11 +1167,10 @@ ShinySession <- R6Class(
|
||||
# This shinyCallingHandlers should maybe be at a higher level,
|
||||
# to include the $then/$catch calls below?
|
||||
hybrid_chain(
|
||||
# TODO: Move ospan wrapper here to capture return value
|
||||
hybrid_chain(
|
||||
{
|
||||
private$withCurrentOutput(name, {
|
||||
maybe_with_reactive_update_active_ospan({
|
||||
maybe_with_otel_span_reactive_update({
|
||||
shinyCallingHandlers(func())
|
||||
}, domain = self)
|
||||
})
|
||||
@@ -2040,7 +2039,7 @@ ShinySession <- R6Class(
|
||||
ext <- paste(".", ext, sep = "")
|
||||
tmpdata <- tempfile(fileext = ext)
|
||||
return(Context$new(getDefaultReactiveDomain(), '[download]')$run(function() {
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
with_promise_domain(reactivePromiseDomain(), {
|
||||
captureStackTraces({
|
||||
self$incrementBusyCount()
|
||||
hybrid_chain(
|
||||
@@ -2213,7 +2212,7 @@ ShinySession <- R6Class(
|
||||
rLog$asyncStart(domain = self)
|
||||
private$sendMessage(busy = "busy")
|
||||
|
||||
create_reactive_update_ospan(domain = self)
|
||||
otel_span_reactive_update_init(domain = self)
|
||||
}
|
||||
private$busyCount <- private$busyCount + 1L
|
||||
},
|
||||
@@ -2236,7 +2235,7 @@ ShinySession <- R6Class(
|
||||
}
|
||||
})
|
||||
|
||||
end_reactive_update_ospan(domain = self)
|
||||
otel_span_reactive_update_teardown(domain = self)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -626,7 +626,7 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
|
||||
domain <- createRenderPrintPromiseDomain(width)
|
||||
hybrid_chain(
|
||||
{
|
||||
promises::with_promise_domain(domain, func())
|
||||
with_promise_domain(domain, func())
|
||||
},
|
||||
function(value) {
|
||||
res <- withVisible(value)
|
||||
@@ -655,7 +655,7 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
|
||||
createRenderPrintPromiseDomain <- function(width) {
|
||||
f <- file()
|
||||
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
function(...) {
|
||||
|
||||
@@ -1525,7 +1525,7 @@ promise_chain <- function(promise, ..., catch = NULL, finally = NULL,
|
||||
}
|
||||
|
||||
if (!is.null(domain)) {
|
||||
promises::with_promise_domain(domain, do(), replace = replace)
|
||||
with_promise_domain(domain, do(), replace = replace)
|
||||
} else {
|
||||
do()
|
||||
}
|
||||
@@ -1542,7 +1542,7 @@ hybrid_chain <- function(expr, ..., catch = NULL, finally = NULL,
|
||||
{
|
||||
captureStackTraces({
|
||||
result <- withVisible(force(expr))
|
||||
if (promises::is.promising(result$value)) {
|
||||
if (is.promising(result$value)) {
|
||||
# Purposefully NOT including domain (nor replace), as we're already in
|
||||
# the domain at this point
|
||||
p <- promise_chain(valueWithVisible(result), ..., catch = catch, finally = finally)
|
||||
@@ -1576,7 +1576,7 @@ hybrid_chain <- function(expr, ..., catch = NULL, finally = NULL,
|
||||
}
|
||||
|
||||
if (!is.null(domain)) {
|
||||
promises::with_promise_domain(domain, do(), replace = replace)
|
||||
with_promise_domain(domain, do(), replace = replace)
|
||||
} else {
|
||||
do()
|
||||
}
|
||||
@@ -1594,7 +1594,7 @@ createVarPromiseDomain <- function(env, name, value) {
|
||||
force(name)
|
||||
force(value)
|
||||
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
function(...) {
|
||||
orig <- env[[name]]
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
15 57 promises::with_promise_domain
|
||||
16 56 captureStackTraces
|
||||
17 55 force
|
||||
18 54 with_context_ospan_async
|
||||
18 54 with_otel_span_context
|
||||
19 53 force
|
||||
20 52 domain$wrapSync
|
||||
21 51 promises::with_promise_domain
|
||||
@@ -72,7 +72,7 @@
|
||||
50 22 promises::with_promise_domain
|
||||
51 21 captureStackTraces
|
||||
52 20 force
|
||||
53 19 with_context_ospan_async
|
||||
53 19 with_otel_span_context
|
||||
54 18 force
|
||||
55 17 domain$wrapSync
|
||||
56 16 promises::with_promise_domain
|
||||
|
||||
@@ -930,7 +930,7 @@ test_that("bindCache reactive visibility - async", {
|
||||
k <- reactiveVal(0)
|
||||
res <- NULL
|
||||
r <- reactive({
|
||||
promise(function(resolve, reject) {
|
||||
promises::promise(function(resolve, reject) {
|
||||
if (k() == 0) resolve(invisible(k()))
|
||||
else resolve(k())
|
||||
})
|
||||
@@ -1140,7 +1140,7 @@ test_that("Custom render functions that call installExprFunction", {
|
||||
|
||||
|
||||
test_that("cacheWriteHook and cacheReadHook for render functions", {
|
||||
testthat::skip_if(get_tracer()$is_enabled(), "Skipping stack trace tests when OpenTelemetry is already enabled")
|
||||
testthat::skip_if(shiny_otel_tracer()$is_enabled(), "Skipping stack trace tests when OpenTelemetry is already enabled")
|
||||
|
||||
write_hook_n <- 0
|
||||
read_hook_n <- 0
|
||||
|
||||
@@ -54,38 +54,26 @@ test_server_with_otel_error <- function(session, server, expr, sanitize = FALSE,
|
||||
}
|
||||
|
||||
|
||||
test_that("has_seen_ospan_error() returns FALSE for unseen errors", {
|
||||
test_that("mark_otel_exception_as_seen() returns modified condition", {
|
||||
cnd <- simpleError("test error")
|
||||
expect_false(has_seen_ospan_error(cnd))
|
||||
})
|
||||
|
||||
test_that("set_ospan_error_as_seen() marks error as seen", {
|
||||
cnd <- simpleError("test error")
|
||||
expect_false(has_seen_ospan_error(cnd))
|
||||
|
||||
cnd <- set_ospan_error_as_seen(cnd)
|
||||
expect_true(has_seen_ospan_error(cnd))
|
||||
})
|
||||
|
||||
test_that("set_ospan_error_as_seen() returns modified condition", {
|
||||
cnd <- simpleError("test error")
|
||||
result <- set_ospan_error_as_seen(cnd)
|
||||
result <- mark_otel_exception_as_seen(cnd)
|
||||
|
||||
expect_true(inherits(result, "error"))
|
||||
expect_true(inherits(result, "condition"))
|
||||
expect_equal(conditionMessage(result), "test error")
|
||||
expect_true(isTRUE(result$.shiny_error_seen))
|
||||
expect_true(isTRUE(result$.shiny_otel_exception))
|
||||
})
|
||||
|
||||
test_that("has_seen_ospan_error() detects marked errors", {
|
||||
test_that("mark_otel_exception_as_seen() marks error as seen", {
|
||||
cnd <- simpleError("test error")
|
||||
cnd$.shiny_error_seen <- TRUE
|
||||
expect_false(has_seen_otel_exception(cnd))
|
||||
|
||||
expect_true(has_seen_ospan_error(cnd))
|
||||
cnd <- mark_otel_exception_as_seen(cnd)
|
||||
expect_true(has_seen_otel_exception(cnd))
|
||||
})
|
||||
|
||||
|
||||
test_that("set_ospan_error_status() records sanitized errors by default", {
|
||||
test_that("set_otel_exception_status() records sanitized errors by default", {
|
||||
server <- function(input, output, session) {
|
||||
r1 <- reactive(label = "r1", {
|
||||
stop("test error in r1")
|
||||
@@ -125,7 +113,7 @@ test_that("set_ospan_error_status() records sanitized errors by default", {
|
||||
)
|
||||
})
|
||||
|
||||
test_that("set_ospan_error_status() records exception only once in reactive context", {
|
||||
test_that("set_otel_exception_status() records exception only once in reactive context", {
|
||||
server <- function(input, output, session) {
|
||||
r1 <- reactive(label = "r1", {
|
||||
stop("test error in r1")
|
||||
@@ -162,7 +150,7 @@ test_that("set_ospan_error_status() records exception only once in reactive cont
|
||||
)
|
||||
})
|
||||
|
||||
test_that("set_ospan_error_status() records exception for multiple independent errors", {
|
||||
test_that("set_otel_exception_status() records exception for multiple independent errors", {
|
||||
server <- function(input, output, session) {
|
||||
r1 <- reactive(label = "r1", {
|
||||
stop("error in r1")
|
||||
@@ -194,7 +182,7 @@ test_that("set_ospan_error_status() records exception for multiple independent e
|
||||
expect_gte(length(exception_events), 1)
|
||||
})
|
||||
|
||||
test_that("set_ospan_error_status() does not record shiny.custom.error", {
|
||||
test_that("set_otel_exception_status() does not record shiny.custom.error", {
|
||||
server <- function(input, output, session) {
|
||||
r <- reactive(label = "r", {
|
||||
cnd <- simpleError("custom error")
|
||||
@@ -218,7 +206,7 @@ test_that("set_ospan_error_status() does not record shiny.custom.error", {
|
||||
}
|
||||
})
|
||||
|
||||
test_that("set_ospan_error_status() does not record shiny.silent.error", {
|
||||
test_that("set_otel_exception_status() does not record shiny.silent.error", {
|
||||
server <- function(input, output, session) {
|
||||
r <- reactive(label = "r", {
|
||||
cnd <- simpleError("silent error")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Tests for label methods used in otel-bind.R
|
||||
test_that("ospan_label_reactive generates correct labels", {
|
||||
test_that("otel_span_label_reactive generates correct labels", {
|
||||
# Create mock reactive with observable attribute
|
||||
x_reactive <- reactive({ 42 })
|
||||
|
||||
@@ -7,40 +7,40 @@ test_that("ospan_label_reactive generates correct labels", {
|
||||
x_observe <- observe({ 42 })
|
||||
|
||||
# Test without domain
|
||||
result <- ospan_label_reactive(x_reactive, domain = MockShinySession$new())
|
||||
result <- otel_span_label_reactive(x_reactive, domain = MockShinySession$new())
|
||||
expect_equal(result, "reactive mock-session:x_reactive")
|
||||
|
||||
# Test with cache class
|
||||
x_reactive_cache <- bindCache(x_reactive, {"cacheKey"})
|
||||
result <- ospan_label_reactive(x_reactive_cache, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive_cache, domain = NULL)
|
||||
expect_equal(result, "reactive cache x_reactive_cache")
|
||||
|
||||
x_reactive_cache <- x_reactive |> bindCache({"cacheKey"})
|
||||
result <- ospan_label_reactive(x_reactive_cache, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive_cache, domain = NULL)
|
||||
expect_equal(result, "reactive cache x_reactive_cache")
|
||||
x_reactive_cache <- reactive({42}) |> bindCache({"cacheKey"})
|
||||
result <- ospan_label_reactive(x_reactive_cache, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive_cache, domain = NULL)
|
||||
expect_equal(result, "reactive cache x_reactive_cache")
|
||||
|
||||
# Test with event class
|
||||
x_reactive_event <- bindEvent(x_reactive, {"eventKey"})
|
||||
result <- ospan_label_reactive(x_reactive_event, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive_event, domain = NULL)
|
||||
expect_equal(result, "reactive event x_reactive_event")
|
||||
x_reactive_event <- x_reactive |> bindEvent({"eventKey"})
|
||||
result <- ospan_label_reactive(x_reactive_event, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive_event, domain = NULL)
|
||||
expect_equal(result, "reactive event x_reactive_event")
|
||||
result <- ospan_label_reactive(x_reactive |> bindEvent({"eventKey"}), domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive |> bindEvent({"eventKey"}), domain = NULL)
|
||||
expect_equal(result, "reactive event <anonymous>")
|
||||
x_reactive_event <- reactive({42}) |> bindEvent({"eventKey"})
|
||||
result <- ospan_label_reactive(x_reactive_event, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive_event, domain = NULL)
|
||||
expect_equal(result, "reactive event x_reactive_event")
|
||||
|
||||
# x_reactive_both <- bindCache(bindEvent(x_reactive, {"eventKey"}), {"cacheKey"})
|
||||
# result <- ospan_label_reactive(x_reactive_both, domain = NULL)
|
||||
# result <- otel_span_label_reactive(x_reactive_both, domain = NULL)
|
||||
# expect_equal(result, "reactive event cache x_reactive_both")
|
||||
|
||||
x_reactive_both2 <- bindEvent(bindCache(x_reactive, {"cacheKey"}), {"eventKey"})
|
||||
result <- ospan_label_reactive(x_reactive_both2, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive_both2, domain = NULL)
|
||||
expect_equal(result, "reactive cache event x_reactive_both2")
|
||||
})
|
||||
|
||||
@@ -62,7 +62,7 @@ test_that("reactive bindCache labels are created", {
|
||||
"cachedReactive(x_reactive)"
|
||||
)
|
||||
expect_equal(
|
||||
ospan_label_reactive(x_reactive_cache, domain = NULL),
|
||||
otel_span_label_reactive(x_reactive_cache, domain = NULL),
|
||||
"reactive cache <anonymous>"
|
||||
)
|
||||
})
|
||||
@@ -106,24 +106,24 @@ test_that("ExtendedTask otel labels are created", {
|
||||
})
|
||||
|
||||
|
||||
test_that("ospan_label_reactive with pre-defined label", {
|
||||
test_that("otel_span_label_reactive with pre-defined label", {
|
||||
x_reactive <- reactive({ 42 }, label = "counter")
|
||||
|
||||
result <- ospan_label_reactive(x_reactive, domain = MockShinySession$new())
|
||||
result <- otel_span_label_reactive(x_reactive, domain = MockShinySession$new())
|
||||
expect_equal(result, "reactive mock-session:counter")
|
||||
|
||||
result <- ospan_label_reactive(x_reactive, domain = NULL)
|
||||
result <- otel_span_label_reactive(x_reactive, domain = NULL)
|
||||
expect_equal(result, "reactive counter")
|
||||
})
|
||||
|
||||
test_that("observer labels are preserved", {
|
||||
x_observe <- observe({ 42 }, label = "my_observer")
|
||||
expect_equal(x_observe$.label, "my_observer")
|
||||
expect_equal(ospan_label_observer(x_observe, domain = NULL), "observe my_observer")
|
||||
expect_equal(otel_span_label_observer(x_observe, domain = NULL), "observe my_observer")
|
||||
|
||||
x_observe <- observe({ 42 })
|
||||
expect_equal(x_observe$.label, "x_observe")
|
||||
expect_equal(ospan_label_observer(x_observe, domain = NULL), "observe x_observe")
|
||||
expect_equal(otel_span_label_observer(x_observe, domain = NULL), "observe x_observe")
|
||||
|
||||
f <- function() {
|
||||
observe({ 42 })
|
||||
@@ -131,35 +131,35 @@ test_that("observer labels are preserved", {
|
||||
|
||||
x_observe <- f()
|
||||
expect_equal(x_observe$.label, as_default_label("observe({\n 42\n})"))
|
||||
expect_equal(ospan_label_observer(x_observe, domain = NULL), "observe <anonymous>")
|
||||
expect_equal(otel_span_label_observer(x_observe, domain = NULL), "observe <anonymous>")
|
||||
})
|
||||
|
||||
test_that("ospan_label_observer generates correct labels", {
|
||||
test_that("otel_span_label_observer generates correct labels", {
|
||||
x_observe <- observe({ 42 }, label = "test_observer" )
|
||||
|
||||
result <- ospan_label_observer(x_observe, domain = MockShinySession$new())
|
||||
result <- otel_span_label_observer(x_observe, domain = MockShinySession$new())
|
||||
expect_equal(result, "observe mock-session:test_observer")
|
||||
result <- ospan_label_observer(x_observe, domain = NULL)
|
||||
result <- otel_span_label_observer(x_observe, domain = NULL)
|
||||
expect_equal(result, "observe test_observer")
|
||||
|
||||
x_observe_event <- bindEvent(x_observe, {"eventKey"})
|
||||
result <- ospan_label_observer(x_observe_event, domain = NULL)
|
||||
result <- otel_span_label_observer(x_observe_event, domain = NULL)
|
||||
expect_equal(result, "observe event x_observe_event")
|
||||
|
||||
x_observe_event <- observe({ 42 }, label = "test_observer" ) |> bindEvent({"eventKey"})
|
||||
result <- ospan_label_observer(x_observe_event, domain = NULL)
|
||||
result <- otel_span_label_observer(x_observe_event, domain = NULL)
|
||||
expect_equal(result, "observe event x_observe_event")
|
||||
|
||||
result <- ospan_label_observer(observe({ 42 }, label = "test_observer" ) |> bindEvent({"eventKey"}), domain = NULL)
|
||||
result <- otel_span_label_observer(observe({ 42 }, label = "test_observer" ) |> bindEvent({"eventKey"}), domain = NULL)
|
||||
expect_equal(result, "observe event <anonymous>")
|
||||
|
||||
x_observe <- observe({ 42 }, label = "test_observer" )
|
||||
x_observe_event <- x_observe |> bindEvent({"eventKey"})
|
||||
result <- ospan_label_observer(x_observe_event, domain = NULL)
|
||||
result <- otel_span_label_observer(x_observe_event, domain = NULL)
|
||||
expect_equal(result, "observe event x_observe_event")
|
||||
})
|
||||
|
||||
test_that("throttle ospan label is correct", {
|
||||
test_that("throttle otel span label is correct", {
|
||||
x_reactive <- reactive({ 42 })
|
||||
x_throttled1 <- throttle(x_reactive, 1000)
|
||||
x_throttled2 <- x_reactive |> throttle(1000)
|
||||
@@ -183,7 +183,7 @@ test_that("throttle ospan label is correct", {
|
||||
expect_equal(attr(x_throttled3, "observable")$.otelLabel, "throttle x_throttled3")
|
||||
})
|
||||
|
||||
test_that("debounce ospan label is correct", {
|
||||
test_that("debounce otel span label is correct", {
|
||||
x_reactive <- reactive({ 42 })
|
||||
x_debounced1 <- debounce(x_reactive, 1000)
|
||||
x_debounced2 <- x_reactive |> debounce(1000)
|
||||
@@ -207,13 +207,13 @@ test_that("debounce ospan label is correct", {
|
||||
expect_equal(attr(x_debounced3, "observable")$.otelLabel, "debounce x_debounced3")
|
||||
})
|
||||
|
||||
test_that("ospan_label_observer handles module namespacing", {
|
||||
test_that("otel_span_label_observer handles module namespacing", {
|
||||
x_observe <- observe({ 42 }, label = "clicks" )
|
||||
result <- ospan_label_observer(x_observe, domain = MockShinySession$new())
|
||||
result <- otel_span_label_observer(x_observe, domain = MockShinySession$new())
|
||||
expect_equal(result, "observe mock-session:clicks")
|
||||
})
|
||||
|
||||
test_that("ospan_label_render_function generates correct labels", {
|
||||
test_that("otel_span_label_render_function generates correct labels", {
|
||||
x_render <- renderText({ "Hello" })
|
||||
mock_domain <- MockShinySession$new()
|
||||
|
||||
@@ -223,27 +223,27 @@ test_that("ospan_label_render_function generates correct labels", {
|
||||
}
|
||||
)
|
||||
|
||||
result <- ospan_label_render_function(x_render, domain = NULL)
|
||||
result <- otel_span_label_render_function(x_render, domain = NULL)
|
||||
expect_equal(result, "output plot1")
|
||||
|
||||
result <- ospan_label_render_function(x_render, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render, domain = mock_domain)
|
||||
expect_equal(result, "output mock-session:plot1")
|
||||
|
||||
x_render_event <- bindEvent(x_render, {"eventKey"})
|
||||
result <- ospan_label_render_function(x_render_event, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render_event, domain = mock_domain)
|
||||
expect_equal(result, "output event mock-session:plot1")
|
||||
|
||||
x_render_cache <- bindCache(x_render, {"cacheKey"})
|
||||
result <- ospan_label_render_function(x_render_cache, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render_cache, domain = mock_domain)
|
||||
expect_equal(result, "output cache mock-session:plot1")
|
||||
|
||||
x_render_both <- bindEvent(bindCache(x_render, {"cacheKey"}), {"eventKey"})
|
||||
result <- ospan_label_render_function(x_render_both, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render_both, domain = mock_domain)
|
||||
expect_equal(result, "output cache event mock-session:plot1")
|
||||
})
|
||||
|
||||
|
||||
test_that("ospan_label_render_function handles cache and event classes", {
|
||||
test_that("otel_span_label_render_function handles cache and event classes", {
|
||||
testthat::local_mocked_bindings(
|
||||
getCurrentOutputInfo = function(session) {
|
||||
list(name = "table1")
|
||||
@@ -256,19 +256,19 @@ test_that("ospan_label_render_function handles cache and event classes", {
|
||||
x_render_both <- bindEvent(bindCache(x_render, {"cacheKey"}), {"eventKey"})
|
||||
mock_domain <- MockShinySession$new()
|
||||
|
||||
result <- ospan_label_render_function(x_render, domain = NULL)
|
||||
result <- otel_span_label_render_function(x_render, domain = NULL)
|
||||
expect_equal(result, "output table1")
|
||||
|
||||
result <- ospan_label_render_function(x_render, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render, domain = mock_domain)
|
||||
expect_equal(result, "output mock-session:table1")
|
||||
|
||||
result <- ospan_label_render_function(x_render_event, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render_event, domain = mock_domain)
|
||||
expect_equal(result, "output event mock-session:table1")
|
||||
|
||||
result <- ospan_label_render_function(x_render_cache, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render_cache, domain = mock_domain)
|
||||
expect_equal(result, "output cache mock-session:table1")
|
||||
|
||||
result <- ospan_label_render_function(x_render_both, domain = mock_domain)
|
||||
result <- otel_span_label_render_function(x_render_both, domain = mock_domain)
|
||||
expect_equal(result, "output cache event mock-session:table1")
|
||||
})
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ for (bind in c("all", "reactivity")) {
|
||||
test_that(paste0("bind='", bind, "' extended tasks are supported"), {
|
||||
server <- function(input, output, session) {
|
||||
rand_task <- ExtendedTask$new(function() {
|
||||
promise_resolve(42) |> then(function(value) {
|
||||
promise_resolve(42) |> promises::then(function(value) {
|
||||
value
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,89 +1,26 @@
|
||||
# Tests for otel-reactive-update.R functions
|
||||
|
||||
# Helper function to create a mock ospan
|
||||
create_mock_ospan <- function(name, attributes = NULL, ended = FALSE) {
|
||||
# Helper function to create a mock otel span
|
||||
create_mock_otel_span <- function(name, attributes = NULL, ended = FALSE) {
|
||||
structure(
|
||||
list(name = name, attributes = attributes, ended = ended),
|
||||
class = "mock_ospan"
|
||||
class = c("mock_otel_span", "otel_span")
|
||||
)
|
||||
}
|
||||
|
||||
# Mock is_ospan function
|
||||
is_ospan <- function(x) {
|
||||
inherits(x, "mock_ospan") && !isTRUE(x$ended)
|
||||
}
|
||||
|
||||
test_that("has_reactive_ospan_cleanup works correctly", {
|
||||
test_that("otel_span_reactive_update_init returns early when otel not enabled", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
# Initially should be FALSE
|
||||
expect_false(has_reactive_ospan_cleanup(domain))
|
||||
|
||||
# After setting, should be TRUE
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- TRUE
|
||||
expect_true(has_reactive_ospan_cleanup(domain))
|
||||
|
||||
# With FALSE value, should be FALSE
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- FALSE
|
||||
expect_false(has_reactive_ospan_cleanup(domain))
|
||||
})
|
||||
|
||||
test_that("set_reactive_ospan_cleanup sets flag correctly", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
expect_false(has_reactive_ospan_cleanup(domain))
|
||||
set_reactive_ospan_cleanup(domain)
|
||||
expect_true(has_reactive_ospan_cleanup(domain))
|
||||
})
|
||||
|
||||
test_that("reactive_update_ospan_is_active works correctly", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
# Initially should be FALSE
|
||||
expect_false(reactive_update_ospan_is_active(domain))
|
||||
|
||||
# After setting, should be TRUE
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- TRUE
|
||||
expect_true(reactive_update_ospan_is_active(domain))
|
||||
|
||||
# With FALSE value, should be FALSE
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- FALSE
|
||||
expect_false(reactive_update_ospan_is_active(domain))
|
||||
})
|
||||
|
||||
test_that("set_reactive_ospan_is_active sets flag correctly", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
expect_false(reactive_update_ospan_is_active(domain))
|
||||
set_reactive_ospan_is_active(domain)
|
||||
expect_true(reactive_update_ospan_is_active(domain))
|
||||
})
|
||||
|
||||
test_that("clear_reactive_ospan_is_active clears flag correctly", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
# Set the flag first
|
||||
set_reactive_ospan_is_active(domain)
|
||||
expect_true(reactive_update_ospan_is_active(domain))
|
||||
|
||||
# Clear it
|
||||
clear_reactive_ospan_is_active(domain)
|
||||
expect_false(reactive_update_ospan_is_active(domain))
|
||||
})
|
||||
|
||||
test_that("create_reactive_update_ospan returns early when otel not enabled", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
# Mock has_otel_bind to return FALSE
|
||||
# Convince has_otel_bind to return FALSE
|
||||
withr::local_options(list(shiny.otel.bind = "none"))
|
||||
|
||||
# Should return early without creating span
|
||||
result <- create_reactive_update_ospan(domain = domain)
|
||||
result <- otel_span_reactive_update_init(domain = domain)
|
||||
expect_null(result)
|
||||
expect_null(domain$userData[["_otel_reactive_update_ospan"]])
|
||||
expect_null(domain$userData[["_otel_span_reactive_update"]])
|
||||
})
|
||||
|
||||
test_that("create_reactive_update_ospan sets up session cleanup on first call", {
|
||||
test_that("otel_span_reactive_update_init sets up session cleanup on first call", {
|
||||
callback_added <- FALSE
|
||||
TestMockShinySession <- R6::R6Class(
|
||||
"TestMockShinySession",
|
||||
@@ -100,46 +37,43 @@ test_that("create_reactive_update_ospan sets up session cleanup on first call",
|
||||
)
|
||||
domain <- TestMockShinySession$new()
|
||||
|
||||
|
||||
# Mock dependencies
|
||||
withr::local_options(list(shiny.otel.bind = "reactive_update"))
|
||||
|
||||
with_mocked_bindings(
|
||||
local_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update",
|
||||
create_shiny_ospan = function(name, ..., attributes = NULL) create_mock_ospan(name, attributes = attributes),
|
||||
otel_session_id_attrs = function(domain) list(session_id = "mock-session-id"),
|
||||
{
|
||||
create_reactive_update_ospan(domain = domain)
|
||||
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")
|
||||
)
|
||||
|
||||
expect_true(callback_added)
|
||||
expect_true(has_reactive_ospan_cleanup(domain))
|
||||
expect_equal(domain$userData[["_otel_reactive_update_ospan"]], create_mock_ospan("reactive_update", attributes = list(session_id = "mock-session-id")))
|
||||
}
|
||||
otel_span_reactive_update_init(domain = domain)
|
||||
|
||||
expect_true(callback_added)
|
||||
expect_true(domain$userData[["_otel_has_reactive_cleanup"]])
|
||||
expect_equal(
|
||||
domain$userData[["_otel_span_reactive_update"]],
|
||||
create_mock_otel_span("reactive_update", attributes = list(session_id = "mock-session-id"))
|
||||
)
|
||||
})
|
||||
|
||||
test_that("create_reactive_update_ospan errors when span already exists", {
|
||||
test_that("otel_span_reactive_update_init errors when span already exists", {
|
||||
domain <- MockShinySession$new()
|
||||
domain$token <- "mock-session-token"
|
||||
|
||||
# Set up existing span
|
||||
existing_ospan <- create_mock_ospan("reactive_update", attributes = list(session.id = "mock-session-token"))
|
||||
domain$userData[["_otel_reactive_update_ospan"]] <- existing_ospan
|
||||
existing_otel_span <- create_mock_otel_span("reactive_update", attributes = list(session.id = "mock-session-token"))
|
||||
domain$userData[["_otel_span_reactive_update"]] <- existing_otel_span
|
||||
|
||||
# Mock dependencies
|
||||
with_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update",
|
||||
is_ospan = function(x) inherits(x, "mock_ospan"),
|
||||
{
|
||||
expect_error(
|
||||
create_reactive_update_ospan(domain = domain),
|
||||
"Reactive update span already exists"
|
||||
)
|
||||
}
|
||||
local_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update"
|
||||
)
|
||||
|
||||
expect_error(
|
||||
otel_span_reactive_update_init(domain = domain),
|
||||
"Reactive update span already exists"
|
||||
)
|
||||
})
|
||||
|
||||
test_that("create_reactive_update_ospan doesn't setup cleanup twice", {
|
||||
test_that("otel_span_reactive_update_init doesn't setup cleanup twice", {
|
||||
TestMockShinySession <- R6::R6Class(
|
||||
"TestMockShinySession",
|
||||
inherit = MockShinySession,
|
||||
@@ -157,89 +91,73 @@ test_that("create_reactive_update_ospan doesn't setup cleanup twice", {
|
||||
domain <- TestMockShinySession$new()
|
||||
|
||||
# Set cleanup flag manually
|
||||
set_reactive_ospan_cleanup(domain)
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- TRUE
|
||||
|
||||
# Mock dependencies
|
||||
mock_ospan <- create_mock_ospan("reactive_update")
|
||||
|
||||
with_mocked_bindings(
|
||||
local_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update",
|
||||
create_shiny_ospan = function(...) mock_ospan,
|
||||
{
|
||||
create_reactive_update_ospan(domain = domain)
|
||||
|
||||
# Should not have called onSessionEnded since cleanup was already set
|
||||
expect_equal(domain$callback_count, 0)
|
||||
}
|
||||
start_otel_span = function(...) create_mock_otel_span("reactive_update")
|
||||
)
|
||||
|
||||
otel_span_reactive_update_init(domain = domain)
|
||||
|
||||
# Should not have called onSessionEnded since cleanup was already set
|
||||
expect_equal(domain$callback_count, 0)
|
||||
})
|
||||
|
||||
test_that("end_reactive_update_ospan ends span when it exists", {
|
||||
test_that("otel_span_reactive_update_teardown ends span when it exists", {
|
||||
domain <- MockShinySession$new()
|
||||
mock_ospan <- create_mock_ospan("reactive_update")
|
||||
domain$userData[["_otel_reactive_update_ospan"]] <- mock_ospan
|
||||
mock_otel_span <- create_mock_otel_span("reactive_update")
|
||||
domain$userData[["_otel_span_reactive_update"]] <- mock_otel_span
|
||||
|
||||
span_ended <- FALSE
|
||||
|
||||
with_mocked_bindings(
|
||||
local_mocked_bindings(
|
||||
end_span = function(span) {
|
||||
span_ended <<- TRUE
|
||||
expect_equal(span, mock_ospan)
|
||||
expect_equal(span, mock_otel_span)
|
||||
},
|
||||
.package = "otel",
|
||||
{
|
||||
with_mocked_bindings(
|
||||
is_ospan = function(x) inherits(x, "mock_ospan") && !isTRUE(x$ended),
|
||||
{
|
||||
end_reactive_update_ospan(domain = domain)
|
||||
|
||||
expect_true(span_ended)
|
||||
expect_null(domain$userData[["_otel_reactive_update_ospan"]])
|
||||
}
|
||||
)
|
||||
}
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
otel_span_reactive_update_teardown(domain = domain)
|
||||
|
||||
expect_true(span_ended)
|
||||
expect_null(domain$userData[["_otel_span_reactive_update"]])
|
||||
})
|
||||
|
||||
test_that("end_reactive_update_ospan handles missing span gracefully", {
|
||||
test_that("otel_span_reactive_update_teardown handles missing span gracefully", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
# No span exists
|
||||
expect_null(domain$userData[["_otel_reactive_update_ospan"]])
|
||||
expect_null(domain$userData[["_otel_span_reactive_update"]])
|
||||
|
||||
with_mocked_bindings(
|
||||
is_ospan = function(x) FALSE,
|
||||
{
|
||||
# Should not error
|
||||
expect_no_error(end_reactive_update_ospan(domain = domain))
|
||||
}
|
||||
)
|
||||
# Should not error
|
||||
expect_no_error(otel_span_reactive_update_teardown(domain = domain))
|
||||
})
|
||||
|
||||
test_that("with_reactive_update_active_ospan executes expr without span", {
|
||||
test_that("with_otel_span_reactive_update executes expr without span", {
|
||||
domain <- MockShinySession$new()
|
||||
|
||||
# No span exists
|
||||
test_value <- "initial"
|
||||
|
||||
with_mocked_bindings(
|
||||
is_ospan = function(x) FALSE,
|
||||
{
|
||||
result <- with_reactive_update_active_ospan({
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
}, domain = domain)
|
||||
|
||||
expect_equal(result, "result_value")
|
||||
expect_equal(test_value, "modified")
|
||||
}
|
||||
local_mocked_bindings(
|
||||
is_otel_span = function(x) FALSE
|
||||
)
|
||||
|
||||
result <- with_otel_span_reactive_update({
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
}, domain = domain)
|
||||
|
||||
expect_equal(result, "result_value")
|
||||
expect_equal(test_value, "modified")
|
||||
})
|
||||
|
||||
test_that("with_reactive_update_active_ospan executes expr with active span", {
|
||||
test_that("with_otel_span_reactive_update executes expr with active span", {
|
||||
domain <- MockShinySession$new()
|
||||
mock_ospan <- create_mock_ospan("reactive_update")
|
||||
domain$userData[["_otel_reactive_update_ospan"]] <- mock_ospan
|
||||
mock_otel_span <- create_mock_otel_span("reactive_update")
|
||||
domain$userData[["_otel_span_reactive_update"]] <- mock_otel_span
|
||||
|
||||
span_was_active <- FALSE
|
||||
test_value <- "initial"
|
||||
@@ -247,16 +165,13 @@ test_that("with_reactive_update_active_ospan executes expr with active span", {
|
||||
local_mocked_bindings(
|
||||
with_active_span = function(span, expr) {
|
||||
span_was_active <<- TRUE
|
||||
expect_equal(span, mock_ospan)
|
||||
expect_equal(span, mock_otel_span)
|
||||
force(expr)
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
local_mocked_bindings(
|
||||
is_ospan = function(x) inherits(x, "mock_ospan") && !isTRUE(x$ended)
|
||||
)
|
||||
|
||||
result <- with_reactive_update_active_ospan({
|
||||
result <- with_otel_span_reactive_update({
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
}, domain = domain)
|
||||
@@ -281,16 +196,14 @@ test_that("session cleanup callback works correctly", {
|
||||
)
|
||||
)
|
||||
domain <- TestMockShinySession$new()
|
||||
|
||||
# Mock dependencies and create span with cleanup
|
||||
mock_ospan <- create_mock_ospan("reactive_update")
|
||||
mock_otel_span <- create_mock_otel_span("reactive_update")
|
||||
|
||||
with_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "reactive_update",
|
||||
create_shiny_ospan = function(...) mock_ospan,
|
||||
start_otel_span = function(...) mock_otel_span,
|
||||
otel_session_id_attrs = function(domain) list(session_id = "test"),
|
||||
{
|
||||
create_reactive_update_ospan(domain = domain)
|
||||
otel_span_reactive_update_init(domain = domain)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -298,14 +211,13 @@ test_that("session cleanup callback works correctly", {
|
||||
expect_true(is.function(domain$cleanup_callback))
|
||||
|
||||
# Set up span and test cleanup
|
||||
domain$userData[["_otel_reactive_update_ospan"]] <- mock_ospan
|
||||
set_reactive_ospan_cleanup(domain)
|
||||
domain$userData[["_otel_span_reactive_update"]] <- mock_otel_span
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- TRUE
|
||||
|
||||
span_ended <- FALSE
|
||||
|
||||
with_mocked_bindings(
|
||||
has_reactive_ospan_cleanup = function(d) identical(d, domain),
|
||||
end_reactive_update_ospan = function(domain = NULL) {
|
||||
otel_span_reactive_update_teardown = function(domain = NULL) {
|
||||
span_ended <<- TRUE
|
||||
},
|
||||
{
|
||||
|
||||
@@ -40,14 +40,14 @@ create_mock_session_domain <- function(
|
||||
domain
|
||||
}
|
||||
|
||||
test_that("use_session_start_ospan_async returns early when otel not enabled", {
|
||||
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"))
|
||||
|
||||
result <- use_session_start_ospan_async({
|
||||
result <- otel_span_session_start({
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
}, domain = domain)
|
||||
@@ -58,7 +58,7 @@ test_that("use_session_start_ospan_async returns early when otel not enabled", {
|
||||
expect_length(domain$cleanup_callbacks, 0)
|
||||
})
|
||||
|
||||
test_that("use_session_start_ospan_async sets up session end callback", {
|
||||
test_that("otel_span_session_start sets up session end callback", {
|
||||
domain <- create_mock_session_domain(
|
||||
token = "session-456",
|
||||
request = list(PATH_INFO = "/app", HTTP_HOST = "localhost")
|
||||
@@ -78,7 +78,7 @@ test_that("use_session_start_ospan_async sets up session end callback", {
|
||||
has_otel_bind = 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_shiny_ospan_async = function(name, expr, attributes = NULL) {
|
||||
with_otel_span = function(name, expr, attributes = NULL) {
|
||||
expect_equal(name, "session_start")
|
||||
expect_true("session.id" %in% names(attributes))
|
||||
expect_equal(attributes[["session.id"]], "session-456")
|
||||
@@ -88,7 +88,7 @@ test_that("use_session_start_ospan_async sets up session end callback", {
|
||||
|
||||
expect_length(domain$cleanup_callbacks, 0)
|
||||
|
||||
result <- use_session_start_ospan_async({
|
||||
result <- otel_span_session_start({
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
}, domain = domain)
|
||||
@@ -101,14 +101,14 @@ test_that("use_session_start_ospan_async sets up session end callback", {
|
||||
)
|
||||
})
|
||||
|
||||
test_that("with_session_end_ospan_async returns early when otel not enabled", {
|
||||
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"))
|
||||
|
||||
result <- with_session_end_ospan_async({
|
||||
result <- otel_span_session_end({
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
}, domain = domain)
|
||||
@@ -117,7 +117,7 @@ test_that("with_session_end_ospan_async returns early when otel not enabled", {
|
||||
expect_equal(test_value, "modified")
|
||||
})
|
||||
|
||||
test_that("with_session_end_ospan_async creates span when enabled", {
|
||||
test_that("otel_span_session_end creates span when enabled", {
|
||||
domain <- create_mock_session_domain(token = "session-end-test")
|
||||
|
||||
span_created <- FALSE
|
||||
@@ -129,14 +129,14 @@ test_that("with_session_end_ospan_async creates span when enabled", {
|
||||
with_mocked_bindings(
|
||||
has_otel_bind = function(level) level == "session",
|
||||
otel_session_id_attrs = function(domain) list(session.id = domain$token),
|
||||
with_shiny_ospan_async = function(name, expr, attributes = NULL) {
|
||||
with_otel_span = function(name, expr, attributes = NULL) {
|
||||
span_created <<- TRUE
|
||||
expect_equal(name, "session_end")
|
||||
expect_equal(attributes[["session.id"]], "session-end-test")
|
||||
force(expr)
|
||||
},
|
||||
{
|
||||
result <- with_session_end_ospan_async({
|
||||
result <- otel_span_session_end({
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
}, domain = domain)
|
||||
@@ -161,10 +161,10 @@ test_that("otel_session_attrs extracts request attributes correctly", {
|
||||
|
||||
attrs <- otel_session_attrs(domain)
|
||||
|
||||
expect_equal(attrs$PATH_INFO, "/myapp/page")
|
||||
expect_equal(attrs$HTTP_HOST, "example.com")
|
||||
expect_equal(attrs$HTTP_ORIGIN, "https://example.com")
|
||||
expect_equal(attrs$SERVER_PORT, 8080L) # Should be converted to integer
|
||||
expect_equal(attrs$server.path, "/myapp/page")
|
||||
expect_equal(attrs$server.address, "example.com")
|
||||
expect_equal(attrs$server.origin, "https://example.com")
|
||||
expect_equal(attrs$server.port, 8080L) # Should be converted to integer
|
||||
})
|
||||
|
||||
test_that("otel_session_attrs handles websocket PATH_INFO", {
|
||||
@@ -178,7 +178,7 @@ test_that("otel_session_attrs handles websocket PATH_INFO", {
|
||||
attrs <- otel_session_attrs(domain)
|
||||
|
||||
# Should strip websocket suffix
|
||||
expect_equal(attrs$PATH_INFO, "/myapp/")
|
||||
expect_equal(attrs$server.path, "/myapp/")
|
||||
})
|
||||
|
||||
test_that("otel_session_attrs handles missing request fields", {
|
||||
@@ -191,10 +191,10 @@ test_that("otel_session_attrs handles missing request fields", {
|
||||
|
||||
attrs <- otel_session_attrs(domain)
|
||||
|
||||
expect_equal(attrs$PATH_INFO, "")
|
||||
expect_equal(attrs$HTTP_HOST, "localhost")
|
||||
expect_equal(attrs$HTTP_ORIGIN, "")
|
||||
expect_equal(attrs$SERVER_PORT, NA_integer_)
|
||||
expect_equal(attrs$server.path, "")
|
||||
expect_equal(attrs$server.address, "localhost")
|
||||
expect_equal(attrs$server.origin, "")
|
||||
expect_equal(attrs$server.port, NA_integer_)
|
||||
})
|
||||
|
||||
test_that("otel_session_attrs handles empty request", {
|
||||
@@ -202,10 +202,10 @@ test_that("otel_session_attrs handles empty request", {
|
||||
|
||||
attrs <- otel_session_attrs(domain)
|
||||
|
||||
expect_equal(attrs$PATH_INFO, "")
|
||||
expect_equal(attrs$HTTP_HOST, "")
|
||||
expect_equal(attrs$HTTP_ORIGIN, "")
|
||||
expect_equal(attrs$SERVER_PORT, NA_integer_)
|
||||
expect_equal(attrs$server.path, "")
|
||||
expect_equal(attrs$server.address, "")
|
||||
expect_equal(attrs$server.origin, "")
|
||||
expect_equal(attrs$server.port, NA_integer_)
|
||||
})
|
||||
|
||||
test_that("otel_session_attrs handles invalid SERVER_PORT gracefully", {
|
||||
@@ -217,7 +217,7 @@ test_that("otel_session_attrs handles invalid SERVER_PORT gracefully", {
|
||||
attrs <- otel_session_attrs(domain)
|
||||
|
||||
# Should remain as string if conversion fails
|
||||
expect_equal(attrs$SERVER_PORT, "invalid")
|
||||
expect_equal(attrs$server.port, "invalid")
|
||||
})
|
||||
|
||||
test_that("otel_session_id_attrs returns correct session ID", {
|
||||
@@ -263,7 +263,7 @@ test_that("integration test - session start with full request", {
|
||||
has_otel_bind = 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_shiny_ospan_async = function(name, expr, attributes = NULL) {
|
||||
with_otel_span = function(name, expr, attributes = NULL) {
|
||||
span_attributes <<- attributes
|
||||
force(expr)
|
||||
},
|
||||
@@ -272,7 +272,7 @@ test_that("integration test - session start with full request", {
|
||||
|
||||
expect_length(domain$cleanup_callbacks, 0)
|
||||
|
||||
result <- use_session_start_ospan_async({
|
||||
result <- otel_span_session_start({
|
||||
"test_result"
|
||||
}, domain = domain)
|
||||
|
||||
@@ -280,9 +280,9 @@ test_that("integration test - session start with full request", {
|
||||
|
||||
# Check span attributes include both session ID and request info
|
||||
expect_equal(span_attributes[["session.id"]], "integration-test-session")
|
||||
expect_equal(span_attributes[["PATH_INFO"]], "/dashboard/")
|
||||
expect_equal(span_attributes[["HTTP_HOST"]], "shiny.example.com")
|
||||
expect_equal(span_attributes[["SERVER_PORT"]], 3838L)
|
||||
expect_equal(span_attributes[["server.path"]], "/dashboard/")
|
||||
expect_equal(span_attributes[["server.address"]], "shiny.example.com")
|
||||
expect_equal(span_attributes[["server.port"]], 3838L)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
# Tests for otel-shiny.R functions
|
||||
|
||||
# Helper function to create a mock otel span
|
||||
create_mock_otel_span <- function() {
|
||||
create_mock_otel_span <- function(name = "test_span") {
|
||||
structure(
|
||||
list(name = "test_span"),
|
||||
list(
|
||||
name = name,
|
||||
activate = function(...) NULL,
|
||||
end = function(...) NULL
|
||||
),
|
||||
class = "otel_span"
|
||||
)
|
||||
}
|
||||
@@ -11,7 +15,11 @@ create_mock_otel_span <- function() {
|
||||
# Helper function to create a mock tracer
|
||||
create_mock_tracer <- function() {
|
||||
structure(
|
||||
list(name = "mock_tracer", is_enabled = function() TRUE),
|
||||
list(
|
||||
name = "mock_tracer",
|
||||
is_enabled = function() TRUE,
|
||||
start_span = function(name, ...) create_mock_otel_span(name)
|
||||
),
|
||||
class = "otel_tracer"
|
||||
)
|
||||
}
|
||||
@@ -28,38 +36,8 @@ test_that("otel_tracer_name constant is correct", {
|
||||
expect_equal(otel_tracer_name, "co.posit.r-package.shiny")
|
||||
})
|
||||
|
||||
test_that("with_shiny_ospan_async calls with_ospan_async with correct parameters", {
|
||||
mock_tracer <- create_mock_tracer()
|
||||
with_ospan_async_called <- FALSE
|
||||
test_value <- "initial"
|
||||
|
||||
with_mocked_bindings(
|
||||
get_tracer = function() mock_tracer,
|
||||
with_ospan_async = function(name, expr, ..., attributes = NULL, tracer = NULL) {
|
||||
with_ospan_async_called <<- TRUE
|
||||
expect_equal(name, "test_span")
|
||||
expect_equal(tracer, mock_tracer)
|
||||
expect_equal(attributes, list(key = "value"))
|
||||
force(expr)
|
||||
},
|
||||
{
|
||||
result <- with_shiny_ospan_async(
|
||||
"test_span",
|
||||
{
|
||||
test_value <- "modified"
|
||||
"result_value"
|
||||
},
|
||||
attributes = list(key = "value")
|
||||
)
|
||||
|
||||
expect_true(with_ospan_async_called)
|
||||
expect_equal(result, "result_value")
|
||||
expect_equal(test_value, "modified")
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test_that("create_shiny_ospan calls otel::start_span with correct parameters", {
|
||||
test_that("start_otel_span calls otel::start_span with correct parameters", {
|
||||
mock_tracer <- create_mock_tracer()
|
||||
mock_span <- create_mock_otel_span()
|
||||
start_span_called <- FALSE
|
||||
@@ -73,49 +51,51 @@ test_that("create_shiny_ospan calls otel::start_span with correct parameters", {
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
get_tracer = function() mock_tracer,
|
||||
{
|
||||
result <- create_shiny_ospan("test_span", extra_param = "value")
|
||||
|
||||
expect_true(start_span_called)
|
||||
expect_equal(result, mock_span)
|
||||
}
|
||||
local_mocked_bindings(
|
||||
shiny_otel_tracer = function() mock_tracer,
|
||||
)
|
||||
|
||||
result <- start_otel_span("test_span", extra_param = "value")
|
||||
|
||||
expect_true(start_span_called)
|
||||
expect_equal(result, mock_span)
|
||||
})
|
||||
|
||||
test_that("is_ospan correctly identifies otel spans", {
|
||||
test_that("is_otel_span correctly identifies otel spans", {
|
||||
# Test with otel_span object
|
||||
otel_span <- create_mock_otel_span()
|
||||
expect_true(is_ospan(otel_span))
|
||||
expect_true(is_otel_span(otel_span))
|
||||
|
||||
# Test with non-otel objects
|
||||
expect_false(is_ospan("string"))
|
||||
expect_false(is_ospan(123))
|
||||
expect_false(is_ospan(list()))
|
||||
expect_false(is_ospan(NULL))
|
||||
expect_false(is_otel_span("string"))
|
||||
expect_false(is_otel_span(123))
|
||||
expect_false(is_otel_span(list()))
|
||||
expect_false(is_otel_span(NULL))
|
||||
|
||||
# Test with object that has different class
|
||||
other_obj <- structure(list(), class = "other_class")
|
||||
expect_false(is_ospan(other_obj))
|
||||
expect_false(is_otel_span(other_obj))
|
||||
})
|
||||
|
||||
test_that("testthat__is_testing detects testing environment", {
|
||||
# Test when TESTTHAT env var is set to "true"
|
||||
withr::local_envvar(list(TESTTHAT = "true"))
|
||||
expect_true(testthat__is_testing())
|
||||
withr::with_envvar(list(TESTTHAT = "true"), {
|
||||
expect_true(testthat__is_testing())
|
||||
})
|
||||
|
||||
# Test when TESTTHAT env var is not set
|
||||
withr::local_envvar(list(TESTTHAT = NA))
|
||||
expect_false(testthat__is_testing())
|
||||
withr::with_envvar(list(TESTTHAT = NA), {
|
||||
expect_false(testthat__is_testing())
|
||||
})
|
||||
|
||||
# Test when TESTTHAT env var is set to other values
|
||||
withr::local_envvar(list(TESTTHAT = "false"))
|
||||
expect_false(testthat__is_testing())
|
||||
withr::with_envvar(list(TESTTHAT = "false"), {
|
||||
expect_false(testthat__is_testing())
|
||||
})
|
||||
|
||||
withr::local_envvar(list(TESTTHAT = ""))
|
||||
expect_false(testthat__is_testing())
|
||||
withr::with_envvar(list(TESTTHAT = ""), {
|
||||
expect_false(testthat__is_testing())
|
||||
})
|
||||
})
|
||||
|
||||
test_that("otel_log calls otel::log with correct parameters", {
|
||||
@@ -131,14 +111,12 @@ test_that("otel_log calls otel::log with correct parameters", {
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
get_ospan_logger = function() mock_logger,
|
||||
{
|
||||
otel_log("test message", severity = "warn")
|
||||
expect_true(log_called)
|
||||
}
|
||||
local_mocked_bindings(
|
||||
shiny_otel_logger = function() mock_logger,
|
||||
)
|
||||
|
||||
otel_log("test message", severity = "warn")
|
||||
expect_true(log_called)
|
||||
})
|
||||
|
||||
test_that("otel_log uses default severity and logger", {
|
||||
@@ -154,14 +132,12 @@ test_that("otel_log uses default severity and logger", {
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
get_ospan_logger = function() mock_logger,
|
||||
{
|
||||
otel_log("default test")
|
||||
expect_true(log_called)
|
||||
}
|
||||
local_mocked_bindings(
|
||||
shiny_otel_logger = function() mock_logger,
|
||||
)
|
||||
|
||||
otel_log("default test")
|
||||
expect_true(log_called)
|
||||
})
|
||||
|
||||
test_that("otel_is_tracing_enabled calls otel::is_tracing_enabled", {
|
||||
@@ -176,15 +152,13 @@ test_that("otel_is_tracing_enabled calls otel::is_tracing_enabled", {
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
get_tracer = function() mock_tracer,
|
||||
{
|
||||
result <- otel_is_tracing_enabled()
|
||||
expect_true(is_tracing_called)
|
||||
expect_true(result)
|
||||
}
|
||||
local_mocked_bindings(
|
||||
shiny_otel_tracer = function() mock_tracer,
|
||||
)
|
||||
|
||||
result <- otel_is_tracing_enabled()
|
||||
expect_true(is_tracing_called)
|
||||
expect_true(result)
|
||||
})
|
||||
|
||||
test_that("otel_is_tracing_enabled accepts custom tracer", {
|
||||
@@ -205,32 +179,33 @@ test_that("otel_is_tracing_enabled accepts custom tracer", {
|
||||
expect_false(result)
|
||||
})
|
||||
|
||||
test_that("get_ospan_logger caches logger in non-test environment", {
|
||||
test_that("shiny_otel_logger caches logger in non-test environment", {
|
||||
mock_logger <- create_mock_logger()
|
||||
get_logger_call_count <- 0
|
||||
|
||||
fn_env <- environment(get_ospan_logger)
|
||||
fn_env <- environment(shiny_otel_logger)
|
||||
# Reset cached logger now and when test ends
|
||||
fn_env$reset_logger()
|
||||
withr::defer({ fn_env$reset_logger() })
|
||||
|
||||
local_mocked_bindings(
|
||||
otel_get_logger = function() {
|
||||
get_logger = function(...) {
|
||||
get_logger_call_count <<- get_logger_call_count + 1
|
||||
mock_logger
|
||||
}
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
testthat__is_testing = function() TRUE,
|
||||
{
|
||||
# First call
|
||||
logger1 <- get_ospan_logger()
|
||||
logger1 <- shiny_otel_logger()
|
||||
expect_equal(logger1, mock_logger)
|
||||
expect_equal(get_logger_call_count, 1)
|
||||
|
||||
# Second call should call otel::get_logger again (no caching in tests)
|
||||
logger2 <- get_ospan_logger()
|
||||
logger2 <- shiny_otel_logger()
|
||||
expect_equal(logger2, mock_logger)
|
||||
expect_equal(get_logger_call_count, 2) # Incremented
|
||||
}
|
||||
@@ -240,12 +215,12 @@ test_that("get_ospan_logger caches logger in non-test environment", {
|
||||
testthat__is_testing = function() FALSE,
|
||||
{
|
||||
# First call should call otel::get_logger
|
||||
logger1 <- get_ospan_logger()
|
||||
logger1 <- shiny_otel_logger()
|
||||
expect_equal(logger1, mock_logger)
|
||||
expect_equal(get_logger_call_count, 3)
|
||||
|
||||
# Second call should use cached logger
|
||||
logger2 <- get_ospan_logger()
|
||||
logger2 <- shiny_otel_logger()
|
||||
expect_equal(logger2, mock_logger)
|
||||
expect_equal(get_logger_call_count, 3) # Still 3, not incremented
|
||||
}
|
||||
@@ -253,32 +228,33 @@ test_that("get_ospan_logger caches logger in non-test environment", {
|
||||
})
|
||||
|
||||
|
||||
test_that("get_tracer caches tracer in non-test environment", {
|
||||
test_that("shiny_otel_tracer caches tracer in non-test environment", {
|
||||
mock_tracer <- create_mock_tracer()
|
||||
get_tracer_call_count <- 0
|
||||
|
||||
fn_env <- environment(get_tracer)
|
||||
fn_env <- environment(shiny_otel_tracer)
|
||||
# Reset cached tracer now and when test ends
|
||||
fn_env$reset_tracer()
|
||||
withr::defer({ fn_env$reset_tracer() })
|
||||
|
||||
local_mocked_bindings(
|
||||
otel_get_tracer = function() {
|
||||
get_tracer = function(...) {
|
||||
get_tracer_call_count <<- get_tracer_call_count + 1
|
||||
mock_tracer
|
||||
}
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
testthat__is_testing = function() TRUE,
|
||||
{
|
||||
# First call
|
||||
tracer1 <- get_tracer()
|
||||
tracer1 <- shiny_otel_tracer()
|
||||
expect_equal(tracer1, mock_tracer)
|
||||
expect_equal(get_tracer_call_count, 1)
|
||||
|
||||
# Second call should call otel::get_tracer again (no caching in tests)
|
||||
tracer2 <- get_tracer()
|
||||
tracer2 <- shiny_otel_tracer()
|
||||
expect_equal(tracer2, mock_tracer)
|
||||
expect_equal(get_tracer_call_count, 2) # Incremented
|
||||
}
|
||||
@@ -288,59 +264,48 @@ test_that("get_tracer caches tracer in non-test environment", {
|
||||
testthat__is_testing = function() FALSE,
|
||||
{
|
||||
# First call should call otel::get_tracer
|
||||
tracer1 <- get_tracer()
|
||||
tracer1 <- shiny_otel_tracer()
|
||||
expect_equal(tracer1, mock_tracer)
|
||||
expect_equal(get_tracer_call_count, 3)
|
||||
|
||||
# Second call should use cached tracer
|
||||
tracer2 <- get_tracer()
|
||||
tracer2 <- shiny_otel_tracer()
|
||||
expect_equal(tracer2, mock_tracer)
|
||||
expect_equal(get_tracer_call_count, 3) # Still 3, not incremented
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test_that("integration test - with_shiny_ospan_async uses cached tracer", {
|
||||
test_that("integration test - with_otel_span uses cached tracer", {
|
||||
mock_tracer <- create_mock_tracer()
|
||||
get_tracer_call_count <- 0
|
||||
with_ospan_async_called <- FALSE
|
||||
|
||||
fn_env <- environment(get_tracer)
|
||||
fn_env <- environment(shiny_otel_tracer)
|
||||
# Reset cached tracer now and when test ends
|
||||
fn_env$reset_tracer()
|
||||
withr::defer({ fn_env$reset_tracer() })
|
||||
|
||||
local_mocked_bindings(
|
||||
otel_get_tracer = function() {
|
||||
get_tracer = function() {
|
||||
get_tracer_call_count <<- get_tracer_call_count + 1
|
||||
mock_tracer
|
||||
}
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
testthat__is_testing = function() FALSE,
|
||||
with_ospan_async = function(name, expr, ..., attributes = NULL, tracer = NULL) {
|
||||
with_ospan_async_called <<- TRUE
|
||||
expect_equal(tracer, mock_tracer)
|
||||
force(expr)
|
||||
},
|
||||
{
|
||||
# First call to with_shiny_ospan_async
|
||||
with_shiny_ospan_async("span1", { "result1" })
|
||||
expect_equal(get_tracer_call_count, 1)
|
||||
expect_true(with_ospan_async_called)
|
||||
|
||||
with_ospan_async_called <- FALSE
|
||||
|
||||
# Second call should use cached tracer
|
||||
with_shiny_ospan_async("span2", { "result2" })
|
||||
expect_equal(get_tracer_call_count, 1) # Still 1, tracer was cached
|
||||
expect_true(with_ospan_async_called)
|
||||
}
|
||||
.package = "otel"
|
||||
)
|
||||
local_mocked_bindings(
|
||||
testthat__is_testing = function() FALSE,
|
||||
)
|
||||
|
||||
# First call to with_otel_span
|
||||
with_otel_span("span1", { "result1" })
|
||||
expect_equal(get_tracer_call_count, 1)
|
||||
|
||||
# Second call should use cached tracer
|
||||
with_otel_span("span2", { "result2" })
|
||||
expect_equal(get_tracer_call_count, 1) # Still 1, tracer was cached
|
||||
})
|
||||
|
||||
test_that("integration test - create_shiny_ospan with custom parameters", {
|
||||
test_that("integration test - start_otel_span with custom parameters", {
|
||||
mock_tracer <- create_mock_tracer()
|
||||
mock_span <- create_mock_otel_span()
|
||||
start_span_params <- list()
|
||||
@@ -356,21 +321,19 @@ test_that("integration test - create_shiny_ospan with custom parameters", {
|
||||
},
|
||||
.package = "otel"
|
||||
)
|
||||
|
||||
with_mocked_bindings(
|
||||
get_tracer = function() mock_tracer,
|
||||
{
|
||||
result <- create_shiny_ospan(
|
||||
"custom_span",
|
||||
attributes = list(key = "value"),
|
||||
parent = "parent_span"
|
||||
)
|
||||
|
||||
expect_equal(result, mock_span)
|
||||
expect_equal(start_span_params$name, "custom_span")
|
||||
expect_equal(start_span_params$tracer, mock_tracer)
|
||||
expect_equal(start_span_params$extra_args$attributes, list(key = "value"))
|
||||
expect_equal(start_span_params$extra_args$parent, "parent_span")
|
||||
}
|
||||
local_mocked_bindings(
|
||||
shiny_otel_tracer = function() mock_tracer,
|
||||
)
|
||||
|
||||
result <- start_otel_span(
|
||||
"custom_span",
|
||||
attributes = list(key = "value"),
|
||||
parent = "parent_span"
|
||||
)
|
||||
|
||||
expect_equal(result, mock_span)
|
||||
expect_equal(start_span_params$name, "custom_span")
|
||||
expect_equal(start_span_params$tracer, mock_tracer)
|
||||
expect_equal(start_span_params$extra_args$attributes, list(key = "value"))
|
||||
expect_equal(start_span_params$extra_args$parent, "parent_span")
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
with_several_promise_domains <- function(expr) {
|
||||
withReactiveDomain(MockShinySession$new(), {
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
with_promise_domain(reactivePromiseDomain(), {
|
||||
captureStackTraces({
|
||||
expr
|
||||
})
|
||||
|
||||
@@ -118,7 +118,7 @@ dumpTests <- function(df) {
|
||||
}
|
||||
|
||||
test_that("integration tests", {
|
||||
if (get_tracer()$is_enabled()) {
|
||||
if (shiny_otel_tracer()$is_enabled()) {
|
||||
announce_snapshot_file(name = "stacks.md")
|
||||
|
||||
skip("Skipping stack trace tests when OpenTelemetry is already enabled")
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
library(shiny)
|
||||
library(testthat)
|
||||
|
||||
test_that("testServer works with dir app", {
|
||||
# app.R
|
||||
testServer(test_path("..", "test-modules", "06_tabsets"), {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
library(shiny)
|
||||
library(testthat)
|
||||
|
||||
test_that("Nested modules", {
|
||||
child <- function(id) {
|
||||
moduleServer(id, function(input, output, session) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
library(shiny)
|
||||
library(testthat)
|
||||
|
||||
test_that("Variables outside of the module are inaccessible", {
|
||||
module <- local({
|
||||
outside <- 123
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
library(shiny)
|
||||
library(testthat)
|
||||
skip_if_not_installed("future")
|
||||
library(future, warn.conflicts = FALSE)
|
||||
library(promises)
|
||||
|
||||
test_that("handles observers", {
|
||||
server <- function(input, output, session) {
|
||||
@@ -746,7 +744,7 @@ test_that("promise chains evaluate in correct order", {
|
||||
|
||||
server <- function(input, output, session) {
|
||||
r1 <- reactive({
|
||||
promise(function(resolve, reject) {
|
||||
promises::promise(function(resolve, reject) {
|
||||
pushMessage("promise 1")
|
||||
resolve(input$go)
|
||||
})$then(function(value) {
|
||||
@@ -755,7 +753,7 @@ test_that("promise chains evaluate in correct order", {
|
||||
})
|
||||
})
|
||||
r2 <- reactive({
|
||||
promise(function(resolve, reject) {
|
||||
promises::promise(function(resolve, reject) {
|
||||
pushMessage("promise 2")
|
||||
resolve(input$go)
|
||||
})$then(function(value) {
|
||||
|
||||
Reference in New Issue
Block a user