diff --git a/README.md b/README.md index 613b4e6acbd..63cba3eb3b9 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,6 @@ Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telem - [Chris Kleinknecht](https://github.com/c24t), Google - [Diego Hurtado](https://github.com/ocelotl) - [Hector Hernandez](https://github.com/hectorhdzg), Microsoft -- [Leighton Chen](https://github.com/lzchen), Microsoft - [Mauricio Vásquez](https://github.com/mauriciovasquezbernal), Kinvolk - [Reiley Yang](https://github.com/reyang), Microsoft @@ -116,16 +115,17 @@ Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telem Maintainers ([@open-telemetry/python-maintainers](https://github.com/orgs/open-telemetry/teams/python-maintainers)): - [Alex Boten](https://github.com/codeboten), LightStep +- [Leighton Chen](https://github.com/lzchen), Microsoft - [Yusuke Tsutsumi](https://github.com/toumorokoshi), Zillow Group +*Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer).* + ### Thanks to all the people who already contributed! -*Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer).* - ## Release Schedule OpenTelemetry Python is under active development. diff --git a/dev-requirements.txt b/dev-requirements.txt index be74d804b3d..253c0895072 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,7 +2,7 @@ pylint==2.4.4 flake8==3.7.9 isort~=4.3 black>=19.3b0,==19.* -mypy==0.740 +mypy==0.770 sphinx~=2.1 sphinx-rtd-theme~=0.4 sphinx-autodoc-typehints~=1.10.2 diff --git a/docs/examples/basic_meter/observer.py b/docs/examples/basic_meter/observer.py index aa70abe2a44..b61b9e4db80 100644 --- a/docs/examples/basic_meter/observer.py +++ b/docs/examples/basic_meter/observer.py @@ -19,7 +19,7 @@ import psutil from opentelemetry import metrics -from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics import MeterProvider, ValueObserver from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher from opentelemetry.sdk.metrics.export.controller import PushController @@ -43,6 +43,7 @@ def get_cpu_usage_callback(observer): description="per-cpu usage", unit="1", value_type=float, + observer_type=ValueObserver, label_keys=("cpu_number",), ) diff --git a/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py b/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py index faa0788c7f1..bb1a1ee888c 100644 --- a/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py +++ b/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py @@ -114,10 +114,10 @@ def translate_to_collector( ) metric_descriptor = metrics_pb2.MetricDescriptor( - name=metric_record.metric.name, - description=metric_record.metric.description, - unit=metric_record.metric.unit, - type=get_collector_metric_type(metric_record.metric), + name=metric_record.instrument.name, + description=metric_record.instrument.description, + unit=metric_record.instrument.unit, + type=get_collector_metric_type(metric_record.instrument), label_keys=label_keys, ) @@ -151,14 +151,14 @@ def get_collector_point(metric_record: MetricRecord) -> metrics_pb2.Point: metric_record.aggregator.last_update_timestamp ) ) - if metric_record.metric.value_type == int: + if metric_record.instrument.value_type == int: point.int64_value = metric_record.aggregator.checkpoint - elif metric_record.metric.value_type == float: + elif metric_record.instrument.value_type == float: point.double_value = metric_record.aggregator.checkpoint else: raise TypeError( "Unsupported metric type: {}".format( - metric_record.metric.value_type + metric_record.instrument.value_type ) ) return point diff --git a/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py b/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py index 18b4a328067..f9070126476 100644 --- a/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py +++ b/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py @@ -92,7 +92,7 @@ def test_get_collector_point(self): "testName", "testDescription", "unit", float, ValueRecorder ) result = metrics_exporter.get_collector_point( - MetricRecord(aggregator, self._key_labels, int_counter) + MetricRecord(int_counter, self._key_labels, aggregator) ) self.assertIsInstance(result, metrics_pb2.Point) self.assertIsInstance(result.timestamp, Timestamp) @@ -100,13 +100,13 @@ def test_get_collector_point(self): aggregator.update(123.5) aggregator.take_checkpoint() result = metrics_exporter.get_collector_point( - MetricRecord(aggregator, self._key_labels, float_counter) + MetricRecord(float_counter, self._key_labels, aggregator) ) self.assertEqual(result.double_value, 123.5) self.assertRaises( TypeError, metrics_exporter.get_collector_point( - MetricRecord(aggregator, self._key_labels, valuerecorder) + MetricRecord(valuerecorder, self._key_labels, aggregator) ), ) @@ -122,7 +122,7 @@ def test_export(self): "testname", "testdesc", "unit", int, Counter, ["environment"] ) record = MetricRecord( - aggregate.CounterAggregator(), self._key_labels, test_metric + test_metric, self._key_labels, aggregate.CounterAggregator(), ) result = collector_exporter.export([record]) @@ -147,7 +147,7 @@ def test_translate_to_collector(self): aggregator = aggregate.CounterAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._key_labels, test_metric) + record = MetricRecord(test_metric, self._key_labels, aggregator,) output_metrics = metrics_exporter.translate_to_collector([record]) self.assertEqual(len(output_metrics), 1) self.assertIsInstance(output_metrics[0], metrics_pb2.Metric) diff --git a/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py b/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py index cc44621ac48..59ef3f1708a 100644 --- a/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py +++ b/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py @@ -152,22 +152,22 @@ def _translate_to_prometheus(self, metric_record: MetricRecord): metric_name = "" if self._prefix != "": metric_name = self._prefix + "_" - metric_name += self._sanitize(metric_record.metric.name) + metric_name += self._sanitize(metric_record.instrument.name) - if isinstance(metric_record.metric, Counter): + if isinstance(metric_record.instrument, Counter): prometheus_metric = CounterMetricFamily( name=metric_name, - documentation=metric_record.metric.description, + documentation=metric_record.instrument.description, labels=label_keys, ) prometheus_metric.add_metric( labels=label_values, value=metric_record.aggregator.checkpoint ) # TODO: Add support for histograms when supported in OT - elif isinstance(metric_record.metric, ValueRecorder): + elif isinstance(metric_record.instrument, ValueRecorder): prometheus_metric = UnknownMetricFamily( name=metric_name, - documentation=metric_record.metric.description, + documentation=metric_record.instrument.description, labels=label_keys, ) prometheus_metric.add_metric( @@ -176,7 +176,7 @@ def _translate_to_prometheus(self, metric_record: MetricRecord): else: logger.warning( - "Unsupported metric type. %s", type(metric_record.metric) + "Unsupported metric type. %s", type(metric_record.instrument) ) return prometheus_metric diff --git a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py index f986e0c4f5f..1862f789c0c 100644 --- a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py +++ b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py @@ -67,7 +67,7 @@ def test_shutdown(self): def test_export(self): with self._registry_register_patch: record = MetricRecord( - CounterAggregator(), self._labels_key, self._test_metric + self._test_metric, self._labels_key, CounterAggregator(), ) exporter = PrometheusMetricsExporter() result = exporter.export([record]) @@ -90,7 +90,7 @@ def test_counter_to_prometheus(self): aggregator = CounterAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, key_labels, metric) + record = MetricRecord(metric, key_labels, aggregator) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) @@ -118,7 +118,7 @@ def test_invalid_metric(self): ) labels = {"environment": "staging"} key_labels = metrics.get_labels_as_key(labels) - record = MetricRecord(None, key_labels, metric) + record = MetricRecord(metric, key_labels, None) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) collector.collect() diff --git a/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py b/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py index 1621c4a95e6..65bc21370f6 100644 --- a/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py +++ b/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py @@ -45,12 +45,14 @@ import types from urllib.parse import urlparse +from requests import Timeout, URLRequired +from requests.exceptions import InvalidSchema, InvalidURL, MissingSchema from requests.sessions import Session from opentelemetry import context, propagators, trace from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.requests.version import __version__ -from opentelemetry.trace import SpanKind, get_tracer +from opentelemetry.trace import SpanKind from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -80,31 +82,49 @@ def instrumented_request(self, method, url, *args, **kwargs): # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client try: parsed_url = urlparse(url) + span_name = parsed_url.path except ValueError as exc: # Invalid URL - path = "".format(exc) - else: - if parsed_url is None: - path = "" - path = parsed_url.path + span_name = "".format(exc) - with tracer.start_as_current_span(path, kind=SpanKind.CLIENT) as span: + exception = None + + with tracer.start_as_current_span( + span_name, kind=SpanKind.CLIENT + ) as span: span.set_attribute("component", "http") span.set_attribute("http.method", method.upper()) span.set_attribute("http.url", url) headers = kwargs.setdefault("headers", {}) propagators.inject(type(headers).__setitem__, headers) - result = wrapped(self, method, url, *args, **kwargs) # *** PROCEED - span.set_attribute("http.status_code", result.status_code) - span.set_attribute("http.status_text", result.reason) - span.set_status( - Status(_http_status_to_canonical_code(result.status_code)) - ) + try: + result = wrapped( + self, method, url, *args, **kwargs + ) # *** PROCEED + except Exception as exc: # pylint: disable=W0703 + exception = exc + result = getattr(exc, "response", None) + + if exception is not None: + span.set_status( + Status(_exception_to_canonical_code(exception)) + ) + + if result is not None: + span.set_attribute("http.status_code", result.status_code) + span.set_attribute("http.status_text", result.reason) + span.set_status( + Status(_http_status_to_canonical_code(result.status_code)) + ) + if span_callback is not None: span_callback(span, result) - return result + if exception is not None: + raise exception.with_traceback(exception.__traceback__) + + return result instrumented_request.opentelemetry_ext_requests_applied = True @@ -157,6 +177,17 @@ def _http_status_to_canonical_code(code: int, allow_redirect: bool = True): return StatusCanonicalCode.UNKNOWN +def _exception_to_canonical_code(exc: Exception) -> StatusCanonicalCode: + if isinstance( + exc, + (InvalidURL, InvalidSchema, MissingSchema, URLRequired, ValueError), + ): + return StatusCanonicalCode.INVALID_ARGUMENT + if isinstance(exc, Timeout): + return StatusCanonicalCode.DEADLINE_EXCEEDED + return StatusCanonicalCode.UNKNOWN + + class RequestsInstrumentor(BaseInstrumentor): """An instrumentor for requests See `BaseInstrumentor` diff --git a/ext/opentelemetry-ext-requests/tests/test_requests_integration.py b/ext/opentelemetry-ext-requests/tests/test_requests_integration.py index 28359d8f38a..0ad5b9d19df 100644 --- a/ext/opentelemetry-ext-requests/tests/test_requests_integration.py +++ b/ext/opentelemetry-ext-requests/tests/test_requests_integration.py @@ -12,11 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys +from unittest import mock import httpretty import requests -import urllib3 import opentelemetry.ext.requests from opentelemetry import context, propagators, trace @@ -24,6 +23,7 @@ from opentelemetry.sdk import resources from opentelemetry.test.mock_httptextformat import MockHTTPTextFormat from opentelemetry.test.test_base import TestBase +from opentelemetry.trace.status import StatusCanonicalCode class TestRequestsIntegration(TestBase): @@ -92,13 +92,8 @@ def test_not_foundbasic(self): def test_invalid_url(self): url = "http://[::1/nope" - exception_type = requests.exceptions.InvalidURL - if sys.version_info[:2] < (3, 5) and tuple( - map(int, urllib3.__version__.split(".")[:2]) - ) < (1, 25): - exception_type = ValueError - with self.assertRaises(exception_type): + with self.assertRaises(ValueError): requests.post(url) span_list = self.memory_exporter.get_finished_spans() @@ -110,6 +105,9 @@ def test_invalid_url(self): span.attributes, {"component": "http", "http.method": "POST", "http.url": url}, ) + self.assertEqual( + span.status.canonical_code, StatusCanonicalCode.INVALID_ARGUMENT + ) def test_uninstrument(self): RequestsInstrumentor().uninstrument() @@ -229,3 +227,75 @@ def test_custom_tracer_provider(self): span = span_list[0] self.assertIs(span.resource, resource) + + @mock.patch("requests.Session.send", side_effect=requests.RequestException) + def test_requests_exception_without_response(self, *_, **__): + + with self.assertRaises(requests.RequestException): + requests.get(self.URL) + + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + span = span_list[0] + self.assertEqual( + span.attributes, + {"component": "http", "http.method": "GET", "http.url": self.URL}, + ) + self.assertEqual( + span.status.canonical_code, StatusCanonicalCode.UNKNOWN + ) + + mocked_response = requests.Response() + mocked_response.status_code = 500 + mocked_response.reason = "Internal Server Error" + + @mock.patch( + "requests.Session.send", + side_effect=requests.RequestException(response=mocked_response), + ) + def test_requests_exception_with_response(self, *_, **__): + + with self.assertRaises(requests.RequestException): + requests.get(self.URL) + + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + span = span_list[0] + self.assertEqual( + span.attributes, + { + "component": "http", + "http.method": "GET", + "http.url": self.URL, + "http.status_code": 500, + "http.status_text": "Internal Server Error", + }, + ) + self.assertEqual( + span.status.canonical_code, StatusCanonicalCode.INTERNAL + ) + + @mock.patch("requests.Session.send", side_effect=Exception) + def test_requests_basic_exception(self, *_, **__): + + with self.assertRaises(Exception): + requests.get(self.URL) + + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + self.assertEqual( + span_list[0].status.canonical_code, StatusCanonicalCode.UNKNOWN + ) + + @mock.patch("requests.Session.send", side_effect=requests.Timeout) + def test_requests_timeout_exception(self, *_, **__): + + with self.assertRaises(Exception): + requests.get(self.URL) + + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + self.assertEqual( + span_list[0].status.canonical_code, + StatusCanonicalCode.DEADLINE_EXCEEDED, + ) diff --git a/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py b/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py index 88f36f4ac48..09c633f14b4 100644 --- a/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py +++ b/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py @@ -58,6 +58,7 @@ import psutil from opentelemetry import metrics +from opentelemetry.sdk.metrics import ValueObserver from opentelemetry.sdk.metrics.export import MetricsExporter from opentelemetry.sdk.metrics.export.controller import PushController @@ -106,6 +107,7 @@ def __init__( description="System memory", unit="bytes", value_type=int, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -114,6 +116,7 @@ def __init__( description="System CPU", unit="seconds", value_type=float, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -122,6 +125,7 @@ def __init__( description="System network bytes", unit="bytes", value_type=int, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -130,6 +134,7 @@ def __init__( description="Runtime memory", unit="bytes", value_type=int, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -138,6 +143,7 @@ def __init__( description="Runtime CPU", unit="seconds", value_type=float, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -146,9 +152,10 @@ def __init__( description="Runtime: gc objects", unit="objects", value_type=int, + observer_type=ValueObserver, ) - def _get_system_memory(self, observer: metrics.Observer) -> None: + def _get_system_memory(self, observer: metrics.ValueObserver) -> None: """Observer callback for memory available Args: @@ -161,7 +168,7 @@ def _get_system_memory(self, observer: metrics.Observer) -> None: getattr(system_memory, metric), self._system_memory_labels ) - def _get_system_cpu(self, observer: metrics.Observer) -> None: + def _get_system_cpu(self, observer: metrics.ValueObserver) -> None: """Observer callback for system cpu Args: @@ -174,7 +181,7 @@ def _get_system_cpu(self, observer: metrics.Observer) -> None: getattr(cpu_times, _type), self._system_cpu_labels ) - def _get_network_bytes(self, observer: metrics.Observer) -> None: + def _get_network_bytes(self, observer: metrics.ValueObserver) -> None: """Observer callback for network bytes Args: @@ -187,7 +194,7 @@ def _get_network_bytes(self, observer: metrics.Observer) -> None: getattr(net_io, _type), self._network_bytes_labels ) - def _get_runtime_memory(self, observer: metrics.Observer) -> None: + def _get_runtime_memory(self, observer: metrics.ValueObserver) -> None: """Observer callback for runtime memory Args: @@ -200,7 +207,7 @@ def _get_runtime_memory(self, observer: metrics.Observer) -> None: getattr(proc_memory, _type), self._runtime_memory_labels ) - def _get_runtime_cpu(self, observer: metrics.Observer) -> None: + def _get_runtime_cpu(self, observer: metrics.ValueObserver) -> None: """Observer callback for runtime CPU Args: @@ -213,7 +220,7 @@ def _get_runtime_cpu(self, observer: metrics.Observer) -> None: getattr(proc_cpu, _type), self._runtime_cpu_labels ) - def _get_runtime_gc_count(self, observer: metrics.Observer) -> None: + def _get_runtime_gc_count(self, observer: metrics.ValueObserver) -> None: """Observer callback for garbage collection Args: diff --git a/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py b/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py index 70ead2c5152..b2d6bab4015 100644 --- a/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py +++ b/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py @@ -54,7 +54,7 @@ def _assert_metrics(self, observer_name, system_metrics, expected): ): if ( metric.labels in expected - and metric.metric.name == observer_name + and metric.instrument.name == observer_name ): self.assertEqual( metric.aggregator.checkpoint.last, expected[metric.labels], diff --git a/opentelemetry-api/CHANGELOG.md b/opentelemetry-api/CHANGELOG.md index c6759c1df27..b1610ab2c3d 100644 --- a/opentelemetry-api/CHANGELOG.md +++ b/opentelemetry-api/CHANGELOG.md @@ -8,6 +8,8 @@ ([#761](https://github.com/open-telemetry/opentelemetry-python/pull/761)) - Adding trace.get_current_span, Removing Tracer.get_current_span ([#552](https://github.com/open-telemetry/opentelemetry-python/pull/552)) +- Rename Observer to ValueObserver + ([#764](https://github.com/open-telemetry/opentelemetry-python/pull/764)) ## 0.8b0 diff --git a/opentelemetry-api/src/opentelemetry/__init__.pyi b/opentelemetry-api/src/opentelemetry/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-api/src/opentelemetry/configuration/__init__.py b/opentelemetry-api/src/opentelemetry/configuration/__init__.py index 48093415020..0bd2bce8d64 100644 --- a/opentelemetry-api/src/opentelemetry/configuration/__init__.py +++ b/opentelemetry-api/src/opentelemetry/configuration/__init__.py @@ -12,9 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# FIXME find a better way to avoid all those "Expression has type "Any"" errors -# type: ignore - """ Simple configuration manager @@ -95,17 +92,23 @@ from os import environ from re import fullmatch +from typing import ClassVar, Dict, Optional, TypeVar, Union +ConfigValue = Union[str, bool, int, float] +_T = TypeVar("_T", ConfigValue, Optional[ConfigValue]) -class Configuration: - _instance = None - __slots__ = [] +class Configuration: + _instance = None # type: ClassVar[Optional[Configuration]] + _config_map = {} # type: ClassVar[Dict[str, ConfigValue]] def __new__(cls) -> "Configuration": - if Configuration._instance is None: + if cls._instance is not None: + instance = cls._instance + else: - for key, value in environ.items(): + instance = super().__new__(cls) + for key, value_str in environ.items(): match = fullmatch( r"OPENTELEMETRY_PYTHON_([A-Za-z_][\w_]*)", key @@ -114,45 +117,47 @@ def __new__(cls) -> "Configuration": if match is not None: key = match.group(1) + value = value_str # type: ConfigValue - if value == "True": + if value_str == "True": value = True - elif value == "False": + elif value_str == "False": value = False else: try: - value = int(value) + value = int(value_str) except ValueError: pass try: - value = float(value) + value = float(value_str) except ValueError: pass - setattr(Configuration, "_{}".format(key), value) - setattr( - Configuration, - key, - property( - fget=lambda cls, key=key: getattr( - cls, "_{}".format(key) - ) - ), - ) + instance._config_map[key] = value - Configuration.__slots__.append(key) + cls._instance = instance - Configuration.__slots__ = tuple(Configuration.__slots__) + return instance - Configuration._instance = object.__new__(cls) + def __getattr__(self, name: str) -> Optional[ConfigValue]: + return self._config_map.get(name) - return cls._instance + def __setattr__(self, key: str, val: ConfigValue) -> None: + if key == "_config_map": + super().__setattr__(key, val) + else: + raise AttributeError(key) - def __getattr__(self, name): - return None + def get(self, name: str, default: _T) -> _T: + """Use this typed method for dynamic access instead of `getattr` + + :rtype: str or bool or int or float or None + """ + val = self._config_map.get(name, default) + return val @classmethod - def _reset(cls): + def _reset(cls) -> None: """ This method "resets" the global configuration attributes @@ -160,10 +165,6 @@ def _reset(cls): only. """ - for slot in cls.__slots__: - if slot in cls.__dict__.keys(): - delattr(cls, slot) - delattr(cls, "_{}".format(slot)) - - cls.__slots__ = [] - cls._instance = None + if cls._instance: + cls._instance._config_map.clear() # pylint: disable=protected-access + cls._instance = None diff --git a/opentelemetry-api/src/opentelemetry/metrics/__init__.py b/opentelemetry-api/src/opentelemetry/metrics/__init__.py index 16ac4c096f0..da47356e058 100644 --- a/opentelemetry-api/src/opentelemetry/metrics/__init__.py +++ b/opentelemetry-api/src/opentelemetry/metrics/__init__.py @@ -31,7 +31,7 @@ from logging import getLogger from typing import Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar -from opentelemetry.util import _load_provider +from opentelemetry.util import _load_meter_provider logger = getLogger(__name__) ValueT = TypeVar("ValueT", int, float) @@ -162,7 +162,6 @@ class Observer(abc.ABC): """An observer type metric instrument used to capture a current set of values. - Observer instruments are asynchronous, a callback is invoked with the observer instrument as argument allowing the user to capture multiple values per collection interval. @@ -190,6 +189,18 @@ def observe(self, value: ValueT, labels: Dict[str, str]) -> None: """ +class ValueObserver(Observer): + """No-op implementation of ``ValueObserver``.""" + + def observe(self, value: ValueT, labels: Dict[str, str]) -> None: + """Captures ``value`` to the valueobserver. + + Args: + value: The value to capture to this valueobserver metric. + labels: Labels associated to ``value``. + """ + + class MeterProvider(abc.ABC): @abc.abstractmethod def get_meter( @@ -232,7 +243,9 @@ def get_meter( return DefaultMeter() -MetricT = TypeVar("MetricT", Counter, ValueRecorder, Observer) +MetricT = TypeVar("MetricT", Counter, ValueRecorder) +InstrumentT = TypeVar("InstrumentT", Counter, Observer, ValueRecorder) +ObserverT = TypeVar("ObserverT", bound=Observer) ObserverCallbackT = Callable[[Observer], None] @@ -297,6 +310,7 @@ def register_observer( description: str, unit: str, value_type: Type[ValueT], + observer_type: Type[ObserverT], label_keys: Sequence[str] = (), enabled: bool = True, ) -> "Observer": @@ -310,6 +324,7 @@ def register_observer( unit: Unit of the metric values following the UCUM convention (https://unitsofmeasure.org/ucum.html). value_type: The type of values being recorded by the metric. + observer_type: The type of observer being registered. label_keys: The keys for the labels with dynamic values. enabled: Whether to report the metric by default. Returns: A new ``Observer`` metric instrument. @@ -354,6 +369,7 @@ def register_observer( description: str, unit: str, value_type: Type[ValueT], + observer_type: Type[ObserverT], label_keys: Sequence[str] = (), enabled: bool = True, ) -> "Observer": @@ -395,6 +411,6 @@ def get_meter_provider() -> MeterProvider: global _METER_PROVIDER # pylint: disable=global-statement if _METER_PROVIDER is None: - _METER_PROVIDER = _load_provider("meter_provider") + _METER_PROVIDER = _load_meter_provider("meter_provider") return _METER_PROVIDER diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 52dc9bbae0a..88131738417 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -124,7 +124,7 @@ format_trace_id, ) from opentelemetry.trace.status import Status -from opentelemetry.util import _load_provider, types +from opentelemetry.util import _load_trace_provider, types logger = getLogger(__name__) @@ -467,6 +467,6 @@ def get_tracer_provider() -> TracerProvider: global _TRACER_PROVIDER # pylint: disable=global-statement if _TRACER_PROVIDER is None: - _TRACER_PROVIDER = _load_provider("tracer_provider") + _TRACER_PROVIDER = _load_trace_provider("tracer_provider") return _TRACER_PROVIDER diff --git a/opentelemetry-api/src/opentelemetry/util/__init__.py b/opentelemetry-api/src/opentelemetry/util/__init__.py index bab42b0bde2..ed1268fcb62 100644 --- a/opentelemetry-api/src/opentelemetry/util/__init__.py +++ b/opentelemetry-api/src/opentelemetry/util/__init__.py @@ -14,11 +14,17 @@ import re import time from logging import getLogger -from typing import Sequence, Union +from typing import TYPE_CHECKING, Sequence, Union, cast from pkg_resources import iter_entry_points -from opentelemetry.configuration import Configuration # type: ignore +from opentelemetry.configuration import Configuration + +if TYPE_CHECKING: + from opentelemetry.trace import TracerProvider + from opentelemetry.metrics import MeterProvider + +Provider = Union["TracerProvider", "MeterProvider"] logger = getLogger(__name__) @@ -34,25 +40,33 @@ def time_ns() -> int: return int(time.time() * 1e9) -def _load_provider( - provider: str, -) -> Union["TracerProvider", "MeterProvider"]: # type: ignore +def _load_provider(provider: str) -> Provider: try: - return next( # type: ignore + entry_point = next( iter_entry_points( "opentelemetry_{}".format(provider), - name=getattr( - Configuration(), # type: ignore - provider, - "default_{}".format(provider), + name=cast( + str, + Configuration().get( + provider, "default_{}".format(provider), + ), ), ) - ).load()() + ) + return cast(Provider, entry_point.load()(),) except Exception: # pylint: disable=broad-except logger.error("Failed to load configured provider %s", provider) raise +def _load_meter_provider(provider: str) -> "MeterProvider": + return cast("MeterProvider", _load_provider(provider)) + + +def _load_trace_provider(provider: str) -> "TracerProvider": + return cast("TracerProvider", _load_provider(provider)) + + # Pattern for matching up until the first '/' after the 'https://' part. _URL_PATTERN = r"(https?|ftp)://.*?/" diff --git a/opentelemetry-api/tests/configuration/test_configuration.py b/opentelemetry-api/tests/configuration/test_configuration.py index a817a1bf43d..32b62e619d5 100644 --- a/opentelemetry-api/tests/configuration/test_configuration.py +++ b/opentelemetry-api/tests/configuration/test_configuration.py @@ -16,16 +16,16 @@ from unittest import TestCase from unittest.mock import patch -from opentelemetry.configuration import Configuration # type: ignore +from opentelemetry.configuration import Configuration class TestConfiguration(TestCase): - def tearDown(self): + def tearDown(self) -> None: # This call resets the attributes of the Configuration class so that # each test is executed in the same conditions. Configuration._reset() - def test_singleton(self): + def test_singleton(self) -> None: self.assertIsInstance(Configuration(), Configuration) self.assertIs(Configuration(), Configuration()) @@ -39,7 +39,7 @@ def test_singleton(self): "OPENTELEMETRY_PTHON_TRACEX_PROVIDER": "tracex_provider", }, ) - def test_environment_variables(self): # type: ignore + def test_environment_variables(self): self.assertEqual( Configuration().METER_PROVIDER, "meter_provider" ) # pylint: disable=no-member @@ -62,16 +62,21 @@ def test_property(self): with self.assertRaises(AttributeError): Configuration().TRACER_PROVIDER = "new_tracer_provider" - def test_slots(self): + def test_slots(self) -> None: with self.assertRaises(AttributeError): Configuration().XYZ = "xyz" # pylint: disable=assigning-non-slot - def test_getattr(self): + def test_getattr(self) -> None: + # literal access self.assertIsNone(Configuration().XYZ) - def test_reset(self): + # dynamic access + self.assertIsNone(getattr(Configuration(), "XYZ")) + self.assertIsNone(Configuration().get("XYZ", None)) + + def test_reset(self) -> None: environ_patcher = patch.dict( - "os.environ", # type: ignore + "os.environ", {"OPENTELEMETRY_PYTHON_TRACER_PROVIDER": "tracer_provider"}, ) @@ -96,7 +101,7 @@ def test_reset(self): "OPENTELEMETRY_PYTHON_FALSE": "False", }, ) - def test_boolean(self): + def test_boolean(self) -> None: self.assertIsInstance( Configuration().TRUE, bool ) # pylint: disable=no-member @@ -114,7 +119,7 @@ def test_boolean(self): "OPENTELEMETRY_PYTHON_NON_INTEGER": "-12z3", }, ) - def test_integer(self): + def test_integer(self) -> None: self.assertEqual( Configuration().POSITIVE_INTEGER, 123 ) # pylint: disable=no-member @@ -133,7 +138,7 @@ def test_integer(self): "OPENTELEMETRY_PYTHON_NON_FLOAT": "-12z3.123", }, ) - def test_float(self): + def test_float(self) -> None: self.assertEqual( Configuration().POSITIVE_FLOAT, 123.123 ) # pylint: disable=no-member diff --git a/opentelemetry-api/tests/test_implementation.py b/opentelemetry-api/tests/test_implementation.py index 735ac4a683f..d0f9404a911 100644 --- a/opentelemetry-api/tests/test_implementation.py +++ b/opentelemetry-api/tests/test_implementation.py @@ -83,7 +83,9 @@ def test_create_metric(self): def test_register_observer(self): meter = metrics.DefaultMeter() callback = mock.Mock() - observer = meter.register_observer(callback, "", "", "", int, (), True) + observer = meter.register_observer( + callback, "", "", "", int, metrics.ValueObserver + ) self.assertIsInstance(observer, metrics.DefaultObserver) def test_unregister_observer(self): diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 940f1f17b8b..713580dbd6b 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -8,6 +8,10 @@ ([#761](https://github.com/open-telemetry/opentelemetry-python/pull/761)) - Adding trace.get_current_span, Removing Tracer.get_current_span ([#552](https://github.com/open-telemetry/opentelemetry-python/pull/552)) +- bugfix: byte type attributes are decoded before adding to attributes dict + ([#775](https://github.com/open-telemetry/opentelemetry-python/pull/775)) +- Rename Observer to ValueObserver + ([#764](https://github.com/open-telemetry/opentelemetry-python/pull/764)) ## 0.8b0 diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py index f231182cc28..507e00d8ead 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py @@ -190,8 +190,8 @@ def record( UPDATE_FUNCTION = record -class Observer(metrics_api.Observer): - """See `opentelemetry.metrics.Observer`.""" +class ValueObserver(metrics_api.ValueObserver): + """See `opentelemetry.metrics.ValueObserver`.""" def __init__( self, @@ -257,11 +257,11 @@ class Record: def __init__( self, - metric: metrics_api.MetricT, + instrument: metrics_api.InstrumentT, labels: Dict[str, str], aggregator: Aggregator, ): - self.metric = metric + self.instrument = instrument self.labels = labels self.aggregator = aggregator @@ -374,10 +374,11 @@ def register_observer( description: str, unit: str, value_type: Type[metrics_api.ValueT], + observer_type=Type[metrics_api.ObserverT], label_keys: Sequence[str] = (), enabled: bool = True, ) -> metrics_api.Observer: - ob = Observer( + ob = observer_type( callback, name, description, @@ -391,7 +392,7 @@ def register_observer( self.observers.add(ob) return ob - def unregister_observer(self, observer: "Observer") -> None: + def unregister_observer(self, observer: metrics_api.Observer) -> None: with self.observers_lock: self.observers.remove(observer) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py index f5a8693268e..16911f94efb 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py @@ -27,13 +27,13 @@ class MetricsExportResult(Enum): class MetricRecord: def __init__( self, - aggregator: Aggregator, + instrument: metrics_api.InstrumentT, labels: Tuple[Tuple[str, str]], - metric: metrics_api.MetricT, + aggregator: Aggregator, ): - self.aggregator = aggregator + self.instrument = instrument self.labels = labels - self.metric = metric + self.aggregator = aggregator class MetricsExporter: @@ -79,7 +79,7 @@ def export( print( '{}(data="{}", labels="{}", value={})'.format( type(self).__name__, - record.metric, + record.instrument, record.labels, record.aggregator.checkpoint, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py index 7e1baba2c77..1745d854e9d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py @@ -125,7 +125,7 @@ def merge(self, other): ) -class ObserverAggregator(Aggregator): +class ValueObserverAggregator(Aggregator): """Same as MinMaxSumCount but also with last value.""" _TYPE = namedtuple("minmaxsumcountlast", "min max sum count last") diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py index eda504d5684..db3675ecd61 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py @@ -15,13 +15,18 @@ import abc from typing import Sequence, Type -from opentelemetry.metrics import Counter, MetricT, Observer, ValueRecorder +from opentelemetry.metrics import ( + Counter, + InstrumentT, + ValueObserver, + ValueRecorder, +) from opentelemetry.sdk.metrics.export import MetricRecord from opentelemetry.sdk.metrics.export.aggregate import ( Aggregator, CounterAggregator, MinMaxSumCountAggregator, - ObserverAggregator, + ValueObserverAggregator, ) @@ -41,18 +46,18 @@ def __init__(self, stateful: bool): # (deltas) self.stateful = stateful - def aggregator_for(self, metric_type: Type[MetricT]) -> Aggregator: - """Returns an aggregator based on metric type. + def aggregator_for(self, instrument_type: Type[InstrumentT]) -> Aggregator: + """Returns an aggregator based on metric instrument type. Aggregators keep track of and updates values when metrics get updated. """ # pylint:disable=R0201 - if issubclass(metric_type, Counter): + if issubclass(instrument_type, Counter): return CounterAggregator() - if issubclass(metric_type, ValueRecorder): + if issubclass(instrument_type, ValueRecorder): return MinMaxSumCountAggregator() - if issubclass(metric_type, Observer): - return ObserverAggregator() + if issubclass(instrument_type, ValueObserver): + return ValueObserverAggregator() # TODO: Add other aggregators return CounterAggregator() @@ -63,8 +68,8 @@ def checkpoint_set(self) -> Sequence[MetricRecord]: data in all of the aggregators in this batcher. """ metric_records = [] - for (metric, labels), aggregator in self._batch_map.items(): - metric_records.append(MetricRecord(aggregator, labels, metric)) + for (instrument, labels), aggregator in self._batch_map.items(): + metric_records.append(MetricRecord(instrument, labels, aggregator)) return metric_records def finished_collection(self): @@ -90,7 +95,7 @@ class UngroupedBatcher(Batcher): def process(self, record): # Checkpoints the current aggregator value to be collected for export record.aggregator.take_checkpoint() - batch_key = (record.metric, record.labels) + batch_key = (record.instrument, record.labels) batch_value = self._batch_map.get(batch_key) aggregator = record.aggregator if batch_value: @@ -101,6 +106,6 @@ def process(self, record): if self.stateful: # if stateful batcher, create a copy of the aggregator and update # it with the current checkpointed value for long-term storage - aggregator = self.aggregator_for(record.metric.__class__) + aggregator = self.aggregator_for(record.instrument.__class__) aggregator.merge(record.aggregator) self._batch_map[batch_key] = aggregator diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 678682f0db0..77f4df266bc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -424,6 +424,12 @@ def set_attribute(self, key: str, value: types.AttributeValue) -> None: # Freeze mutable sequences defensively if isinstance(value, MutableSequence): value = tuple(value) + if isinstance(value, bytes): + try: + value = value.decode() + except ValueError: + logger.warning("Byte attribute could not be decoded.") + return with self._lock: self.attributes[key] = value diff --git a/opentelemetry-sdk/tests/metrics/export/test_export.py b/opentelemetry-sdk/tests/metrics/export/test_export.py index c77a132459c..178e41b2134 100644 --- a/opentelemetry-sdk/tests/metrics/export/test_export.py +++ b/opentelemetry-sdk/tests/metrics/export/test_export.py @@ -26,7 +26,7 @@ from opentelemetry.sdk.metrics.export.aggregate import ( CounterAggregator, MinMaxSumCountAggregator, - ObserverAggregator, + ValueObserverAggregator, ) from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher from opentelemetry.sdk.metrics.export.controller import PushController @@ -48,7 +48,7 @@ def test_export(self): ) labels = {"environment": "staging"} aggregator = CounterAggregator() - record = MetricRecord(aggregator, labels, metric) + record = MetricRecord(metric, labels, aggregator) result = '{}(data="{}", labels="{}", value={})'.format( ConsoleMetricsExporter.__name__, metric, @@ -90,7 +90,7 @@ def test_checkpoint_set(self): batcher._batch_map = _batch_map records = batcher.checkpoint_set() self.assertEqual(len(records), 1) - self.assertEqual(records[0].metric, metric) + self.assertEqual(records[0].instrument, metric) self.assertEqual(records[0].labels, labels) self.assertEqual(records[0].aggregator, aggregator) @@ -432,11 +432,11 @@ def test_concurrent_update_and_checkpoint(self): self.assertEqual(checkpoint_total, fut.result()) -class TestObserverAggregator(unittest.TestCase): +class TestValueObserverAggregator(unittest.TestCase): @mock.patch("opentelemetry.sdk.metrics.export.aggregate.time_ns") def test_update(self, time_mock): time_mock.return_value = 123 - observer = ObserverAggregator() + observer = ValueObserverAggregator() # test current values without any update self.assertEqual(observer.mmsc.current, (None, None, None, 0)) self.assertIsNone(observer.current) @@ -455,7 +455,7 @@ def test_update(self, time_mock): self.assertEqual(observer.current, values[-1]) def test_checkpoint(self): - observer = ObserverAggregator() + observer = ValueObserverAggregator() # take checkpoint wihtout any update observer.take_checkpoint() @@ -473,15 +473,19 @@ def test_checkpoint(self): ) def test_merge(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) mmsc_checkpoint2 = MinMaxSumCountAggregator._TYPE(1, 33, 44, 2) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) - checkpoint2 = ObserverAggregator._TYPE(*(mmsc_checkpoint2 + (27,))) + checkpoint2 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint2 + (27,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer2.mmsc.checkpoint = mmsc_checkpoint2 @@ -507,15 +511,19 @@ def test_merge(self): self.assertEqual(observer1.last_update_timestamp, 123) def test_merge_last_updated(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) mmsc_checkpoint2 = MinMaxSumCountAggregator._TYPE(1, 33, 44, 2) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) - checkpoint2 = ObserverAggregator._TYPE(*(mmsc_checkpoint2 + (27,))) + checkpoint2 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint2 + (27,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer2.mmsc.checkpoint = mmsc_checkpoint2 @@ -541,15 +549,19 @@ def test_merge_last_updated(self): self.assertEqual(observer1.last_update_timestamp, 123) def test_merge_last_updated_none(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) mmsc_checkpoint2 = MinMaxSumCountAggregator._TYPE(1, 33, 44, 2) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) - checkpoint2 = ObserverAggregator._TYPE(*(mmsc_checkpoint2 + (27,))) + checkpoint2 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint2 + (27,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer2.mmsc.checkpoint = mmsc_checkpoint2 @@ -575,11 +587,13 @@ def test_merge_last_updated_none(self): self.assertEqual(observer1.last_update_timestamp, 100) def test_merge_with_empty(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer1.checkpoint = checkpoint1 diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index 9d5d2b15d8e..4c2d691549d 100644 --- a/opentelemetry-sdk/tests/metrics/test_metrics.py +++ b/opentelemetry-sdk/tests/metrics/test_metrics.py @@ -88,7 +88,7 @@ def callback(observer): self.assertIsInstance(observer, metrics_api.Observer) observer.observe(45, {}) - observer = metrics.Observer( + observer = metrics.ValueObserver( callback, "name", "desc", "unit", int, meter, (), True ) @@ -165,7 +165,7 @@ def test_register_observer(self): callback = mock.Mock() observer = meter.register_observer( - callback, "name", "desc", "unit", int, (), True + callback, "name", "desc", "unit", int, metrics.ValueObserver ) self.assertIsInstance(observer, metrics_api.Observer) @@ -185,7 +185,7 @@ def test_unregister_observer(self): callback = mock.Mock() observer = meter.register_observer( - callback, "name", "desc", "unit", int, (), True + callback, "name", "desc", "unit", int, metrics.ValueObserver ) meter.unregister_observer(observer) @@ -290,10 +290,10 @@ def test_record(self): ) -class TestObserver(unittest.TestCase): +class TestValueObserver(unittest.TestCase): def test_observe(self): meter = metrics.MeterProvider().get_meter(__name__) - observer = metrics.Observer( + observer = metrics.ValueObserver( None, "name", "desc", "unit", int, meter, ("key",), True ) labels = {"key": "value"} @@ -310,7 +310,7 @@ def test_observe(self): def test_observe_disabled(self): meter = metrics.MeterProvider().get_meter(__name__) - observer = metrics.Observer( + observer = metrics.ValueObserver( None, "name", "desc", "unit", int, meter, ("key",), False ) labels = {"key": "value"} @@ -320,7 +320,7 @@ def test_observe_disabled(self): @mock.patch("opentelemetry.sdk.metrics.logger") def test_observe_incorrect_type(self, logger_mock): meter = metrics.MeterProvider().get_meter(__name__) - observer = metrics.Observer( + observer = metrics.ValueObserver( None, "name", "desc", "unit", int, meter, ("key",), True ) labels = {"key": "value"} @@ -332,7 +332,7 @@ def test_run(self): meter = metrics.MeterProvider().get_meter(__name__) callback = mock.Mock() - observer = metrics.Observer( + observer = metrics.ValueObserver( callback, "name", "desc", "unit", int, meter, (), True ) @@ -346,7 +346,7 @@ def test_run_exception(self, logger_mock): callback = mock.Mock() callback.side_effect = Exception("We have a problem!") - observer = metrics.Observer( + observer = metrics.ValueObserver( callback, "name", "desc", "unit", int, meter, (), True ) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index e60f23b8ce3..b3600f042fe 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -471,6 +471,22 @@ def test_invalid_attribute_values(self): self.assertEqual(len(root.attributes), 0) + def test_byte_type_attribute_value(self): + with self.tracer.start_as_current_span("root") as root: + with self.assertLogs(level=WARNING): + root.set_attribute( + "invalid-byte-type-attribute", + b"\xd8\xe1\xb7\xeb\xa8\xe5 \xd2\xb7\xe1", + ) + self.assertFalse( + "invalid-byte-type-attribute" in root.attributes + ) + + root.set_attribute("valid-byte-type-attribute", b"valid byte") + self.assertTrue( + isinstance(root.attributes["valid-byte-type-attribute"], str) + ) + def test_check_attribute_helper(self): # pylint: disable=protected-access self.assertFalse(trace._is_valid_attribute_value([1, 2, 3.4, "ss", 4]))