Repository: open-telemetry/opentelemetry-python

Issue:
Support env variables for Zipkin exporter
The spec describes environment variables that should be supported to configure the Zipkin exporter, this feature request is to add support in the current implementation.

https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-environment-variables.md


Discussion:

Resolving patch:
diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py
--- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py
+++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py
@@ -24,6 +24,7 @@
 
 .. _Zipkin: https://zipkin.io/
 .. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
+.. _Specification: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-environment-variables.md#zipkin-exporter
 
 .. code:: python
 
@@ -39,10 +40,7 @@
     zipkin_exporter = zipkin.ZipkinSpanExporter(
         service_name="my-helloworld-service",
         # optional:
-        # host_name="localhost",
-        # port=9411,
-        # endpoint="/api/v2/spans",
-        # protocol="http",
+        # url="http://localhost:9411/api/v2/spans",
         # ipv4="",
         # ipv6="",
         # retry=False,
@@ -57,24 +55,25 @@
     with tracer.start_as_current_span("foo"):
         print("Hello world!")
 
+The exporter supports endpoint configuration via the OTEL_EXPORTER_ZIPKIN_ENDPOINT environment variables as defined in the `Specification`_
+
 API
 ---
 """
 
 import json
 import logging
+import os
 from typing import Optional, Sequence
+from urllib.parse import urlparse
 
 import requests
 
 from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
 from opentelemetry.trace import Span, SpanContext, SpanKind
 
-DEFAULT_ENDPOINT = "/api/v2/spans"
-DEFAULT_HOST_NAME = "localhost"
-DEFAULT_PORT = 9411
-DEFAULT_PROTOCOL = "http"
 DEFAULT_RETRY = False
+DEFAULT_URL = "http://localhost:9411/api/v2/spans"
 ZIPKIN_HEADERS = {"Content-Type": "application/json"}
 
 SPAN_KIND_MAP = {
@@ -96,10 +95,7 @@ class ZipkinSpanExporter(SpanExporter):
     Args:
         service_name: Service that logged an annotation in a trace.Classifier
             when query for spans.
-        host_name: The host name of the Zipkin server
-        port: The port of the Zipkin server
-        endpoint: The endpoint of the Zipkin server
-        protocol: The protocol used for the request.
+        url: The Zipkin endpoint URL
         ipv4: Primary IPv4 address associated with this connection.
         ipv6: Primary IPv6 address associated with this connection.
         retry: Set to True to configure the exporter to retry on failure.
@@ -108,22 +104,21 @@ class ZipkinSpanExporter(SpanExporter):
     def __init__(
         self,
         service_name: str,
-        host_name: str = DEFAULT_HOST_NAME,
-        port: int = DEFAULT_PORT,
-        endpoint: str = DEFAULT_ENDPOINT,
-        protocol: str = DEFAULT_PROTOCOL,
+        url: str = None,
         ipv4: Optional[str] = None,
         ipv6: Optional[str] = None,
         retry: Optional[str] = DEFAULT_RETRY,
     ):
         self.service_name = service_name
-        self.host_name = host_name
-        self.port = port
-        self.endpoint = endpoint
-        self.protocol = protocol
-        self.url = "{}://{}:{}{}".format(
-            self.protocol, self.host_name, self.port, self.endpoint
-        )
+        if url is None:
+            self.url = os.environ.get(
+                "OTEL_EXPORTER_ZIPKIN_ENDPOINT", DEFAULT_URL
+            )
+        else:
+            self.url = url
+
+        self.port = urlparse(self.url).port
+
         self.ipv4 = ipv4
         self.ipv6 = ipv6
         self.retry = retry


Test patch:
diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py
--- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py
+++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import json
+import os
 import unittest
 from unittest.mock import MagicMock, patch
 
@@ -43,54 +44,56 @@ def setUp(self):
         self._test_span.start()
         self._test_span.end()
 
+    def tearDown(self):
+        if "OTEL_EXPORTER_ZIPKIN_ENDPOINT" in os.environ:
+            del os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"]
+
+    def test_constructor_env_var(self):
+        """Test the default values assigned by constructor."""
+        url = "https://foo:9911/path"
+        os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] = url
+        service_name = "my-service-name"
+        port = 9911
+        exporter = ZipkinSpanExporter(service_name)
+        ipv4 = None
+        ipv6 = None
+
+        self.assertEqual(exporter.service_name, service_name)
+        self.assertEqual(exporter.ipv4, ipv4)
+        self.assertEqual(exporter.ipv6, ipv6)
+        self.assertEqual(exporter.url, url)
+        self.assertEqual(exporter.port, port)
+
     def test_constructor_default(self):
         """Test the default values assigned by constructor."""
         service_name = "my-service-name"
-        host_name = "localhost"
         port = 9411
-        endpoint = "/api/v2/spans"
         exporter = ZipkinSpanExporter(service_name)
         ipv4 = None
         ipv6 = None
-        protocol = "http"
         url = "http://localhost:9411/api/v2/spans"
 
         self.assertEqual(exporter.service_name, service_name)
-        self.assertEqual(exporter.host_name, host_name)
         self.assertEqual(exporter.port, port)
-        self.assertEqual(exporter.endpoint, endpoint)
         self.assertEqual(exporter.ipv4, ipv4)
         self.assertEqual(exporter.ipv6, ipv6)
-        self.assertEqual(exporter.protocol, protocol)
         self.assertEqual(exporter.url, url)
 
     def test_constructor_explicit(self):
         """Test the constructor passing all the options."""
         service_name = "my-opentelemetry-zipkin"
-        host_name = "opentelemetry.io"
         port = 15875
-        endpoint = "/myapi/traces?format=zipkin"
         ipv4 = "1.2.3.4"
         ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
-        protocol = "https"
         url = "https://opentelemetry.io:15875/myapi/traces?format=zipkin"
         exporter = ZipkinSpanExporter(
-            service_name=service_name,
-            host_name=host_name,
-            port=port,
-            endpoint=endpoint,
-            ipv4=ipv4,
-            ipv6=ipv6,
-            protocol=protocol,
+            service_name=service_name, url=url, ipv4=ipv4, ipv6=ipv6,
         )
 
         self.assertEqual(exporter.service_name, service_name)
-        self.assertEqual(exporter.host_name, host_name)
         self.assertEqual(exporter.port, port)
-        self.assertEqual(exporter.endpoint, endpoint)
         self.assertEqual(exporter.ipv4, ipv4)
         self.assertEqual(exporter.ipv6, ipv6)
-        self.assertEqual(exporter.protocol, protocol)
         self.assertEqual(exporter.url, url)
 
     # pylint: disable=too-many-locals