Skip to content

fix(telemetry): make ThreadingInstrumentor opt-in with env-var precedence#2167

Open
jpr5 wants to merge 1 commit intostrands-agents:mainfrom
jpr5:fix/threading-instrumentor-opt-in
Open

fix(telemetry): make ThreadingInstrumentor opt-in with env-var precedence#2167
jpr5 wants to merge 1 commit intostrands-agents:mainfrom
jpr5:fix/threading-instrumentor-opt-in

Conversation

@jpr5
Copy link
Copy Markdown

@jpr5 jpr5 commented Apr 20, 2026

strands-agents>=1.35.0 unconditionally calls ThreadingInstrumentor().instrument() on Tracer construction. When another OTel setup (Azure Monitor, opentelemetry-distro, Langfuse autoloader, etc.) already wraps concurrent.futures.ThreadPoolExecutor.submit, or when the host sets OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=threading, strands still installs its own wrapper — defeating the documented disable mechanism and imposing global Python state mutation on every strands user.

Tracer now accepts an instrument_threading: bool | None kwarg. Resolver precedence (highest → lowest):

  1. OTEL_PYTHON_DISABLED_INSTRUMENTATIONS containing 'threading' → always disabled (matches OpenTelemetry's auto-loader semantics).
  2. Explicit Tracer(instrument_threading=...) kwarg.
  3. STRANDS_INSTRUMENT_THREADING env var ('1'/'true'/'yes').
  4. Default: disabled.

_maybe_instrument_threading checks the underscore-prefixed private flag _is_instrumented_by_opentelemetry to avoid stacking wrappers when the host has already installed the instrumentor. instrument() failures are caught and logged (error level when the user explicitly requested, warning level otherwise) so telemetry failure never crashes the host.

Regression tests run in isolated subprocesses because BaseInstrumentor is a process-wide singleton. Tests cover default-off, env-var opt-in, kwarg opt-in, env-var-disabled-precedence-over-kwarg-opt-in, kwarg-false-overrides-env-opt-in, idempotency when already instrumented, graceful failure on instrument() exceptions, and user-requested failures logged at ERROR while auto-enabled failures log at WARNING.

Downstream workaround in CopilotKit PR #4083 (commit 9227bc27d, showcase/packages/strands/src/agent_server.py) neutralized the instrumentor at import time; this removes the need for that workaround.

…ence

strands-agents>=1.35.0 unconditionally calls ThreadingInstrumentor().
instrument() on Tracer construction. When another OTel setup (Azure
Monitor, opentelemetry-distro, Langfuse autoloader, etc.) already
wraps concurrent.futures.ThreadPoolExecutor.submit, or when the host
sets OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=threading, strands still
installs its own wrapper — defeating the documented disable mechanism
and imposing global Python state mutation on every strands user.

Tracer now accepts an instrument_threading: bool | None kwarg. Resolver
precedence (highest → lowest):
  1. OTEL_PYTHON_DISABLED_INSTRUMENTATIONS containing 'threading' →
     always disabled (matches OpenTelemetry's auto-loader semantics).
  2. Explicit Tracer(instrument_threading=...) kwarg.
  3. STRANDS_INSTRUMENT_THREADING env var ('1'/'true'/'yes').
  4. Default: disabled.

_maybe_instrument_threading checks the underscore-prefixed private flag
_is_instrumented_by_opentelemetry to avoid stacking wrappers when the
host has already installed the instrumentor. instrument() failures are
caught and logged (error level when the user explicitly requested,
warning level otherwise) so telemetry failure never crashes the host.

Regression tests run in isolated subprocesses because BaseInstrumentor
is a process-wide singleton. Tests cover default-off, env-var opt-in,
kwarg opt-in, env-var-disabled-precedence-over-kwarg-opt-in,
kwarg-false-overrides-env-opt-in, idempotency when already instrumented,
graceful failure on instrument() exceptions, and user-requested failures
logged at ERROR while auto-enabled failures log at WARNING.

Downstream workaround in CopilotKit PR #4083 (commit 9227bc27d,
showcase/packages/strands/src/agent_server.py) neutralized the
instrumentor at import time; this removes the need for that workaround.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant