From 50ae78688068eab56257f5b707521c598dafb77c Mon Sep 17 00:00:00 2001 From: Simon Couch Date: Thu, 3 Sep 2026 11:15:43 -0500 Subject: [PATCH] repair otelsdk envvars --- pkg-r/R/tracing.R | 22 +++++++++++++++++ pkg-r/tests/testthat/test-tracing.R | 38 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/pkg-r/R/tracing.R b/pkg-r/R/tracing.R index 1a4784c2..af20d9f4 100644 --- a/pkg-r/R/tracing.R +++ b/pkg-r/R/tracing.R @@ -102,11 +102,33 @@ repair_connect_trace_routing <- function() { paste0("job.key=", job_key) ) Sys.setenv(OTEL_RESOURCE_ATTRIBUTES = paste(pairs, collapse = ",")) + repair_otelsdk_resource_attributes(guid, job_key) reset_otel_tracer_provider() refresh_ellmer_otel_cache() invisible(TRUE) } +# otelsdk's C++ SDK statically caches its environment-derived resource when +# ellmer first initializes OTel. Rebuilding the provider does not refresh it. +repair_otelsdk_resource_attributes <- function(guid, job_key) { + if (!is_installed("otelsdk")) { + return(invisible(NULL)) + } + tryCatch( + { + the <- asNamespace("otelsdk")$the + attributes <- the$default_resource_attributes + if (is.environment(the) && is.list(attributes)) { + attributes[["content.guid"]] <- guid + attributes[["job.key"]] <- job_key + the$default_resource_attributes <- attributes + } + }, + error = function(err) NULL + ) + invisible(NULL) +} + # Start and activate a span for the calling frame's lifetime, ending when it # exits. Unlike trajectory logging, these setup spans aren't gated behind # `log = TRUE`: they cover product setup (data source and agent construction), diff --git a/pkg-r/tests/testthat/test-tracing.R b/pkg-r/tests/testthat/test-tracing.R index c052a3ea..3685eefd 100644 --- a/pkg-r/tests/testthat/test-tracing.R +++ b/pkg-r/tests/testthat/test-tracing.R @@ -226,6 +226,39 @@ test_that("content capture is enabled when unset", { ) }) +test_that("Connect routing repairs otelsdk's cached resource", { + skip_on_cran() + skip_if_not_installed("otelsdk") + withr::local_envvar( + POSIT_PRODUCT = "CONNECT", + CONNECT_CONTENT_GUID = "content-guid", + CONNECT_CONTENT_JOB_KEY = "job-key", + OTEL_RESOURCE_ATTRIBUTES = "k8s.namespace.name=test" + ) + the <- asNamespace("otelsdk")$the + old_attributes <- the$default_resource_attributes + withr::defer(the$default_resource_attributes <- old_attributes) + local_mocked_bindings( + reset_otel_tracer_provider = function() NULL, + refresh_ellmer_otel_cache = function() NULL + ) + + expect_true(repair_connect_trace_routing()) + expect_equal( + Sys.getenv("OTEL_RESOURCE_ATTRIBUTES"), + paste( + "k8s.namespace.name=test", + "content.guid=content-guid", + "job.key=job-key", + sep = "," + ) + ) + expect_equal( + the$default_resource_attributes[c("content.guid", "job.key")], + list("content.guid" = "content-guid", "job.key" = "job-key") + ) +}) + test_that("share_with grants wait for tracing to be live", { skip_if_not_installed("otel") withr::local_envvar(CONNECT_CONTENT_GUID = "guid") @@ -255,6 +288,11 @@ test_that("the internals the tracing hacks rely on still exist", { the <- asNamespace("otel")[["the"]] expect_true(is.environment(the)) expect_true(exists("tracer_provider", envir = the, inherits = FALSE)) + + skip_if_not_installed("otelsdk") + sdk_the <- asNamespace("otelsdk")[["the"]] + expect_true(is.environment(sdk_the)) + expect_type(sdk_the$default_resource_attributes, "list") }) test_that("local_commons_span is a no-op without otel", {