From f669b679b6c9036ae7075f4eca7b188ff4c09f4b Mon Sep 17 00:00:00 2001 From: Mateusz 'mat' Rumian Date: Thu, 15 Oct 2020 17:48:46 +0200 Subject: [PATCH] Support Environment Variables for JaegerSpanExporter configuration (#1114) --- .../CHANGELOG.md | 3 + .../opentelemetry-exporter-jaeger/README.rst | 8 ++ .../opentelemetry/exporter/jaeger/__init__.py | 84 +++++++++++-------- .../tests/test_jaeger_exporter.py | 76 ++++++++++++----- 4 files changed, 117 insertions(+), 54 deletions(-) diff --git a/exporter/opentelemetry-exporter-jaeger/CHANGELOG.md b/exporter/opentelemetry-exporter-jaeger/CHANGELOG.md index 1545e187fcf..b74ba6ea892 100644 --- a/exporter/opentelemetry-exporter-jaeger/CHANGELOG.md +++ b/exporter/opentelemetry-exporter-jaeger/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased +- Add support for Jaeger Span Exporter configuration by environment variables and
+ change JaegerSpanExporter constructor parameters + ([#1114](https://github.com/open-telemetry/opentelemetry-python/pull/1114)) ## Version 0.13b0 diff --git a/exporter/opentelemetry-exporter-jaeger/README.rst b/exporter/opentelemetry-exporter-jaeger/README.rst index 0069d130cdf..8a9b6b08197 100644 --- a/exporter/opentelemetry-exporter-jaeger/README.rst +++ b/exporter/opentelemetry-exporter-jaeger/README.rst @@ -19,6 +19,14 @@ Installation .. _Jaeger: https://www.jaegertracing.io/ .. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ +Configuration +------------- + +OpenTelemetry Jaeger Exporter can be configured by setting `JaegerSpanExporter parameters +`_ or by setting +`environment variables `_ References ---------- diff --git a/exporter/opentelemetry-exporter-jaeger/src/opentelemetry/exporter/jaeger/__init__.py b/exporter/opentelemetry-exporter-jaeger/src/opentelemetry/exporter/jaeger/__init__.py index 3cfd3fca431..7c6d8bd679f 100644 --- a/exporter/opentelemetry-exporter-jaeger/src/opentelemetry/exporter/jaeger/__init__.py +++ b/exporter/opentelemetry-exporter-jaeger/src/opentelemetry/exporter/jaeger/__init__.py @@ -39,10 +39,7 @@ agent_host_name='localhost', agent_port=6831, # optional: configure also collector - # collector_host_name='localhost', - # collector_port=14268, - # collector_endpoint='/api/traces?format=jaeger.thrift', - # collector_protocol='http', + # collector_endpoint='http://localhost:14268/api/traces?format=jaeger.thrift', # username=xxxx, # optional # password=xxxx, # optional ) @@ -69,7 +66,7 @@ from thrift.protocol import TBinaryProtocol, TCompactProtocol from thrift.transport import THttpClient, TTransport -import opentelemetry.trace as trace_api +from opentelemetry.configuration import Configuration from opentelemetry.exporter.jaeger.gen.agent import Agent as agent from opentelemetry.exporter.jaeger.gen.jaeger import Collector as jaeger from opentelemetry.sdk.trace.export import Span, SpanExporter, SpanExportResult @@ -77,8 +74,6 @@ DEFAULT_AGENT_HOST_NAME = "localhost" DEFAULT_AGENT_PORT = 6831 -DEFAULT_COLLECTOR_ENDPOINT = "/api/traces?format=jaeger.thrift" -DEFAULT_COLLECTOR_PROTOCOL = "http" UDP_PACKET_MAX_LENGTH = 65000 @@ -93,11 +88,7 @@ class JaegerSpanExporter(SpanExporter): when query for spans. agent_host_name: The host name of the Jaeger-Agent. agent_port: The port of the Jaeger-Agent. - collector_host_name: The host name of the Jaeger-Collector HTTP/HTTPS - Thrift. - collector_port: The port of the Jaeger-Collector HTTP/HTTPS Thrift. collector_endpoint: The endpoint of the Jaeger-Collector HTTP/HTTPS Thrift. - collector_protocol: The transfer protocol for the Jaeger-Collector(HTTP or HTTPS). username: The user name of the Basic Auth if authentication is required. password: The password of the Basic Auth if authentication is @@ -107,25 +98,39 @@ class JaegerSpanExporter(SpanExporter): def __init__( self, service_name, - agent_host_name=DEFAULT_AGENT_HOST_NAME, - agent_port=DEFAULT_AGENT_PORT, - collector_host_name=None, - collector_port=None, - collector_endpoint=DEFAULT_COLLECTOR_ENDPOINT, - collector_protocol=DEFAULT_COLLECTOR_PROTOCOL, + agent_host_name=None, + agent_port=None, + collector_endpoint=None, username=None, password=None, ): self.service_name = service_name - self.agent_host_name = agent_host_name - self.agent_port = agent_port + self.agent_host_name = _parameter_setter( + param=agent_host_name, + env_variable=Configuration().EXPORTER_JAEGER_AGENT_HOST, + default=DEFAULT_AGENT_HOST_NAME, + ) + self.agent_port = _parameter_setter( + param=agent_port, + env_variable=Configuration().EXPORTER_JAEGER_AGENT_PORT, + default=DEFAULT_AGENT_PORT, + ) self._agent_client = None - self.collector_host_name = collector_host_name - self.collector_port = collector_port - self.collector_endpoint = collector_endpoint - self.collector_protocol = collector_protocol - self.username = username - self.password = password + self.collector_endpoint = _parameter_setter( + param=collector_endpoint, + env_variable=Configuration().EXPORTER_JAEGER_ENDPOINT, + default=None, + ) + self.username = _parameter_setter( + param=username, + env_variable=Configuration().EXPORTER_JAEGER_USER, + default=None, + ) + self.password = _parameter_setter( + param=password, + env_variable=Configuration().EXPORTER_JAEGER_PASSWORD, + default=None, + ) self._collector = None @property @@ -141,21 +146,16 @@ def collector(self): if self._collector is not None: return self._collector - if self.collector_host_name is None or self.collector_port is None: + if self.collector_endpoint is None: return None - thrift_url = "{}://{}:{}{}".format( - self.collector_protocol, - self.collector_host_name, - self.collector_port, - self.collector_endpoint, - ) - auth = None if self.username is not None and self.password is not None: auth = (self.username, self.password) - self._collector = Collector(thrift_url=thrift_url, auth=auth) + self._collector = Collector( + thrift_url=self.collector_endpoint, auth=auth + ) return self._collector def export(self, spans): @@ -177,6 +177,22 @@ def shutdown(self): pass +def _parameter_setter(param, env_variable, default): + """Returns value according to the provided data. + + Args: + param: Constructor parameter value + env_variable: Environment variable related to the parameter + default: Constructor parameter default value + """ + if param is None: + res = env_variable or default + else: + res = param + + return res + + def _nsec_to_usec_round(nsec): """Round nanoseconds to microseconds""" return (nsec + 500) // 10 ** 3 diff --git a/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py b/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py index 0a01bcb2347..3daeacb7fd3 100644 --- a/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py +++ b/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py @@ -20,6 +20,7 @@ # pylint:disable=import-error import opentelemetry.exporter.jaeger as jaeger_exporter from opentelemetry import trace as trace_api +from opentelemetry.configuration import Configuration from opentelemetry.exporter.jaeger.gen.jaeger import ttypes as jaeger from opentelemetry.sdk import trace from opentelemetry.sdk.trace import Resource @@ -43,20 +44,14 @@ def setUp(self): def test_constructor_default(self): """Test the default values assigned by constructor.""" service_name = "my-service-name" - host_name = "localhost" - thrift_port = None + agent_host_name = "localhost" agent_port = 6831 - collector_endpoint = "/api/traces?format=jaeger.thrift" - collector_protocol = "http" exporter = jaeger_exporter.JaegerSpanExporter(service_name) self.assertEqual(exporter.service_name, service_name) - self.assertEqual(exporter.collector_host_name, None) - self.assertEqual(exporter.agent_host_name, host_name) + self.assertEqual(exporter.agent_host_name, agent_host_name) self.assertEqual(exporter.agent_port, agent_port) - self.assertEqual(exporter.collector_port, thrift_port) - self.assertEqual(exporter.collector_protocol, collector_protocol) - self.assertEqual(exporter.collector_endpoint, collector_endpoint) + self.assertEqual(exporter.collector_endpoint, None) self.assertEqual(exporter.username, None) self.assertEqual(exporter.password, None) self.assertTrue(exporter.collector is None) @@ -65,10 +60,7 @@ def test_constructor_default(self): def test_constructor_explicit(self): """Test the constructor passing all the options.""" service = "my-opentelemetry-jaeger" - collector_host_name = "opentelemetry.io" - collector_port = 15875 - collector_endpoint = "/myapi/traces?format=jaeger.thrift" - collector_protocol = "https" + collector_endpoint = "https://opentelemetry.io:15875" agent_port = 14268 agent_host_name = "opentelemetry.io" @@ -79,21 +71,16 @@ def test_constructor_explicit(self): exporter = jaeger_exporter.JaegerSpanExporter( service_name=service, - collector_host_name=collector_host_name, - collector_port=collector_port, - collector_endpoint=collector_endpoint, - collector_protocol="https", agent_host_name=agent_host_name, agent_port=agent_port, + collector_endpoint=collector_endpoint, username=username, password=password, ) + self.assertEqual(exporter.service_name, service) self.assertEqual(exporter.agent_host_name, agent_host_name) self.assertEqual(exporter.agent_port, agent_port) - self.assertEqual(exporter.collector_host_name, collector_host_name) - self.assertEqual(exporter.collector_port, collector_port) - self.assertEqual(exporter.collector_protocol, collector_protocol) self.assertTrue(exporter.collector is not None) self.assertEqual(exporter.collector.auth, auth) # property should not construct new object @@ -107,6 +94,55 @@ def test_constructor_explicit(self): self.assertNotEqual(exporter.collector, collector) self.assertTrue(exporter.collector.auth is None) + def test_constructor_by_environment_variables(self): + """Test the constructor using Environment Variables.""" + service = "my-opentelemetry-jaeger" + + agent_host_name = "opentelemetry.io" + agent_port = "6831" + + collector_endpoint = "https://opentelemetry.io:15875" + + username = "username" + password = "password" + auth = (username, password) + + environ_patcher = mock.patch.dict( + "os.environ", + { + "OTEL_EXPORTER_JAEGER_AGENT_HOST": agent_host_name, + "OTEL_EXPORTER_JAEGER_AGENT_PORT": agent_port, + "OTEL_EXPORTER_JAEGER_ENDPOINT": collector_endpoint, + "OTEL_EXPORTER_JAEGER_USER": username, + "OTEL_EXPORTER_JAEGER_PASSWORD": password, + }, + ) + + environ_patcher.start() + + exporter = jaeger_exporter.JaegerSpanExporter(service_name=service) + + self.assertEqual(exporter.service_name, service) + self.assertEqual(exporter.agent_host_name, agent_host_name) + self.assertEqual(exporter.agent_port, int(agent_port)) + self.assertTrue(exporter.collector is not None) + self.assertEqual(exporter.collector_endpoint, collector_endpoint) + self.assertEqual(exporter.collector.auth, auth) + # property should not construct new object + collector = exporter.collector + self.assertEqual(exporter.collector, collector) + # property should construct new object + # pylint: disable=protected-access + exporter._collector = None + exporter.username = None + exporter.password = None + self.assertNotEqual(exporter.collector, collector) + self.assertTrue(exporter.collector.auth is None) + + environ_patcher.stop() + + Configuration._reset() + def test_nsec_to_usec_round(self): # pylint: disable=protected-access nsec_to_usec_round = jaeger_exporter._nsec_to_usec_round