Repository: open-telemetry/opentelemetry-python

Issue:
datadog: honor DD environment variables
There's a few environment variables that are not honored by the exporter currently. These are:

* DD_AGENT_HOST, for the agent hostname
* DATADOG_SERVICE_NAME, DD_SERVICE_NAME, DD_SERVICE for the datadog service name.

There is a more comprehensive list, as outlined here:

https://docs.datadoghq.com/tracing/setup/python/#configuration


Discussion:
another one :DD_ENV: https://docs.datadoghq.com/tracing/setup/python/#configuration
Thanks for raising these issues @toumorokoshi.

I only added support for configuring `DD_TRACE_AGENT_URL` whereas setting the agent hostname and port have been deprecated (though the documentation you linked to does not reflect that, which we will address):

https://github.com/open-telemetry/opentelemetry-python/blob/master/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py#L63-L67
http://pypi.datadoghq.com/trace/docs/configuration.html

Similarly, the exporter supports `DD_SERVICE` whereas the `DATADOG_SERVICE_NAME` and `DD_SERVICE_NAME` have been deprecated.

I'll start a PR for adding `DD_ENV`, as well as the other tracer configuration options missing (`DD_VERSION` for adding the application version to the root span and `DD_TAGS` for adding default tags to all spans).

Resolving patch:
diff --git a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/constants.py b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/constants.py
--- a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/constants.py
+++ b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/constants.py
@@ -4,3 +4,5 @@
 USER_KEEP = 2
 SAMPLE_RATE_METRIC_KEY = "_sample_rate"
 SAMPLING_PRIORITY_KEY = "_sampling_priority_v1"
+ENV_KEY = "env"
+VERSION_KEY = "version"
diff --git a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py
--- a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py
+++ b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py
@@ -25,7 +25,7 @@
 from opentelemetry.trace.status import StatusCanonicalCode
 
 # pylint:disable=relative-beyond-top-level
-from .constants import DD_ORIGIN, SAMPLE_RATE_METRIC_KEY
+from .constants import DD_ORIGIN, ENV_KEY, SAMPLE_RATE_METRIC_KEY, VERSION_KEY
 
 logger = logging.getLogger(__name__)
 
@@ -56,16 +56,24 @@ class DatadogSpanExporter(SpanExporter):
 
     Args:
         agent_url: The url of the Datadog Agent or use ``DD_TRACE_AGENT_URL`` environment variable
