Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions python/scenario/_tracing/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import logging
import os
from typing import List, Optional, Sequence
from typing import List, Optional, Protocol, Sequence, cast

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
Expand All @@ -25,6 +25,10 @@
_initialized = False


class _SpanProcessorProvider(Protocol):
def add_span_processor(self, span_processor: SpanProcessor) -> None: ...


def setup_scenario_tracing(
*,
span_filter: Optional[SpanFilter] = None,
Expand Down Expand Up @@ -88,7 +92,7 @@ def ensure_tracing_initialized(observability: Optional[dict] = None) -> None:
_initialized = True


def _get_concrete_provider(provider) -> Optional[TracerProvider]:
def _get_concrete_provider(provider) -> Optional[_SpanProcessorProvider]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Complete the new Python annotations.

  • python/scenario/_tracing/setup.py#L95-L95: annotate provider.
  • python/scenario/_tracing/setup.py#L140-L145: parameterize Sequence with the instrumentor type.
  • python/tests/test_tracing_setup.py#L98-L99: annotate the first span_processor parameter.
  • python/tests/test_tracing_setup.py#L146-L147: annotate the second span_processor parameter.

As per coding guidelines, Python functions under python/**/*.py must use explicit, specific type annotations.

📍 Affects 2 files
  • python/scenario/_tracing/setup.py#L95-L95 (this comment)
  • python/scenario/_tracing/setup.py#L140-L145
  • python/tests/test_tracing_setup.py#L98-L99
  • python/tests/test_tracing_setup.py#L146-L147
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/scenario/_tracing/setup.py` at line 95, Complete the explicit Python
annotations: annotate the provider parameter in _get_concrete_provider,
parameterize the Sequence return or parameter annotation with the instrumentor
type in setup.py, and annotate both span_processor parameters in the affected
test functions. Apply the changes at python/scenario/_tracing/setup.py lines 95
and 140-145, and python/tests/test_tracing_setup.py lines 98-99 and 146-147.

Source: Coding guidelines

"""Returns the concrete TracerProvider if one exists.

Checks the provider itself and one level of delegation
Expand All @@ -97,6 +101,12 @@ def _get_concrete_provider(provider) -> Optional[TracerProvider]:
if isinstance(provider, TracerProvider):
return provider

# OpenTelemetry's public provider interface does not require an SDK
# TracerProvider subclass. Providers such as Temporal's replay-safe wrapper
# expose the processor hook directly and are safe to configure in place.
if callable(getattr(provider, "add_span_processor", None)):
return cast(_SpanProcessorProvider, provider)

Comment on lines +104 to +109

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg 'python/scenario/_tracing/setup\.py|python/tests/test_tracing_setup\.py|pyproject\.toml|requirements|poetry.lock|uv.lock' || true

echo "== setup relevant lines =="
wc -l python/scenario/_tracing/setup.py python/tests/test_tracing_setup.py
sed -n '1,170p' python/scenario/_tracing/setup.py
sed -n '80,170p' python/tests/test_tracing_setup.py

echo "== package opentelemetry deps =="
rg -n "opentelemetry|otel" python pyproject.toml Pipfile requirements.txt uv.lock poetry.lock 2>/dev/null || true

echo "== source references for get_tracers/add_span_processor =="
rg -n "get_tracer|_get_concrete_provider|add_span_processor|_do_setup|tracer_provider" python/scenario/_tracing/setup.py python/tests/test_tracing_setup.py

Repository: langwatch/scenario

Length of output: 39749


🌐 Web query:

OpenTelemetry Python TracerProvider defines get_tracer tracer_provider argument instrumentation source

💡 Result:

In the OpenTelemetry Python SDK, the TracerProvider.get_tracer method is used to acquire a Tracer instance [1][2]. The argument often referred to as the "instrumentation source" is the instrumenting_module_name [1][3][2]. The get_tracer method signature is as follows [1][4]: get_tracer(instrumenting_module_name, instrumenting_library_version=None, schema_url=None, attributes=None) Key details regarding this argument include: 1. Identification: The instrumenting_module_name acts as a unique identifier for the instrumentation scope [1][2]. It should represent the library, package, module, or class that is performing the instrumentation [1][3]. 2. Naming Convention: It is explicitly recommended to use a fixed string—such as the full name of the instrumentation package—rather than using the dynamic name variable, as name can vary depending on which file the tracer is initialized in [1][4][2]. 3. Instrumentation vs. Instrumented: A critical distinction is that this name should identify the code doing the instrumentation, not the library being instrumented [1][2][5]. For example, when instrumenting the "requests" library, one should use "opentelemetry.instrumentation.requests" rather than just "requests" [1][3][2]. 4. Convenience Wrapper: The opentelemetry.trace.get_tracer function serves as a convenience wrapper for the TracerProvider.get_tracer method [1][2]. If a TracerProvider is not explicitly provided, it defaults to the currently configured global TracerProvider [1][4][2]. These identifiers (name and version) are crucial for telemetry processing, allowing users to understand the source of the data, filter signals, and debug instrumentation issues [6][5][7].

Citations:


🌐 Web query:

opentelemetry-python instrument.py inject tracer_provider get_tracer provider.tracer_provider

💡 Result:

In OpenTelemetry Python, the tracer_provider and the get_tracer function are core components used to manage and create tracers for instrumentation [1][2][3]. TracerProvider The TracerProvider acts as the factory for tracers [2][4]. You typically initialize a TracerProvider, configure it with span processors and exporters, and then set it as the global default provider [1][3]. Global Configuration To set the global TracerProvider, you use: from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider provider = TracerProvider trace.set_tracer_provider(provider) This allows other parts of your application to access the same provider via trace.get_tracer_provider [5][2]. get_tracer The get_tracer function is a convenience wrapper provided by the opentelemetry.trace API to create a tracer [5][2]. Its signature is: get_tracer(instrumenting_module_name, instrumenting_library_version=None, tracer_provider=None, schema_url=None, attributes=None) Usage Patterns: 1. Using the Global Provider (Default): If you omit the tracer_provider argument, the function automatically uses the currently configured global tracer provider [5][2]. tracer = trace.get_tracer("my.instrumentation.name") 2. Providing a Specific Provider: If you need to use a specific, non-global provider (e.g., in advanced scenarios requiring different resource configurations), you can pass it explicitly [6]: tracer = trace.get_tracer("my.instrumentation.name", tracer_provider=my_custom_provider) Regarding your query about instrument.py, note that automatic instrumentation tools like the opentelemetry-instrument CLI automatically handle the configuration of the global tracer provider based on environment variables [7]. Manual instrumentation requires explicitly initializing these components in your code as shown above [8][3].

Citations:


🌐 Web query:

site:github.com/open-telemetry/opentelemetry-python-contrib tracer_provider.tracer_provider get_tracer tracer_provider

💡 Result:

In OpenTelemetry Python, the expression tracer_provider.get_tracer refers to the method used to retrieve a tracer instance from a specific TracerProvider object [1]. While users often access the global tracer provider via trace.get_tracer_provider [2][3], libraries and instrumentations frequently accept an optional tracer_provider argument [4][5]. When this argument is provided, the instrumentation uses that specific TracerProvider instance to create its tracer via the get_tracer method, falling back to the global provider only if one is not explicitly provided [4][5][6]. The standard signature for this method is typically: tracer = tracer_provider.get_tracer( instrumenting_module_name, instrumenting_library_version=None, schema_url=None) This mechanism ensures that instrumented code can be configured with a specific provider (which may have unique resource attributes or span processors) rather than relying solely on the global state [4][5].

Citations:


🌐 Web query:

opentelemetry-instrumentation-requests get_tracer tracer_provider Instrumentor._instrument tracer_provider

💡 Result:

In the OpenTelemetry Python instrumentation for the requests library, the components you mentioned—get_tracer, tracer_provider, and Instrumentor._instrument—are part of the internal implementation used to set up and manage telemetry collection [1][2]. 1. tracer_provider: This is an optional argument passed to the instrumentation methods [3][4]. It allows you to specify a custom TracerProvider if you do not wish to use the global default provider [3][4]. 2. get_tracer: Inside the instrumentation package, this function is called to retrieve a tracer instance [1][2]. It typically uses the provided tracer_provider (or the global default) along with library-specific information (like name and version) to initialize a tracer that records spans for outgoing HTTP requests [1][2]. 3. Instrumentor._instrument: The RequestsInstrumentor class inherits from BaseInstrumentor [3][4]. The _instrument method is an internal implementation method that you generally do not call directly [5]. When you call.instrument on the RequestsInstrumentor instance, it internally calls this _instrument method, passing along configuration options like the tracer_provider [4][5]. This method then proceeds to perform the actual monkey-patching of the requests library to enable tracing [4][6]. In typical usage, you do not need to interact with these internal methods. Instead, you use the public API: from opentelemetry.instrumentation.requests import RequestsInstrumentor # Automatically instruments 'requests' using global providers RequestsInstrumentor.instrument # Or, optionally pass a custom TracerProvider RequestsInstrumentor.instrument(tracer_provider=my_custom_tracer_provider)

Citations:


🌐 Web query:

github.com/open-telemetry/opentelemetry-instrumentation BaseInstrumentor tracer_provider get_tracer tracer_provider

💡 Result:

In OpenTelemetry Python instrumentation, the BaseInstrumentor class serves as an abstract base class for creating instrumentation libraries [1][2]. While BaseInstrumentor itself provides methods for instrumenting and uninstrumenting libraries [2], the acquisition of a tracer is handled by the OpenTelemetry API's tracer provider mechanisms [3][4]. To acquire a tracer within an instrumentation, you use the TracerProvider's get_tracer method [3][4]. The recommended approach involves the following steps: 1. Tracer Acquisition: Use opentelemetry.trace.get_tracer. This is a convenience wrapper that delegates to the configured TracerProvider [3][4]. 2. Tracer Provider Handling: While you can call trace.get_tracer directly (which defaults to the global tracer provider), many custom instrumentors are designed to accept an optional tracer_provider as a keyword argument (e.g., in their init or instrument method) [5][6]. If a tracer_provider is provided, it should be used to create the tracer; otherwise, the global tracer provider is used [3][7][4]. 3. Scope Identification: When calling get_tracer, you must provide a unique identifier for the instrumentation scope (instrumenting_module_name), typically the name of the package or module performing the instrumentation [3][4]. It is best practice to use a fixed string rather than name, as the latter can result in inconsistent tracer names if the instrumentation spans multiple files [4][8]. Example usage pattern: from opentelemetry import trace # Inside your instrumentor tracer = trace.get_tracer( instrumenting_module_name="my.instrumentation.package", instrumenting_library_version="1.0.0", tracer_provider=self.tracer_provider # If optional provider was passed to the instrumentor) This pattern ensures that your instrumentation can be easily integrated into different environments, including those that require custom tracer providers rather than the global default [7][5][6].

Citations:


🌐 Web query:

site:github.com/open-telemetry/opentelemetry-python-contrib "tracer_provider.tracer_provider" "tracer_provider.get_tracer" "instrumentor.py"

💡 Result:

In the context of the OpenTelemetry Python instrumentation libraries, tracer_provider and get_tracer are used to manage how spans are created and reported [1][2]. The standard pattern for OpenTelemetry instrumentations involves using a tracer provider to create tracer instances, which are then used to instrument library code [1][2][3]. Key aspects of this pattern include: 1. Tracer Provider Usage: Instrumentations are designed to accept an optional tracer_provider argument [1][2][3]. If a tracer provider is provided, the instrumentation uses it to create its tracer; if it is not provided, the instrumentation falls back to the global tracer provider [1][2][3]. This is important because different tracer providers can have different configurations, such as distinct Resource attributes [1][2]. 2. Implementation in instrumentor.py: The instrumentor.py file, located in the opentelemetry-instrumentation package, defines the BaseInstrumentor class [4]. While the base class provides the structure for instrumentation lifecycle management (such as instrument and uninstrument methods) [5][4], the actual tracer initialization typically occurs within the specific instrumentation packages (e.g., opentelemetry-instrumentation-fastapi or opentelemetry-instrumentation-aws-lambda) [6][3]. 3. Creating the Tracer: The get_tracer function from the opentelemetry.trace API is used to instantiate the tracer [3]. It is commonly called as follows: tracer = get_tracer(name, version, tracer_provider=tracer_provider, schema_url=...) [3] This ensures that the instrumentation is properly associated with the correct tracer provider and semantic convention schema [3].

Citations:


🌐 Web query:

github.com/open-telemetry/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py tracer_provider

💡 Result:

In the OpenTelemetry Python Requests instrumentation library, the tracer_provider is an optional keyword argument that can be passed to the instrument method of the RequestsInstrumentor class [1][2]. When invoking RequestsInstrumentor.instrument, you can provide a custom TracerProvider instance to control where spans are sent [1][3]. If no tracer_provider is specified, the instrumentation defaults to using the global TracerProvider (configured via trace.set_tracer_provider) [4][2]. Example usage: from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.sdk.trace import TracerProvider # Create a custom tracer provider provider = TracerProvider # Pass the provider to the instrumentor RequestsInstrumentor.instrument(tracer_provider=provider) The tracer_provider argument allows developers to use specific, isolated tracing configurations for their requests, rather than relying on the globally registered provider [4][2]. Internal implementations of the instrumentation use this provider when initializing the tracer via get_tracer [5][2].

Citations:


Require the full tracer-provider contract before instrumenting.

instrumentor.instrument(tracer_provider=provider) may pass this object into OpenTelemetry instrumentation code, where it is expected to support get_tracer(...); only add_span_processor leaves the contract incomplete. Require both methods in _SpanProcessorProvider, _get_concrete_provider, and the provider test doubles.

📍 Affects 2 files
  • python/scenario/_tracing/setup.py#L104-L109 (this comment)
  • python/scenario/_tracing/setup.py#L28-L31
  • python/tests/test_tracing_setup.py#L96-L106
  • python/tests/test_tracing_setup.py#L144-L157
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/scenario/_tracing/setup.py` around lines 104 - 109, Require providers
to expose both get_tracer and add_span_processor before treating them as
_SpanProcessorProvider instances: update _SpanProcessorProvider,
_get_concrete_provider, and all provider test doubles in
python/scenario/_tracing/setup.py:28-31,
python/scenario/_tracing/setup.py:104-109,
python/tests/test_tracing_setup.py:96-106, and
python/tests/test_tracing_setup.py:144-157. Ensure only providers satisfying the
full contract are passed to instrumentation.

# Check delegation pattern
delegate = None
if hasattr(provider, "get_delegate"):
Expand All @@ -122,16 +132,17 @@ def _do_setup(
concrete = _get_concrete_provider(existing_provider)

if concrete is not None:
_attach_to_existing(concrete, span_filter, span_processors, trace_exporter)
_attach_to_existing(concrete, span_filter, span_processors, trace_exporter, instrumentors)
else:
_full_setup(span_filter, span_processors, trace_exporter, instrumentors)


def _attach_to_existing(
provider: TracerProvider,
provider: _SpanProcessorProvider,
span_filter: Optional[SpanFilter],
span_processors: Optional[List[SpanProcessor]],
trace_exporter: Optional[SpanExporter],
instrumentors: Optional[Sequence],
) -> None:
"""Attach processors to an existing TracerProvider."""
provider.add_span_processor(judge_span_collector)
Expand All @@ -150,6 +161,10 @@ def _attach_to_existing(
# Add LangWatch exporter
_add_langwatch_exporter(provider, span_filter)

if instrumentors:
for instrumentor in instrumentors:
instrumentor.instrument(tracer_provider=provider)


def _full_setup(
span_filter: Optional[SpanFilter],
Expand Down
26 changes: 26 additions & 0 deletions python/tests/test_tracing_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
setup_scenario_tracing,
ensure_tracing_initialized,
_get_concrete_provider,
_do_setup,
_reset_tracing_for_tests,
)

Expand Down Expand Up @@ -92,6 +93,17 @@ def test_returns_tracer_provider_directly(self) -> None:

assert result is provider

def test_returns_public_provider_with_span_processor_support(self) -> None:
class PublicProvider:
def add_span_processor(self, span_processor) -> None:
del span_processor

provider = PublicProvider()

result = _get_concrete_provider(provider)

assert result is provider

def test_returns_delegate_from_get_delegate(self) -> None:
concrete = TracerProvider()

Expand Down Expand Up @@ -129,6 +141,20 @@ class FakeProxy:
assert result is None


def test_instruments_an_existing_public_provider() -> None:
class PublicProvider:
def add_span_processor(self, span_processor) -> None:
del span_processor

provider = PublicProvider()
instrumentor = MagicMock()

with patch("scenario._tracing.setup.trace.get_tracer_provider", return_value=provider):
_do_setup(instrumentors=[instrumentor])

instrumentor.instrument.assert_called_once_with(tracer_provider=provider)


class TestResetTracingForTests:
"""Tests for _reset_tracing_for_tests."""

Expand Down