Skip to content

Commit 1215b88

Browse files
authored
chore: updated scope name, enable setting up meter (#331)
1 parent 52677ab commit 1215b88

File tree

4 files changed

+116
-29
lines changed

4 files changed

+116
-29
lines changed

src/strands/telemetry/config.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
import logging
88
from importlib.metadata import version
99

10+
import opentelemetry.metrics as metrics_api
11+
import opentelemetry.sdk.metrics as metrics_sdk
1012
import opentelemetry.trace as trace_api
1113
from opentelemetry import propagate
1214
from opentelemetry.baggage.propagation import W3CBaggagePropagator
1315
from opentelemetry.propagators.composite import CompositePropagator
16+
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader
1417
from opentelemetry.sdk.resources import Resource
1518
from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider
1619
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SimpleSpanProcessor
@@ -65,6 +68,9 @@ class StrandsTelemetry:
6568
>>> telemetry.setup_console_exporter()
6669
>>> telemetry.setup_otlp_exporter()
6770
71+
To setup global meter provider
72+
>>> telemetry.setup_meter(enable_console_exporter=True, enable_otlp_exporter=True) # default are False
73+
6874
Note:
6975
- The tracer provider is automatically initialized upon instantiation
7076
- When no tracer_provider is provided, the instance sets itself as the global provider
@@ -86,15 +92,15 @@ def __init__(
8692
The instance is ready to use immediately after initialization, though
8793
trace exporters must be configured separately using the setup methods.
8894
"""
95+
self.resource = get_otel_resource()
8996
if tracer_provider:
9097
self.tracer_provider = tracer_provider
9198
else:
92-
self.resource = get_otel_resource()
9399
self._initialize_tracer()
94100

95101
def _initialize_tracer(self) -> None:
96102
"""Initialize the OpenTelemetry tracer."""
97-
logger.info("initializing tracer")
103+
logger.info("Initializing tracer")
98104

99105
# Create tracer provider
100106
self.tracer_provider = SDKTracerProvider(resource=self.resource)
@@ -115,7 +121,7 @@ def _initialize_tracer(self) -> None:
115121
def setup_console_exporter(self) -> "StrandsTelemetry":
116122
"""Set up console exporter for the tracer provider."""
117123
try:
118-
logger.info("enabling console export")
124+
logger.info("Enabling console export")
119125
console_processor = SimpleSpanProcessor(ConsoleSpanExporter())
120126
self.tracer_provider.add_span_processor(console_processor)
121127
except Exception as e:
@@ -134,3 +140,30 @@ def setup_otlp_exporter(self) -> "StrandsTelemetry":
134140
except Exception as e:
135141
logger.exception("error=<%s> | Failed to configure OTLP exporter", e)
136142
return self
143+
144+
def setup_meter(
145+
self, enable_console_exporter: bool = False, enable_otlp_exporter: bool = False
146+
) -> "StrandsTelemetry":
147+
"""Initialize the OpenTelemetry Meter."""
148+
logger.info("Initializing meter")
149+
metrics_readers = []
150+
try:
151+
if enable_console_exporter:
152+
logger.info("Enabling console metrics exporter")
153+
console_reader = PeriodicExportingMetricReader(ConsoleMetricExporter())
154+
metrics_readers.append(console_reader)
155+
if enable_otlp_exporter:
156+
logger.info("Enabling OTLP metrics exporter")
157+
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
158+
159+
otlp_reader = PeriodicExportingMetricReader(OTLPMetricExporter())
160+
metrics_readers.append(otlp_reader)
161+
except Exception as e:
162+
logger.exception("error=<%s> | Failed to configure OTLP metrics exporter", e)
163+
164+
self.meter_provider = metrics_sdk.MeterProvider(resource=self.resource, metric_readers=metrics_readers)
165+
166+
# Set as global tracer provider
167+
metrics_api.set_meter_provider(self.meter_provider)
168+
logger.info("Strands Meter configured")
169+
return self

src/strands/telemetry/tracer.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,9 @@ class Tracer:
8181

8282
def __init__(
8383
self,
84-
service_name: str = "strands-agents",
85-
):
86-
"""Initialize the tracer.
87-
88-
Args:
89-
service_name: Name of the service for OpenTelemetry.
90-
"""
91-
self.service_name = service_name
84+
) -> None:
85+
"""Initialize the tracer."""
86+
self.service_name = __name__
9287
self.tracer_provider: Optional[trace_api.TracerProvider] = None
9388
self.tracer: Optional[trace_api.Tracer] = None
9489

@@ -505,9 +500,7 @@ def end_agent_span(
505500
_tracer_instance = None
506501

507502

508-
def get_tracer(
509-
service_name: str = "strands-agents",
510-
) -> Tracer:
503+
def get_tracer() -> Tracer:
511504
"""Get or create the global tracer.
512505
513506
Args:
@@ -519,9 +512,7 @@ def get_tracer(
519512
global _tracer_instance
520513

521514
if not _tracer_instance:
522-
_tracer_instance = Tracer(
523-
service_name=service_name,
524-
)
515+
_tracer_instance = Tracer()
525516

526517
return _tracer_instance
527518

tests/strands/telemetry/test_config.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ def mock_set_tracer_provider():
3333
yield mock_set
3434

3535

36+
@pytest.fixture
37+
def mock_meter_provider():
38+
with mock.patch("strands.telemetry.config.metrics_sdk.MeterProvider") as mock_meter_provider:
39+
yield mock_meter_provider
40+
41+
42+
@pytest.fixture
43+
def mock_metrics_api():
44+
with mock.patch("strands.telemetry.config.metrics_api") as mock_metrics_api:
45+
yield mock_metrics_api
46+
47+
3648
@pytest.fixture
3749
def mock_set_global_textmap():
3850
with mock.patch("strands.telemetry.config.propagate.set_global_textmap") as mock_set_global_textmap:
@@ -45,6 +57,26 @@ def mock_console_exporter():
4557
yield mock_console_exporter
4658

4759

60+
@pytest.fixture
61+
def mock_reader():
62+
with mock.patch("strands.telemetry.config.PeriodicExportingMetricReader") as mock_reader:
63+
yield mock_reader
64+
65+
66+
@pytest.fixture
67+
def mock_console_metrics_exporter():
68+
with mock.patch("strands.telemetry.config.ConsoleMetricExporter") as mock_console_metrics_exporter:
69+
yield mock_console_metrics_exporter
70+
71+
72+
@pytest.fixture
73+
def mock_otlp_metrics_exporter():
74+
with mock.patch(
75+
"opentelemetry.exporter.otlp.proto.http.metric_exporter.OTLPMetricExporter"
76+
) as mock_otlp_metrics_exporter:
77+
yield mock_otlp_metrics_exporter
78+
79+
4880
@pytest.fixture
4981
def mock_otlp_exporter():
5082
with mock.patch("opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter") as mock_otlp_exporter:
@@ -88,6 +120,48 @@ def test_init_default(mock_resource, mock_tracer_provider, mock_set_tracer_provi
88120
mock_set_global_textmap.assert_called()
89121

90122

123+
def test_setup_meter_with_console_exporter(
124+
mock_resource,
125+
mock_reader,
126+
mock_console_metrics_exporter,
127+
mock_otlp_metrics_exporter,
128+
mock_metrics_api,
129+
mock_meter_provider,
130+
):
131+
"""Test add console metrics exporter"""
132+
mock_metrics_api.MeterProvider.return_value = mock_meter_provider
133+
134+
telemetry = StrandsTelemetry()
135+
telemetry.setup_meter(enable_console_exporter=True)
136+
137+
mock_console_metrics_exporter.assert_called_once()
138+
mock_reader.assert_called_once_with(mock_console_metrics_exporter.return_value)
139+
mock_otlp_metrics_exporter.assert_not_called()
140+
141+
mock_metrics_api.set_meter_provider.assert_called_once()
142+
143+
144+
def test_setup_meter_with_console_and_otlp_exporter(
145+
mock_resource,
146+
mock_reader,
147+
mock_console_metrics_exporter,
148+
mock_otlp_metrics_exporter,
149+
mock_metrics_api,
150+
mock_meter_provider,
151+
):
152+
"""Test add console and otlp metrics exporter"""
153+
mock_metrics_api.MeterProvider.return_value = mock_meter_provider
154+
155+
telemetry = StrandsTelemetry()
156+
telemetry.setup_meter(enable_console_exporter=True, enable_otlp_exporter=True)
157+
158+
mock_console_metrics_exporter.assert_called_once()
159+
mock_otlp_metrics_exporter.assert_called_once()
160+
assert mock_reader.call_count == 2
161+
162+
mock_metrics_api.set_meter_provider.assert_called_once()
163+
164+
91165
def test_setup_console_exporter(mock_resource, mock_tracer_provider, mock_console_exporter, mock_simple_processor):
92166
"""Test add console exporter"""
93167

tests/strands/telemetry/test_tracer.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_init_default():
5252
"""Test initializing the Tracer with default parameters."""
5353
tracer = Tracer()
5454

55-
assert tracer.service_name == "strands-agents"
55+
assert tracer.service_name == "strands.telemetry.tracer"
5656
assert tracer.tracer_provider is not None
5757
assert tracer.tracer is not None
5858

@@ -347,17 +347,6 @@ def test_get_tracer_new_endpoint():
347347
assert tracer1 is tracer2
348348

349349

350-
def test_get_tracer_parameters():
351-
"""Test that get_tracer passes parameters correctly."""
352-
# Reset the singleton first
353-
with mock.patch("strands.telemetry.tracer._tracer_instance", None):
354-
tracer = get_tracer(
355-
service_name="test-service",
356-
)
357-
358-
assert tracer.service_name == "test-service"
359-
360-
361350
def test_initialize_tracer_with_custom_tracer_provider(mock_get_tracer_provider):
362351
"""Test initializing the tracer with NoOpTracerProvider."""
363352
tracer = Tracer()

0 commit comments

Comments
 (0)