-        service: The service to be used for the application or use ``DD_SERVICE`` environment variable
+        service: The service name to be used for the application or use ``DD_SERVICE`` environment variable
+        env: Set the application’s environment or use ``DD_ENV`` environment variable
+        version: Set the application’s version or use ``DD_VERSION`` environment variable
+        tags: A list of default tags to be added to every span or use ``DD_TAGS`` environment variable
     """
 
-    def __init__(self, agent_url=None, service=None):
+    def __init__(
+        self, agent_url=None, service=None, env=None, version=None, tags=None
+    ):
         self.agent_url = (
             agent_url
             if agent_url
             else os.environ.get("DD_TRACE_AGENT_URL", DEFAULT_AGENT_URL)
         )
-        self.service = service if service else os.environ.get("DD_SERVICE")
+        self.service = service or os.environ.get("DD_SERVICE")
+        self.env = env or os.environ.get("DD_ENV")
+        self.version = version or os.environ.get("DD_VERSION")
+        self.tags = _parse_tags_str(tags or os.environ.get("DD_TAGS"))
         self._agent_writer = None
 
     @property
@@ -133,6 +141,17 @@ def _translate_to_datadog(self, spans):
 
             datadog_span.set_tags(span.attributes)
 
+            # add configured env tag
+            if self.env is not None:
+                datadog_span.set_tag(ENV_KEY, self.env)
+
+            # add configured application version tag to only root span
+            if self.version is not None and parent_id == 0:
+                datadog_span.set_tag(VERSION_KEY, self.version)
+
+            # add configured global tags
+            datadog_span.set_tags(self.tags)
+
             # add origin to root span
             origin = _get_origin(span)
             if origin and parent_id == 0:
@@ -230,3 +249,35 @@ def _get_sampling_rate(span):
         and isinstance(span.sampler, trace_api.sampling.ProbabilitySampler)
         else None
     )
+
+
+def _parse_tags_str(tags_str):
+    """Parse a string of tags typically provided via environment variables.
+
+    The expected string is of the form::
+        "key1:value1,key2:value2"
+
+    :param tags_str: A string of the above form to parse tags from.
+    :return: A dict containing the tags that were parsed.
+    """
+    parsed_tags = {}
+    if not tags_str:
+        return parsed_tags
+
+    for tag in tags_str.split(","):
+        try:
+            key, value = tag.split(":", 1)
+
+            # Validate the tag
+            if key == "" or value == "" or value.endswith(":"):
+                raise ValueError
+        except ValueError:
+            logger.error(
+                "Malformed tag in tag pair '%s' from tag string '%s'.",
+                tag,
+                tags_str,
+            )
+        else:
+            parsed_tags[key] = value
+
+    return parsed_tags


Test patch:
diff --git a/ext/opentelemetry-ext-datadog/tests/test_datadog_exporter.py b/ext/opentelemetry-ext-datadog/tests/test_datadog_exporter.py
--- a/ext/opentelemetry-ext-datadog/tests/test_datadog_exporter.py
+++ b/ext/opentelemetry-ext-datadog/tests/test_datadog_exporter.py
@@ -72,26 +72,73 @@ def test_constructor_explicit(self):
         """Test the constructor passing all the options."""
         agent_url = "http://localhost:8126"
         exporter = datadog.DatadogSpanExporter(
-            agent_url=agent_url, service="explicit"
+            agent_url=agent_url, service="explicit",
         )
 
         self.assertEqual(exporter.agent_url, agent_url)
         self.assertEqual(exporter.service, "explicit")
-        self.assertIsNotNone(exporter.agent_writer)
+        self.assertIsNone(exporter.env)
+        self.assertIsNone(exporter.version)
+        self.assertEqual(exporter.tags, {})
+
+        exporter = datadog.DatadogSpanExporter(
+            agent_url=agent_url,
+            service="explicit",
+            env="test",
+            version="0.0.1",
+            tags="",
+        )
+
+        self.assertEqual(exporter.agent_url, agent_url)
+        self.assertEqual(exporter.service, "explicit")
+        self.assertEqual(exporter.env, "test")
+        self.assertEqual(exporter.version, "0.0.1")
+        self.assertEqual(exporter.tags, {})
+
+        exporter = datadog.DatadogSpanExporter(
+            agent_url=agent_url,
+            service="explicit",
+            env="test",
+            version="0.0.1",
+            tags="team:testers,layer:app",
+        )
+
+        self.assertEqual(exporter.agent_url, agent_url)
+        self.assertEqual(exporter.service, "explicit")
+        self.assertEqual(exporter.env, "test")
+        self.assertEqual(exporter.version, "0.0.1")
+        self.assertEqual(exporter.tags, {"team": "testers", "layer": "app"})
 
     @mock.patch.dict(
         "os.environ",
-        {"DD_TRACE_AGENT_URL": "http://agent:8126", "DD_SERVICE": "environ"},
+        {
+            "DD_TRACE_AGENT_URL": "http://agent:8126",
+            "DD_SERVICE": "test-service",
+            "DD_ENV": "test",
+            "DD_VERSION": "0.0.1",
+            "DD_TAGS": "team:testers",
+        },
     )
     def test_constructor_environ(self):
         exporter = datadog.DatadogSpanExporter()
 
         self.assertEqual(exporter.agent_url, "http://agent:8126")
-        self.assertEqual(exporter.service, "environ")
+        self.assertEqual(exporter.service, "test-service")
+        self.assertEqual(exporter.env, "test")
+        self.assertEqual(exporter.version, "0.0.1")
+        self.assertEqual(exporter.tags, {"team": "testers"})
         self.assertIsNotNone(exporter.agent_writer)
 
     # pylint: disable=too-many-locals
-    @mock.patch.dict("os.environ", {"DD_SERVICE": "test-service"})
+    @mock.patch.dict(
+        "os.environ",
+        {
+            "DD_SERVICE": "test-service",
+            "DD_ENV": "test",
+            "DD_VERSION": "0.0.1",
+            "DD_TAGS": "team:testers",
+        },
+    )
     def test_translate_to_datadog(self):
         # pylint: disable=invalid-name
         self.maxDiff = None
@@ -174,6 +221,7 @@ def test_translate_to_datadog(self):
                 duration=durations[0],
                 error=0,
                 service="test-service",
+                meta={"env": "test", "team": "testers"},
             ),
             dict(
                 trace_id=trace_id_low,
@@ -185,6 +233,7 @@ def test_translate_to_datadog(self):
                 duration=durations[1],
                 error=0,
                 service="test-service",
+                meta={"env": "test", "team": "testers", "version": "0.0.1"},
             ),
             dict(
                 trace_id=trace_id_low,
@@ -196,12 +245,12 @@ def test_translate_to_datadog(self):
                 duration=durations[2],
                 error=0,
                 service="test-service",
+                meta={"env": "test", "team": "testers", "version": "0.0.1"},
             ),
         ]
 
         self.assertEqual(datadog_spans, expected_spans)
 
-    @mock.patch.dict("os.environ", {"DD_SERVICE": "test-service"})
     def test_export(self):
         """Test that agent and/or collector are invoked"""
         # create and save span to be used in tests