diff --git a/CHANGELOG.md b/CHANGELOG.md index 83fd52921a1..17c23e4fa62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3038](https://github.com/open-telemetry/opentelemetry-python/pull/3038)) - Fix: Avoid generator in metrics _ViewInstrumentMatch.collect() ([#3035](https://github.com/open-telemetry/opentelemetry-python/pull/3035) +- [exporter-otlp-proto-grpc] add user agent string + ([#3009](https://github.com/open-telemetry/opentelemetry-python/pull/3009)) ## Version 1.14.0/0.35b0 (2022-11-04) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/__init__.py index 0a33b6325ad..60af18de2e6 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/__init__.py @@ -69,3 +69,7 @@ API --- """ +from .version import __version__ + +_USER_AGENT_HEADER_VALUE = "OTel OTLP Exporter Python/" + __version__ +_OTLP_GRPC_HEADERS = [("user-agent", _USER_AGENT_HEADER_VALUE)] 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 d1427e41fad..c068f87d783 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 @@ -54,6 +54,9 @@ from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.metrics.export import MetricsData from opentelemetry.util.re import parse_env_headers +from opentelemetry.exporter.otlp.proto.grpc import ( + _OTLP_GRPC_HEADERS, +) logger = getLogger(__name__) SDKDataT = TypeVar("SDKDataT") @@ -251,6 +254,10 @@ def __init__( self._headers = tuple(temp_headers.items()) elif isinstance(self._headers, dict): self._headers = tuple(self._headers.items()) + if self._headers is None: + self._headers = tuple(_OTLP_GRPC_HEADERS) + else: + self._headers = self._headers + tuple(_OTLP_GRPC_HEADERS) self._timeout = timeout or int( environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, 10) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index c5f0fcd92ec..0861d06d654 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -26,6 +26,7 @@ OTLPLogExporter, ) from opentelemetry.exporter.otlp.proto.grpc.exporter import _translate_value +from opentelemetry.exporter.otlp.proto.grpc.version import __version__ from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( ExportLogsServiceRequest, ExportLogsServiceResponse, @@ -249,6 +250,13 @@ def test_otlp_exporter_endpoint(self, mock_secure, mock_insecure): ) mock_method.reset_mock() + def test_otlp_headers_from_env(self): + # pylint: disable=protected-access + self.assertEqual( + self.exporter._headers, + (("user-agent", "OTel OTLP Exporter Python/" + __version__),), + ) + @patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo") @patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep") def test_unavailable(self, mock_sleep, mock_expo): diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py index 4f0349257d8..6436661a985 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py @@ -26,6 +26,7 @@ from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( OTLPMetricExporter, ) +from opentelemetry.exporter.otlp.proto.grpc.version import __version__ from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( ExportMetricsServiceRequest, ExportMetricsServiceResponse, @@ -413,14 +414,24 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure): exporter = OTLPMetricExporter() # pylint: disable=protected-access self.assertEqual( - exporter._headers, (("key1", "value1"), ("key2", "VALUE=2")) + exporter._headers, + ( + ("key1", "value1"), + ("key2", "VALUE=2"), + ("user-agent", "OTel OTLP Exporter Python/" + __version__), + ), ) exporter = OTLPMetricExporter( headers=(("key3", "value3"), ("key4", "value4")) ) # pylint: disable=protected-access self.assertEqual( - exporter._headers, (("key3", "value3"), ("key4", "value4")) + exporter._headers, + ( + ("key3", "value3"), + ("key4", "value4"), + ("user-agent", "OTel OTLP Exporter Python/" + __version__), + ), ) @patch.dict( diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py index cfb286edee0..89cf7f66391 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py @@ -29,6 +29,7 @@ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( OTLPSpanExporter, ) +from opentelemetry.exporter.otlp.proto.grpc.version import __version__ from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( ExportTraceServiceRequest, ExportTraceServiceResponse, @@ -275,21 +276,36 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure): exporter = OTLPSpanExporter() # pylint: disable=protected-access self.assertEqual( - exporter._headers, (("key1", "value1"), ("key2", "VALUE=2")) + exporter._headers, + ( + ("key1", "value1"), + ("key2", "VALUE=2"), + ("user-agent", "OTel OTLP Exporter Python/" + __version__), + ), ) exporter = OTLPSpanExporter( headers=(("key3", "value3"), ("key4", "value4")) ) # pylint: disable=protected-access self.assertEqual( - exporter._headers, (("key3", "value3"), ("key4", "value4")) + exporter._headers, + ( + ("key3", "value3"), + ("key4", "value4"), + ("user-agent", "OTel OTLP Exporter Python/" + __version__), + ), ) exporter = OTLPSpanExporter( headers={"key5": "value5", "key6": "value6"} ) # pylint: disable=protected-access self.assertEqual( - exporter._headers, (("key5", "value5"), ("key6", "value6")) + exporter._headers, + ( + ("key5", "value5"), + ("key6", "value6"), + ("user-agent", "OTel OTLP Exporter Python/" + __version__), + ), ) @patch.dict( @@ -434,7 +450,11 @@ def test_otlp_exporter_otlp_compression_precendence( def test_otlp_headers(self, mock_ssl_channel, mock_secure): exporter = OTLPSpanExporter() # pylint: disable=protected-access - self.assertIsNone(exporter._headers, None) + # This ensures that there is no other header than standard user-agent. + self.assertEqual( + exporter._headers, + (("user-agent", "OTel OTLP Exporter Python/" + __version__),), + ) @patch("opentelemetry.exporter.otlp.proto.grpc.exporter.backoff") @patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")