@@ -14,6 +14,7 @@ def mock_traceloop_components():
1414 mocks = {
1515 'traceloop' : stack .enter_context (patch ('sap_cloud_sdk.core.telemetry.auto_instrument.Traceloop' )),
1616 'exporter' : stack .enter_context (patch ('sap_cloud_sdk.core.telemetry.auto_instrument.OTLPSpanExporter' )),
17+ 'console_exporter' : stack .enter_context (patch ('sap_cloud_sdk.core.telemetry.auto_instrument.ConsoleSpanExporter' )),
1718 'transformer' : stack .enter_context (patch ('sap_cloud_sdk.core.telemetry.auto_instrument.GenAIAttributeTransformer' )),
1819 'create_resource' : stack .enter_context (patch ('sap_cloud_sdk.core.telemetry.auto_instrument.create_resource_attributes_from_env' )),
1920 'get_app_name' : stack .enter_context (patch ('sap_cloud_sdk.core.telemetry.auto_instrument._get_app_name' )),
@@ -24,17 +25,6 @@ def mock_traceloop_components():
2425class TestAutoInstrument :
2526 """Test suite for auto_instrument function."""
2627
27- def test_auto_instrument_without_endpoint (self ):
28- """Test that auto_instrument warns when OTEL_EXPORTER_OTLP_ENDPOINT is not set."""
29- with patch .dict ('os.environ' , {}, clear = True ):
30- with patch ('sap_cloud_sdk.core.telemetry.auto_instrument.logger' ) as mock_logger :
31- auto_instrument ()
32-
33- # Should log warning about missing endpoint
34- mock_logger .warning .assert_called_once ()
35- warning_message = mock_logger .warning .call_args [0 ][0 ]
36- assert "OTEL_EXPORTER_OTLP_ENDPOINT not set" in warning_message
37-
3828 def test_auto_instrument_with_endpoint_success (self , mock_traceloop_components ):
3929 """Test successful auto-instrumentation with valid endpoint."""
4030 mock_traceloop_components ['get_app_name' ].return_value = 'test-app'
@@ -146,12 +136,73 @@ def test_auto_instrument_legacy_schema_parameter_ignored(self, mock_traceloop_co
146136 """Test that legacy_schema parameter is accepted but doesn't affect behavior."""
147137 mock_traceloop_components ['get_app_name' ].return_value = 'test-app'
148138 mock_traceloop_components ['create_resource' ].return_value = {}
149-
139+
150140 with patch .dict ('os.environ' , {'OTEL_EXPORTER_OTLP_ENDPOINT' : 'http://localhost:4317' }, clear = True ):
151141 # Should not raise an error
152142 auto_instrument ()
153143 auto_instrument ()
154144 auto_instrument ()
155-
145+
156146 # Verify Traceloop was initialized each time
157147 assert mock_traceloop_components ['traceloop' ].init .call_count == 3
148+
149+ def test_auto_instrument_with_console_exporter (self , mock_traceloop_components ):
150+ """Test that auto_instrument uses ConsoleSpanExporter when OTEL_TRACES_EXPORTER=console."""
151+ mock_traceloop_components ['get_app_name' ].return_value = 'test-app'
152+ mock_traceloop_components ['create_resource' ].return_value = {}
153+
154+ with patch .dict ('os.environ' , {'OTEL_TRACES_EXPORTER' : 'console' }, clear = True ):
155+ auto_instrument ()
156+
157+ mock_traceloop_components ['console_exporter' ].assert_called_once_with ()
158+ mock_traceloop_components ['exporter' ].assert_not_called ()
159+ mock_traceloop_components ['traceloop' ].init .assert_called_once ()
160+
161+ def test_auto_instrument_console_exporter_case_insensitive (self , mock_traceloop_components ):
162+ """Test that OTEL_TRACES_EXPORTER=console matching is case insensitive."""
163+ mock_traceloop_components ['get_app_name' ].return_value = 'test-app'
164+ mock_traceloop_components ['create_resource' ].return_value = {}
165+
166+ for value in ['CONSOLE' , 'Console' , 'CONSOLE' ]:
167+ mock_traceloop_components ['console_exporter' ].reset_mock ()
168+ mock_traceloop_components ['traceloop' ].reset_mock ()
169+ with patch .dict ('os.environ' , {'OTEL_TRACES_EXPORTER' : value }, clear = True ):
170+ auto_instrument ()
171+ mock_traceloop_components ['console_exporter' ].assert_called_once_with ()
172+
173+ def test_auto_instrument_console_wins_when_both_set (self , mock_traceloop_components ):
174+ """Test that console exporter is used when OTEL_TRACES_EXPORTER=console, even if OTLP endpoint is also set."""
175+ mock_traceloop_components ['get_app_name' ].return_value = 'test-app'
176+ mock_traceloop_components ['create_resource' ].return_value = {}
177+
178+ with patch .dict ('os.environ' , {
179+ 'OTEL_EXPORTER_OTLP_ENDPOINT' : 'http://localhost:4317' ,
180+ 'OTEL_TRACES_EXPORTER' : 'console' ,
181+ }, clear = True ):
182+ auto_instrument ()
183+
184+ mock_traceloop_components ['console_exporter' ].assert_called_once_with ()
185+ mock_traceloop_components ['exporter' ].assert_not_called ()
186+
187+ def test_auto_instrument_console_wraps_with_transformer (self , mock_traceloop_components ):
188+ """Test that ConsoleSpanExporter is wrapped with GenAIAttributeTransformer."""
189+ mock_traceloop_components ['get_app_name' ].return_value = 'test-app'
190+ mock_traceloop_components ['create_resource' ].return_value = {}
191+ mock_console_instance = MagicMock ()
192+ mock_traceloop_components ['console_exporter' ].return_value = mock_console_instance
193+
194+ with patch .dict ('os.environ' , {'OTEL_TRACES_EXPORTER' : 'console' }, clear = True ):
195+ auto_instrument ()
196+
197+ mock_traceloop_components ['transformer' ].assert_called_once_with (mock_console_instance )
198+
199+ def test_auto_instrument_without_endpoint_or_console (self ):
200+ """Test that auto_instrument warns when neither OTLP endpoint nor console exporter is configured."""
201+ with patch .dict ('os.environ' , {}, clear = True ):
202+ with patch ('sap_cloud_sdk.core.telemetry.auto_instrument.logger' ) as mock_logger :
203+ auto_instrument ()
204+
205+ mock_logger .warning .assert_called_once ()
206+ warning_message = mock_logger .warning .call_args [0 ][0 ]
207+ assert "OTEL_EXPORTER_OTLP_ENDPOINT not set" in warning_message
208+
0 commit comments