diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d98b5e97..48929707db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3341](https://github.com/open-telemetry/opentelemetry-python/pull/3341)) - Upgrade opentelemetry-proto to 0.20 and regen [#3355](https://github.com/open-telemetry/opentelemetry-python/pull/3355)) +- Include endpoint in Grpc transient error warning + [#3362](https://github.com/open-telemetry/opentelemetry-python/pull/3362)) ## Version 1.18.0/0.39b0 (2023-05-04) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index daef82f90a..61fe760529 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,28 @@ behind this is that every PR that adds/removes public symbols fails in CI, forci If after checking them, it is considered that they are indeed necessary, the PR will be labeled with `Skip Public API check` so that this check is not run. +Also, we try to keep our console output as clean as possible. Most of the time this means catching expected log messages in the test cases: + +``` python +from logging import WARNING + +... + + def test_case(self): + with self.assertLogs(level=WARNING): + some_function_that_will_log_a_warning_message() +``` + +Other options can be to disable logging propagation or disabling a logger altogether. + +A similar approach can be followed to catch warnings: + +``` python + def test_case(self): + with self.assertWarns(DeprecationWarning): + some_function_that_will_raise_a_deprecation_warning() +``` + See [`tox.ini`](https://github.com/open-telemetry/opentelemetry-python/blob/main/tox.ini) for more detail on available tox commands. diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py index fa041539c7..243c88b08f 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py @@ -178,11 +178,11 @@ def __init__( ): super().__init__() - endpoint = endpoint or environ.get( + self._endpoint = endpoint or environ.get( OTEL_EXPORTER_OTLP_ENDPOINT, "http://localhost:4317" ) - parsed_url = urlparse(endpoint) + parsed_url = urlparse(self._endpoint) if parsed_url.scheme == "https": insecure = False @@ -197,7 +197,7 @@ def __init__( insecure = False if parsed_url.netloc: - endpoint = parsed_url.netloc + self._endpoint = parsed_url.netloc self._headers = headers or environ.get(OTEL_EXPORTER_OTLP_HEADERS) if isinstance(self._headers, str): @@ -223,14 +223,16 @@ def __init__( if insecure: self._client = self._stub( - insecure_channel(endpoint, compression=compression) + insecure_channel(self._endpoint, compression=compression) ) else: credentials = _get_credentials( credentials, OTEL_EXPORTER_OTLP_CERTIFICATE ) self._client = self._stub( - secure_channel(endpoint, credentials, compression=compression) + secure_channel( + self._endpoint, credentials, compression=compression + ) ) self._export_lock = threading.Lock() @@ -304,18 +306,20 @@ def _export( logger.warning( ( "Transient error %s encountered while exporting " - "%s, retrying in %ss." + "%s to %s, retrying in %ss." ), error.code(), self._exporting, + self._endpoint, delay, ) sleep(delay) continue else: logger.error( - "Failed to export %s, error code: %s", + "Failed to export %s to %s, error code: %s", self._exporting, + self._endpoint, error.code(), ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py index c757755740..f1b95986da 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py @@ -93,7 +93,7 @@ def _exporting(self) -> str: otlp_mock_exporter._export(Mock()) self.assertEqual( warning.records[0].message, - "Failed to export mock, error code: None", + "Failed to export mock to localhost:4317, error code: None", ) def code(self): # pylint: disable=function-redefined @@ -112,7 +112,7 @@ def trailing_metadata(self): warning.records[0].message, ( "Transient error StatusCode.CANCELLED encountered " - "while exporting mock, retrying in 0s." + "while exporting mock to localhost:4317, retrying in 0s." ), ) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 3e85b64fe4..d170089812 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -172,3 +172,13 @@ def detach(token: object) -> None: _SUPPRESS_HTTP_INSTRUMENTATION_KEY = create_key( "suppress_http_instrumentation" ) + +__all__ = [ + "Context", + "attach", + "create_key", + "detach", + "get_current", + "get_value", + "set_value", +] diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 304df22754..bf9e0b89a4 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -588,6 +588,11 @@ def use_span( description=f"{type(exc).__name__}: {exc}", ) ) + + # This causes parent spans to set their status to ERROR and to record + # an exception as an event if a child span raises an exception even if + # such child span was started with both record_exception and + # set_status_on_exception attributes set to False. raise finally: diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 774df5771a..0a33e941ff 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -29,6 +29,7 @@ LoggingHandler, LogRecord, ) +from opentelemetry.sdk._logs._internal.export import _logger from opentelemetry.sdk._logs.export import ( BatchLogRecordProcessor, ConsoleLogExporter, @@ -167,7 +168,8 @@ def test_simple_log_record_processor_shutdown(self): ) exporter.clear() logger_provider.shutdown() - logger.warning("Log after shutdown") + with self.assertLogs(level=logging.WARNING): + logger.warning("Log after shutdown") finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 0) @@ -239,7 +241,9 @@ def test_args_defaults(self): ) def test_args_env_var_value_error(self): exporter = InMemoryLogExporter() + _logger.disabled = True log_record_processor = BatchLogRecordProcessor(exporter) + _logger.disabled = False self.assertEqual(log_record_processor._exporter, exporter) self.assertEqual(log_record_processor._max_queue_size, 2048) self.assertEqual(log_record_processor._schedule_delay_millis, 5000) @@ -315,12 +319,14 @@ def test_shutdown(self): provider.add_log_record_processor(log_record_processor) logger = logging.getLogger("shutdown") - logger.propagate = False logger.addHandler(LoggingHandler(logger_provider=provider)) - logger.warning("warning message: %s", "possible upcoming heatwave") - logger.error("Very high rise in temperatures across the globe") - logger.critical("Temperature hits high 420 C in Hyderabad") + with self.assertLogs(level=logging.WARNING): + logger.warning("warning message: %s", "possible upcoming heatwave") + with self.assertLogs(level=logging.WARNING): + logger.error("Very high rise in temperatures across the globe") + with self.assertLogs(level=logging.WARNING): + logger.critical("Temperature hits high 420 C in Hyderabad") log_record_processor.shutdown() self.assertTrue(exporter._stopped) diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 04cf5640f5..bb4fc3a829 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -42,7 +42,8 @@ def test_handler_default_log_level(self): logger.debug("Debug message") self.assertEqual(emitter_mock.emit.call_count, 0) # Assert emit gets called for warning message - logger.warning("Warning message") + with self.assertLogs(level=logging.WARNING): + logger.warning("Warning message") self.assertEqual(emitter_mock.emit.call_count, 1) def test_handler_custom_log_level(self): @@ -53,11 +54,14 @@ def test_handler_custom_log_level(self): logger = get_logger( level=logging.ERROR, logger_provider=emitter_provider_mock ) - logger.warning("Warning message test custom log level") + with self.assertLogs(level=logging.WARNING): + logger.warning("Warning message test custom log level") # Make sure any log with level < ERROR is ignored self.assertEqual(emitter_mock.emit.call_count, 0) - logger.error("Mumbai, we have a major problem") - logger.critical("No Time For Caution") + with self.assertLogs(level=logging.ERROR): + logger.error("Mumbai, we have a major problem") + with self.assertLogs(level=logging.CRITICAL): + logger.critical("No Time For Caution") self.assertEqual(emitter_mock.emit.call_count, 2) def test_log_record_no_span_context(self): @@ -67,7 +71,8 @@ def test_log_record_no_span_context(self): ) logger = get_logger(logger_provider=emitter_provider_mock) # Assert emit gets called for warning message - logger.warning("Warning message") + with self.assertLogs(level=logging.WARNING): + logger.warning("Warning message") args, _ = emitter_mock.emit.call_args_list[0] log_record = args[0] @@ -86,7 +91,8 @@ def test_log_record_user_attributes(self): ) logger = get_logger(logger_provider=emitter_provider_mock) # Assert emit gets called for warning message - logger.warning("Warning message", extra={"http.status_code": 200}) + with self.assertLogs(level=logging.WARNING): + logger.warning("Warning message", extra={"http.status_code": 200}) args, _ = emitter_mock.emit.call_args_list[0] log_record = args[0] @@ -104,7 +110,8 @@ def test_log_record_exception(self): try: raise ZeroDivisionError("division by zero") except ZeroDivisionError: - logger.exception("Zero Division Error") + with self.assertLogs(level=logging.ERROR): + logger.exception("Zero Division Error") args, _ = emitter_mock.emit.call_args_list[0] log_record = args[0] @@ -137,7 +144,8 @@ def test_log_exc_info_false(self): try: raise ZeroDivisionError("division by zero") except ZeroDivisionError: - logger.error("Zero Division Error", exc_info=False) + with self.assertLogs(level=logging.ERROR): + logger.error("Zero Division Error", exc_info=False) args, _ = emitter_mock.emit.call_args_list[0] log_record = args[0] @@ -160,7 +168,8 @@ def test_log_record_trace_correlation(self): tracer = trace.TracerProvider().get_tracer(__name__) with tracer.start_as_current_span("test") as span: - logger.critical("Critical message within span") + with self.assertLogs(level=logging.CRITICAL): + logger.critical("Critical message within span") args, _ = emitter_mock.emit.call_args_list[0] log_record = args[0] diff --git a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py index 0d5ac8b115..7f4bbc32c1 100644 --- a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py +++ b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py @@ -68,15 +68,18 @@ def test_log_record_processor(self): logger.addHandler(handler) # Test no proessor added - logger.critical("Odisha, we have another major cyclone") + with self.assertLogs(level=logging.CRITICAL): + logger.critical("Odisha, we have another major cyclone") self.assertEqual(len(logs_list_1), 0) self.assertEqual(len(logs_list_2), 0) # Add one processor provider.add_log_record_processor(processor1) - logger.warning("Brace yourself") - logger.error("Some error message") + with self.assertLogs(level=logging.WARNING): + logger.warning("Brace yourself") + with self.assertLogs(level=logging.ERROR): + logger.error("Some error message") expected_list_1 = [ ("Brace yourself", "WARNING"), @@ -86,7 +89,8 @@ def test_log_record_processor(self): # Add another processor provider.add_log_record_processor(processor2) - logger.critical("Something disastrous") + with self.assertLogs(level=logging.CRITICAL): + logger.critical("Something disastrous") expected_list_1.append(("Something disastrous", "CRITICAL")) expected_list_2 = [("Something disastrous", "CRITICAL")] diff --git a/opentelemetry-sdk/tests/metrics/test_instrument.py b/opentelemetry-sdk/tests/metrics/test_instrument.py index 8add3e69a0..5eb1a90885 100644 --- a/opentelemetry-sdk/tests/metrics/test_instrument.py +++ b/opentelemetry-sdk/tests/metrics/test_instrument.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from logging import WARNING from unittest import TestCase from unittest.mock import Mock @@ -50,7 +51,8 @@ def test_add(self): def test_add_non_monotonic(self): mc = Mock() counter = _Counter("name", Mock(), mc) - counter.add(-1.0) + with self.assertLogs(level=WARNING): + counter.add(-1.0) mc.consume_measurement.assert_not_called() def test_disallow_direct_counter_creation(self): @@ -360,7 +362,8 @@ def test_record(self): def test_record_non_monotonic(self): mc = Mock() hist = _Histogram("name", Mock(), mc) - hist.record(-1.0) + with self.assertLogs(level=WARNING): + hist.record(-1.0) mc.consume_measurement.assert_not_called() def test_disallow_direct_histogram_creation(self): diff --git a/opentelemetry-sdk/tests/metrics/test_metric_reader_storage.py b/opentelemetry-sdk/tests/metrics/test_metric_reader_storage.py index da45ffe4ae..97b5532fea 100644 --- a/opentelemetry-sdk/tests/metrics/test_metric_reader_storage.py +++ b/opentelemetry-sdk/tests/metrics/test_metric_reader_storage.py @@ -99,7 +99,8 @@ def test_creates_view_instrument_matches( # instrument2 matches view2, so should create a single # ViewInstrumentMatch MockViewInstrumentMatch.call_args_list.clear() - storage.consume_measurement(Measurement(1, instrument2)) + with self.assertLogs(level=WARNING): + storage.consume_measurement(Measurement(1, instrument2)) self.assertEqual(len(MockViewInstrumentMatch.call_args_list), 1) @patch( @@ -150,7 +151,8 @@ def test_forwards_calls_to_view_instrument_match( view_instrument_match3.consume_measurement.assert_not_called() measurement = Measurement(1, instrument2) - storage.consume_measurement(measurement) + with self.assertLogs(level=WARNING): + storage.consume_measurement(measurement) view_instrument_match3.consume_measurement.assert_called_once_with( measurement ) diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index 13efbb91c0..37a6a77aff 100644 --- a/opentelemetry-sdk/tests/metrics/test_metrics.py +++ b/opentelemetry-sdk/tests/metrics/test_metrics.py @@ -122,19 +122,21 @@ def test_get_meter_empty(self): should return a NoOpMeter. """ - meter = MeterProvider().get_meter( - None, - version="version", - schema_url="schema_url", - ) + with self.assertLogs(level=WARNING): + meter = MeterProvider().get_meter( + None, + version="version", + schema_url="schema_url", + ) self.assertIsInstance(meter, NoOpMeter) self.assertEqual(meter._name, None) - meter = MeterProvider().get_meter( - "", - version="version", - schema_url="schema_url", - ) + with self.assertLogs(level=WARNING): + meter = MeterProvider().get_meter( + "", + version="version", + schema_url="schema_url", + ) self.assertIsInstance(meter, NoOpMeter) self.assertEqual(meter._name, "") @@ -483,9 +485,10 @@ def test_duplicate_instrument_aggregate_data(self): counter_0_0 = meter_0.create_counter( "counter", unit="unit", description="description" ) - counter_0_1 = meter_0.create_counter( - "counter", unit="unit", description="description" - ) + with self.assertLogs(level=WARNING): + counter_0_1 = meter_0.create_counter( + "counter", unit="unit", description="description" + ) counter_1_0 = meter_1.create_counter( "counter", unit="unit", description="description" ) @@ -496,7 +499,8 @@ def test_duplicate_instrument_aggregate_data(self): counter_0_0.add(1, {}) counter_0_1.add(2, {}) - counter_1_0.add(7, {}) + with self.assertLogs(level=WARNING): + counter_1_0.add(7, {}) sleep(1) diff --git a/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py b/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py index aa0eed285d..98f59526ef 100644 --- a/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py +++ b/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py @@ -13,6 +13,7 @@ # limitations under the License. import math +from logging import WARNING from time import sleep, time_ns from typing import Optional, Sequence from unittest.mock import Mock @@ -127,7 +128,8 @@ def test_defaults(self): pmr = PeriodicExportingMetricReader(FakeMetricsExporter()) self.assertEqual(pmr._export_interval_millis, 60000) self.assertEqual(pmr._export_timeout_millis, 30000) - pmr.shutdown() + with self.assertLogs(level=WARNING): + pmr.shutdown() def _create_periodic_reader( self, metrics, exporter, collect_wait=0, interval=60000, timeout=30000 @@ -212,8 +214,9 @@ def test_shutdown_multiple_times(self): pmr = self._create_periodic_reader([], FakeMetricsExporter()) with self.assertLogs(level="WARNING") as w: self.run_with_many_threads(pmr.shutdown) - self.assertTrue("Can't shutdown multiple times", w.output[0]) - pmr.shutdown() + self.assertTrue("Can't shutdown multiple times", w.output[0]) + with self.assertLogs(level="WARNING") as w: + pmr.shutdown() def test_exporter_temporality_preference(self): exporter = FakeMetricsExporter( diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index bf372edc0b..1fc81c7cae 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -16,7 +16,7 @@ import unittest import uuid -from logging import ERROR +from logging import ERROR, WARNING from os import environ from unittest.mock import Mock, patch from urllib import parse @@ -221,16 +221,19 @@ def test_service_name_using_process_name(self): ) def test_invalid_resource_attribute_values(self): - resource = Resource( - { - SERVICE_NAME: "test", - "non-primitive-data-type": {}, - "invalid-byte-type-attribute": b"\xd8\xe1\xb7\xeb\xa8\xe5 \xd2\xb7\xe1", - "": "empty-key-value", - None: "null-key-value", - "another-non-primitive": uuid.uuid4(), - } - ) + with self.assertLogs(level=WARNING): + resource = Resource( + { + SERVICE_NAME: "test", + "non-primitive-data-type": {}, + "invalid-byte-type-attribute": ( + b"\xd8\xe1\xb7\xeb\xa8\xe5 \xd2\xb7\xe1" + ), + "": "empty-key-value", + None: "null-key-value", + "another-non-primitive": uuid.uuid4(), + } + ) self.assertEqual( resource.attributes, { @@ -390,12 +393,13 @@ def test_resource_detector_ignore_error(self): resource_detector = Mock(spec=ResourceDetector) resource_detector.detect.side_effect = Exception() resource_detector.raise_on_error = False - self.assertEqual( - get_aggregated_resources([resource_detector]), - _DEFAULT_RESOURCE.merge( - Resource({SERVICE_NAME: "unknown_service"}, "") - ), - ) + with self.assertLogs(level=WARNING): + self.assertEqual( + get_aggregated_resources([resource_detector]), + _DEFAULT_RESOURCE.merge( + Resource({SERVICE_NAME: "unknown_service"}, "") + ), + ) def test_resource_detector_raise_error(self): resource_detector = Mock(spec=ResourceDetector) @@ -470,10 +474,11 @@ def test_multiple_with_whitespace(self): def test_invalid_key_value_pairs(self): detector = OTELResourceDetector() environ[OTEL_RESOURCE_ATTRIBUTES] = "k=v,k2=v2,invalid,,foo=bar=baz," - self.assertEqual( - detector.detect(), - Resource({"k": "v", "k2": "v2", "foo": "bar=baz"}), - ) + with self.assertLogs(level=WARNING): + self.assertEqual( + detector.detect(), + Resource({"k": "v", "k2": "v2", "foo": "bar=baz"}), + ) def test_multiple_with_url_decode(self): detector = OTELResourceDetector() diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index aa1c58cb51..e64e64ade0 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -14,7 +14,7 @@ # type: ignore # pylint: skip-file -from logging import getLogger +from logging import WARNING, getLogger from os import environ from typing import Dict, Iterable, Optional, Sequence from unittest import TestCase @@ -377,7 +377,8 @@ def test_trace_init_custom_id_generator(self, mock_entry_points): ) def test_trace_init_custom_sampler_with_env_non_existent_entry_point(self): sampler_name = _get_sampler() - sampler = _import_sampler(sampler_name) + with self.assertLogs(level=WARNING): + sampler = _import_sampler(sampler_name) _init_tracing({}, sampler=sampler) provider = self.set_provider_mock.call_args[0][0] self.assertIsNone(provider.sampler) @@ -415,7 +416,8 @@ def test_trace_init_custom_sampler_with_env_bad_factory( ) sampler_name = _get_sampler() - sampler = _import_sampler(sampler_name) + with self.assertLogs(level=WARNING): + sampler = _import_sampler(sampler_name) _init_tracing({}, sampler=sampler) provider = self.set_provider_mock.call_args[0][0] self.assertIsNone(provider.sampler) @@ -492,7 +494,8 @@ def test_trace_init_custom_ratio_sampler_with_env_bad_arg( ) sampler_name = _get_sampler() - sampler = _import_sampler(sampler_name) + with self.assertLogs(level=WARNING): + sampler = _import_sampler(sampler_name) _init_tracing({}, sampler=sampler) provider = self.set_provider_mock.call_args[0][0] self.assertIsNone(provider.sampler) @@ -517,7 +520,8 @@ def test_trace_init_custom_ratio_sampler_with_env_missing_arg( ) sampler_name = _get_sampler() - sampler = _import_sampler(sampler_name) + with self.assertLogs(level=WARNING): + sampler = _import_sampler(sampler_name) _init_tracing({}, sampler=sampler) provider = self.set_provider_mock.call_args[0][0] self.assertIsNone(provider.sampler) @@ -642,7 +646,8 @@ def test_logging_init_disable_default(self, logging_mock, tracing_mock): @patch("opentelemetry.sdk._configuration._init_tracing") @patch("opentelemetry.sdk._configuration._init_logging") def test_logging_init_enable_env(self, logging_mock, tracing_mock): - _initialize_components("auto-version") + with self.assertLogs(level=WARNING): + _initialize_components("auto-version") self.assertEqual(logging_mock.call_count, 1) self.assertEqual(tracing_mock.call_count, 1) diff --git a/opentelemetry-sdk/tests/trace/export/test_export.py b/opentelemetry-sdk/tests/trace/export/test_export.py index cb7739f328..7784ef4c9d 100644 --- a/opentelemetry-sdk/tests/trace/export/test_export.py +++ b/opentelemetry-sdk/tests/trace/export/test_export.py @@ -34,6 +34,7 @@ OTEL_BSP_SCHEDULE_DELAY, ) from opentelemetry.sdk.trace import export +from opentelemetry.sdk.trace.export import logger from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( InMemorySpanExporter, ) @@ -208,9 +209,11 @@ def test_args_env_var_defaults(self): ) def test_args_env_var_value_error(self): + logger.disabled = True batch_span_processor = export.BatchSpanProcessor( MySpanExporter(destination=[]) ) + logger.disabled = False self.assertEqual(batch_span_processor.max_queue_size, 2048) self.assertEqual(batch_span_processor.schedule_delay_millis, 5000) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index c0b192192f..a49b86b851 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -55,7 +55,12 @@ get_span_with_dropped_attributes_events_links, new_tracer, ) -from opentelemetry.trace import Status, StatusCode +from opentelemetry.trace import ( + Status, + StatusCode, + get_tracer, + set_tracer_provider, +) class TestTracer(unittest.TestCase): @@ -265,21 +270,27 @@ def test_instrumentation_info(self): tracer2 = tracer_provider.get_tracer("instr2", "1.3b3", schema_url) span1 = tracer1.start_span("s1") span2 = tracer2.start_span("s2") - self.assertEqual( - span1.instrumentation_info, InstrumentationInfo("instr1", "") - ) - self.assertEqual( - span2.instrumentation_info, - InstrumentationInfo("instr2", "1.3b3", schema_url), - ) + with self.assertWarns(DeprecationWarning): + self.assertEqual( + span1.instrumentation_info, InstrumentationInfo("instr1", "") + ) + with self.assertWarns(DeprecationWarning): + self.assertEqual( + span2.instrumentation_info, + InstrumentationInfo("instr2", "1.3b3", schema_url), + ) - self.assertEqual(span2.instrumentation_info.schema_url, schema_url) - self.assertEqual(span2.instrumentation_info.version, "1.3b3") - self.assertEqual(span2.instrumentation_info.name, "instr2") + with self.assertWarns(DeprecationWarning): + self.assertEqual(span2.instrumentation_info.schema_url, schema_url) + with self.assertWarns(DeprecationWarning): + self.assertEqual(span2.instrumentation_info.version, "1.3b3") + with self.assertWarns(DeprecationWarning): + self.assertEqual(span2.instrumentation_info.name, "instr2") - self.assertLess( - span1.instrumentation_info, span2.instrumentation_info - ) # Check sortability. + with self.assertWarns(DeprecationWarning): + self.assertLess( + span1.instrumentation_info, span2.instrumentation_info + ) # Check sortability. def test_invalid_instrumentation_info(self): tracer_provider = trace.TracerProvider() @@ -690,24 +701,34 @@ def test_attributes(self): def test_invalid_attribute_values(self): with self.tracer.start_as_current_span("root") as root: - root.set_attributes( - {"correct-value": "foo", "non-primitive-data-type": {}} - ) + with self.assertLogs(level=WARNING): + root.set_attributes( + {"correct-value": "foo", "non-primitive-data-type": {}} + ) - root.set_attribute("non-primitive-data-type", {}) - root.set_attribute( - "list-of-mixed-data-types-numeric-first", - [123, False, "string"], - ) - root.set_attribute( - "list-of-mixed-data-types-non-numeric-first", - [False, 123, "string"], - ) - root.set_attribute("list-with-non-primitive-data-type", [{}, 123]) - root.set_attribute("list-with-numeric-and-bool", [1, True]) + with self.assertLogs(level=WARNING): + root.set_attribute("non-primitive-data-type", {}) + with self.assertLogs(level=WARNING): + root.set_attribute( + "list-of-mixed-data-types-numeric-first", + [123, False, "string"], + ) + with self.assertLogs(level=WARNING): + root.set_attribute( + "list-of-mixed-data-types-non-numeric-first", + [False, 123, "string"], + ) + with self.assertLogs(level=WARNING): + root.set_attribute( + "list-with-non-primitive-data-type", [{}, 123] + ) + with self.assertLogs(level=WARNING): + root.set_attribute("list-with-numeric-and-bool", [1, True]) - root.set_attribute("", 123) - root.set_attribute(None, 123) + with self.assertLogs(level=WARNING): + root.set_attribute("", 123) + with self.assertLogs(level=WARNING): + root.set_attribute(None, 123) self.assertEqual(len(root.attributes), 1) self.assertEqual(root.attributes["correct-value"], "foo") @@ -823,10 +844,16 @@ def test_invalid_event_attributes(self): self.assertEqual(trace_api.get_current_span(), trace_api.INVALID_SPAN) with self.tracer.start_as_current_span("root") as root: - root.add_event("event0", {"attr1": True, "attr2": ["hi", False]}) - root.add_event("event0", {"attr1": {}}) - root.add_event("event0", {"attr1": [[True]]}) - root.add_event("event0", {"attr1": [{}], "attr2": [1, 2]}) + with self.assertLogs(level=WARNING): + root.add_event( + "event0", {"attr1": True, "attr2": ["hi", False]} + ) + with self.assertLogs(level=WARNING): + root.add_event("event0", {"attr1": {}}) + with self.assertLogs(level=WARNING): + root.add_event("event0", {"attr1": [[True]]}) + with self.assertLogs(level=WARNING): + root.add_event("event0", {"attr1": [{}], "attr2": [1, 2]}) self.assertEqual(len(root.events), 4) self.assertEqual(root.events[0].attributes, {"attr1": True}) @@ -1047,14 +1074,16 @@ def unset_status_test(context): root.status.description, "AssertionError: unknown" ) - unset_status_test( - trace.TracerProvider().get_tracer(__name__).start_span("root") - ) - unset_status_test( - trace.TracerProvider() - .get_tracer(__name__) - .start_as_current_span("root") - ) + with self.assertLogs(level=WARNING): + unset_status_test( + trace.TracerProvider().get_tracer(__name__).start_span("root") + ) + with self.assertLogs(level=WARNING): + unset_status_test( + trace.TracerProvider() + .get_tracer(__name__) + .start_as_current_span("root") + ) def test_last_status_wins(self): def error_status_test(context): @@ -1828,3 +1857,88 @@ def test_constant_default_trace_options(self): self.assertEqual( trace_api.DEFAULT_TRACE_OPTIONS, trace_api.TraceFlags.DEFAULT ) + + +class TestParentChildSpanException(unittest.TestCase): + def test_parent_child_span_exception(self): + """ + Tests that a parent span has its status set to ERROR when a child span + raises an exception even when the child span has its + ``record_exception`` and ``set_status_on_exception`` attributes + set to ``False``. + """ + + set_tracer_provider(TracerProvider()) + tracer = get_tracer(__name__) + + exception = Exception("exception") + + exception_type = exception.__class__.__name__ + exception_message = exception.args[0] + + try: + with tracer.start_as_current_span( + "parent", + ) as parent_span: + with tracer.start_as_current_span( + "child", + record_exception=False, + set_status_on_exception=False, + ) as child_span: + raise exception + + except Exception: # pylint: disable=broad-except + pass + + self.assertTrue(child_span.status.is_ok) + self.assertIsNone(child_span.status.description) + self.assertTupleEqual(child_span.events, ()) + + self.assertFalse(parent_span.status.is_ok) + self.assertEqual( + parent_span.status.description, + f"{exception_type}: {exception_message}", + ) + self.assertEqual( + parent_span.events[0].attributes["exception.type"], exception_type + ) + self.assertEqual( + parent_span.events[0].attributes["exception.message"], + exception_message, + ) + + def test_child_parent_span_exception(self): + """ + Tests that a child span does not have its status set to ERROR when a + parent span raises an exception and the parent span has its + ``record_exception`` and ``set_status_on_exception`` attributes + set to ``False``. + """ + + set_tracer_provider(TracerProvider()) + tracer = get_tracer(__name__) + + exception = Exception("exception") + + try: + with tracer.start_as_current_span( + "parent", + record_exception=False, + set_status_on_exception=False, + ) as parent_span: + with tracer.start_as_current_span( + "child", + ) as child_span: + pass + raise exception + + except Exception: # pylint: disable=broad-except + pass + + self.assertTrue(child_span.status.is_ok) + self.assertIsNone(child_span.status.description) + self.assertTupleEqual(child_span.events, ()) + + self.assertTrue(parent_span.status.is_ok) + self.assertIsNone(parent_span.status.description) + self.assertTupleEqual(parent_span.events, ()) diff --git a/pyproject.toml b/pyproject.toml index a20a405675..27d530a7f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,4 +17,3 @@ exclude = ''' [tool.pytest.ini_options] addopts = "-rs -v" log_cli = true -log_cli_level = "warning"