We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
exporter-datadog: docstring does not match arguments
The docstring here:
https://github.com/open-telemetry/opentelemetry-python-contrib/blob/master/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py#L68
says it expects a list, but what it really expects is a string containing a comma-separated list of tags.

Do I fix the docstring, do I change the code code to accept a real list ,or do I change the code to accept *either* a list or the current string?  I'm also working on #154 and would really prefer both objects accept the same parameters if possible.. but this is just odd, and IMHO not very pythonic.

What do we prefer for things like this?

(I'm happy to do the work, again, since I'm already working on related stuff)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import logging
16 import os
17 from urllib.parse import urlparse
18 
19 from ddtrace.ext import SpanTypes as DatadogSpanTypes
20 from ddtrace.internal.writer import AgentWriter
21 from ddtrace.span import Span as DatadogSpan
22 
23 import opentelemetry.trace as trace_api
24 from opentelemetry.exporter.datadog.constants import (
25     DD_ORIGIN,
26     ENV_KEY,
27     SAMPLE_RATE_METRIC_KEY,
28     SERVICE_NAME_TAG,
29     VERSION_KEY,
30 )
31 from opentelemetry.sdk.trace import sampling
32 from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
33 
34 logger = logging.getLogger(__name__)
35 
36 
37 DEFAULT_AGENT_URL = "http://localhost:8126"
38 _INSTRUMENTATION_SPAN_TYPES = {
39     "opentelemetry.instrumentation.aiohttp-client": DatadogSpanTypes.HTTP,
40     "opentelemetry.instrumentation.asgi": DatadogSpanTypes.WEB,
41     "opentelemetry.instrumentation.dbapi": DatadogSpanTypes.SQL,
42     "opentelemetry.instrumentation.django": DatadogSpanTypes.WEB,
43     "opentelemetry.instrumentation.flask": DatadogSpanTypes.WEB,
44     "opentelemetry.instrumentation.grpc": DatadogSpanTypes.GRPC,
45     "opentelemetry.instrumentation.jinja2": DatadogSpanTypes.TEMPLATE,
46     "opentelemetry.instrumentation.mysql": DatadogSpanTypes.SQL,
47     "opentelemetry.instrumentation.psycopg2": DatadogSpanTypes.SQL,
48     "opentelemetry.instrumentation.pymemcache": DatadogSpanTypes.CACHE,
49     "opentelemetry.instrumentation.pymongo": DatadogSpanTypes.MONGODB,
50     "opentelemetry.instrumentation.pymysql": DatadogSpanTypes.SQL,
51     "opentelemetry.instrumentation.redis": DatadogSpanTypes.REDIS,
52     "opentelemetry.instrumentation.requests": DatadogSpanTypes.HTTP,
53     "opentelemetry.instrumentation.sqlalchemy": DatadogSpanTypes.SQL,
54     "opentelemetry.instrumentation.wsgi": DatadogSpanTypes.WEB,
55 }
56 
57 
58 class DatadogSpanExporter(SpanExporter):
59     """Datadog span exporter for OpenTelemetry.
60 
61     Args:
62         agent_url: The url of the Datadog Agent or use ``DD_TRACE_AGENT_URL`` environment variable
63         service: The service name to be used for the application or use ``DD_SERVICE`` environment variable
64         env: Set the application’s environment or use ``DD_ENV`` environment variable
65         version: Set the application’s version or use ``DD_VERSION`` environment variable
66         tags: A list of default tags to be added to every span or use ``DD_TAGS`` environment variable
67     """
68 
69     def __init__(
70         self, agent_url=None, service=None, env=None, version=None, tags=None
71     ):
72         self.agent_url = (
73             agent_url
74             if agent_url
75             else os.environ.get("DD_TRACE_AGENT_URL", DEFAULT_AGENT_URL)
76         )
77         self.service = service or os.environ.get("DD_SERVICE")
78         self.env = env or os.environ.get("DD_ENV")
79         self.version = version or os.environ.get("DD_VERSION")
80         self.tags = _parse_tags_str(tags or os.environ.get("DD_TAGS"))
81         self._agent_writer = None
82 
83     @property
84     def agent_writer(self):
85         if self._agent_writer is None:
86             url_parsed = urlparse(self.agent_url)
87             if url_parsed.scheme in ("http", "https"):
88                 self._agent_writer = AgentWriter(
89                     hostname=url_parsed.hostname,
90                     port=url_parsed.port,
91                     https=url_parsed.scheme == "https",
92                 )
93             elif url_parsed.scheme == "unix":
94                 self._agent_writer = AgentWriter(uds_path=url_parsed.path)
95             else:
96                 raise ValueError(
97                     "Unknown scheme `%s` for agent URL" % url_parsed.scheme
98                 )
99         return self._agent_writer
100 
101     def export(self, spans):
102         datadog_spans = self._translate_to_datadog(spans)
103 
104         self.agent_writer.write(spans=datadog_spans)
105 
106         return SpanExportResult.SUCCESS
107 
108     def shutdown(self):
109         if self.agent_writer.started:
110             self.agent_writer.stop()
111             self.agent_writer.join(self.agent_writer.exit_timeout)
112 
113     # pylint: disable=too-many-locals
114     def _translate_to_datadog(self, spans):
115         datadog_spans = []
116 
117         for span in spans:
118             trace_id, parent_id, span_id = _get_trace_ids(span)
119 
120             # datadog Span is initialized with a reference to the tracer which is
121             # used to record the span when it is finished. We can skip ignore this
122             # because we are not calling the finish method and explictly set the
123             # duration.
124             tracer = None
125 
126             # extract resource attributes to be used as tags as well as potential service name
127             [
128                 resource_tags,
129                 resource_service_name,
130             ] = _extract_tags_from_resource(span.resource)
131 
132             datadog_span = DatadogSpan(
133                 tracer,
134                 _get_span_name(span),
135                 service=resource_service_name or self.service,
136                 resource=_get_resource(span),
137                 span_type=_get_span_type(span),
138                 trace_id=trace_id,
139                 span_id=span_id,
140                 parent_id=parent_id,
141             )
142             datadog_span.start_ns = span.start_time
143             datadog_span.duration_ns = span.end_time - span.start_time
144 
145             if not span.status.is_ok:
146                 datadog_span.error = 1
147                 if span.status.description:
148                     exc_type, exc_val = _get_exc_info(span)
149                     # no mapping for error.stack since traceback not recorded
150                     datadog_span.set_tag("error.msg", exc_val)
151                     datadog_span.set_tag("error.type", exc_type)
152 
153             # combine resource attributes and span attributes, don't modify existing span attributes
154             combined_span_tags = {}
155             combined_span_tags.update(resource_tags)
156             combined_span_tags.update(span.attributes)
157 
158             datadog_span.set_tags(combined_span_tags)
159 
160             # add configured env tag
161             if self.env is not None:
162                 datadog_span.set_tag(ENV_KEY, self.env)
163 
164             # add configured application version tag to only root span
165             if self.version is not None and parent_id == 0:
166                 datadog_span.set_tag(VERSION_KEY, self.version)
167 
168             # add configured global tags
169             datadog_span.set_tags(self.tags)
170 
171             # add origin to root span
172             origin = _get_origin(span)
173             if origin and parent_id == 0:
174                 datadog_span.set_tag(DD_ORIGIN, origin)
175 
176             sampling_rate = _get_sampling_rate(span)
177             if sampling_rate is not None:
178                 datadog_span.set_metric(SAMPLE_RATE_METRIC_KEY, sampling_rate)
179 
180             # span events and span links are not supported
181 
182             datadog_spans.append(datadog_span)
183 
184         return datadog_spans
185 
186 
187 def _get_trace_ids(span):
188     """Extract tracer ids from span"""
189     ctx = span.get_span_context()
190     trace_id = ctx.trace_id
191     span_id = ctx.span_id
192 
193     if isinstance(span.parent, trace_api.Span):
194         parent_id = span.parent.get_span_context().span_id
195     elif isinstance(span.parent, trace_api.SpanContext):
196         parent_id = span.parent.span_id
197     else:
198         parent_id = 0
199 
200     trace_id = _convert_trace_id_uint64(trace_id)
201 
202     return trace_id, parent_id, span_id
203 
204 
205 def _convert_trace_id_uint64(otel_id):
206     """Convert 128-bit int used for trace_id to 64-bit unsigned int"""
207     return otel_id & 0xFFFFFFFFFFFFFFFF
208 
209 
210 def _get_span_name(span):
211     """Get span name by using instrumentation and kind while backing off to
212     span.name
213     """
214     instrumentation_name = (
215         span.instrumentation_info.name if span.instrumentation_info else None
216     )
217     span_kind_name = span.kind.name if span.kind else None
218     name = (
219         "{}.{}".format(instrumentation_name, span_kind_name)
220         if instrumentation_name and span_kind_name
221         else span.name
222     )
223     return name
224 
225 
226 def _get_resource(span):
227     """Get resource name for span"""
228     if "http.method" in span.attributes:
229         route = span.attributes.get("http.route")
230         return (
231             span.attributes["http.method"] + " " + route
232             if route
233             else span.attributes["http.method"]
234         )
235 
236     return span.name
237 
238 
239 def _get_span_type(span):
240     """Get Datadog span type"""
241     instrumentation_name = (
242         span.instrumentation_info.name if span.instrumentation_info else None
243     )
244     span_type = _INSTRUMENTATION_SPAN_TYPES.get(instrumentation_name)
245     return span_type
246 
247 
248 def _get_exc_info(span):
249     """Parse span status description for exception type and value"""
250     exc_type, exc_val = span.status.description.split(":", 1)
251     return exc_type, exc_val.strip()
252 
253 
254 def _get_origin(span):
255     ctx = span.get_span_context()
256     origin = ctx.trace_state.get(DD_ORIGIN)
257     return origin
258 
259 
260 def _get_sampling_rate(span):
261     ctx = span.get_span_context()
262     tracer_provider = trace_api.get_tracer_provider()
263     if not hasattr(tracer_provider, "sampler"):
264         return None
265     sampler = tracer_provider.sampler
266     return (
267         sampler.rate
268         if ctx.trace_flags.sampled
269         and isinstance(sampler, sampling.TraceIdRatioBased)
270         else None
271     )
272 
273 
274 def _parse_tags_str(tags_str):
275     """Parse a string of tags typically provided via environment variables.
276 
277     The expected string is of the form::
278         "key1:value1,key2:value2"
279 
280     :param tags_str: A string of the above form to parse tags from.
281     :return: A dict containing the tags that were parsed.
282     """
283     parsed_tags = {}
284     if not tags_str:
285         return parsed_tags
286 
287     for tag in tags_str.split(","):
288         try:
289             key, value = tag.split(":", 1)
290 
291             # Validate the tag
292             if key == "" or value == "" or value.endswith(":"):
293                 raise ValueError
294         except ValueError:
295             logger.error(
296                 "Malformed tag in tag pair '%s' from tag string '%s'.",
297                 tag,
298                 tags_str,
299             )
300         else:
301             parsed_tags[key] = value
302 
303     return parsed_tags
304 
305 
306 def _extract_tags_from_resource(resource):
307     """Parse tags from resource.attributes, except service.name which
308     has special significance within datadog"""
309     tags = {}
310     service_name = None
311     if not (resource and getattr(resource, "attributes", None)):
312         return [tags, service_name]
313 
314     for attribute_key, attribute_value in resource.attributes.items():
315         if attribute_key == SERVICE_NAME_TAG:
316             service_name = attribute_value
317         else:
318             tags[attribute_key] = attribute_value
319     return [tags, service_name]
320
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py
--- a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py
+++ b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py
@@ -63,7 +63,7 @@
         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
+        tags: A list (formatted as a comma-separated string) of default tags to be added to every span or use ``DD_TAGS`` environment variable
     """
 
     def __init__(

{"golden_diff": "diff --git a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py\n--- a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py\n+++ b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py\n@@ -63,7 +63,7 @@\n         service: The service name to be used for the application or use ``DD_SERVICE`` environment variable\n         env: Set the application\u2019s environment or use ``DD_ENV`` environment variable\n         version: Set the application\u2019s version or use ``DD_VERSION`` environment variable\n-        tags: A list of default tags to be added to every span or use ``DD_TAGS`` environment variable\n+        tags: A list (formatted as a comma-separated string) of default tags to be added to every span or use ``DD_TAGS`` environment variable\n     \"\"\"\n \n     def __init__(\n", "issue": "exporter-datadog: docstring does not match arguments\nThe docstring here:\r\nhttps://github.com/open-telemetry/opentelemetry-python-contrib/blob/master/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py#L68\r\nsays it expects a list, but what it really expects is a string containing a comma-separated list of tags.\r\n\r\nDo I fix the docstring, do I change the code code to accept a real list ,or do I change the code to accept *either* a list or the current string?  I'm also working on #154 and would really prefer both objects accept the same parameters if possible.. but this is just odd, and IMHO not very pythonic.\r\n\r\nWhat do we prefer for things like this?\r\n\r\n(I'm happy to do the work, again, since I'm already working on related stuff)\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport os\nfrom urllib.parse import urlparse\n\nfrom ddtrace.ext import SpanTypes as DatadogSpanTypes\nfrom ddtrace.internal.writer import AgentWriter\nfrom ddtrace.span import Span as DatadogSpan\n\nimport opentelemetry.trace as trace_api\nfrom opentelemetry.exporter.datadog.constants import (\n    DD_ORIGIN,\n    ENV_KEY,\n    SAMPLE_RATE_METRIC_KEY,\n    SERVICE_NAME_TAG,\n    VERSION_KEY,\n)\nfrom opentelemetry.sdk.trace import sampling\nfrom opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult\n\nlogger = logging.getLogger(__name__)\n\n\nDEFAULT_AGENT_URL = \"http://localhost:8126\"\n_INSTRUMENTATION_SPAN_TYPES = {\n    \"opentelemetry.instrumentation.aiohttp-client\": DatadogSpanTypes.HTTP,\n    \"opentelemetry.instrumentation.asgi\": DatadogSpanTypes.WEB,\n    \"opentelemetry.instrumentation.dbapi\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.django\": DatadogSpanTypes.WEB,\n    \"opentelemetry.instrumentation.flask\": DatadogSpanTypes.WEB,\n    \"opentelemetry.instrumentation.grpc\": DatadogSpanTypes.GRPC,\n    \"opentelemetry.instrumentation.jinja2\": DatadogSpanTypes.TEMPLATE,\n    \"opentelemetry.instrumentation.mysql\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.psycopg2\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.pymemcache\": DatadogSpanTypes.CACHE,\n    \"opentelemetry.instrumentation.pymongo\": DatadogSpanTypes.MONGODB,\n    \"opentelemetry.instrumentation.pymysql\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.redis\": DatadogSpanTypes.REDIS,\n    \"opentelemetry.instrumentation.requests\": DatadogSpanTypes.HTTP,\n    \"opentelemetry.instrumentation.sqlalchemy\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.wsgi\": DatadogSpanTypes.WEB,\n}\n\n\nclass DatadogSpanExporter(SpanExporter):\n    \"\"\"Datadog span exporter for OpenTelemetry.\n\n    Args:\n        agent_url: The url of the Datadog Agent or use ``DD_TRACE_AGENT_URL`` environment variable\n        service: The service name to be used for the application or use ``DD_SERVICE`` environment variable\n        env: Set the application\u2019s environment or use ``DD_ENV`` environment variable\n        version: Set the application\u2019s version or use ``DD_VERSION`` environment variable\n        tags: A list of default tags to be added to every span or use ``DD_TAGS`` environment variable\n    \"\"\"\n\n    def __init__(\n        self, agent_url=None, service=None, env=None, version=None, tags=None\n    ):\n        self.agent_url = (\n            agent_url\n            if agent_url\n            else os.environ.get(\"DD_TRACE_AGENT_URL\", DEFAULT_AGENT_URL)\n        )\n        self.service = service or os.environ.get(\"DD_SERVICE\")\n        self.env = env or os.environ.get(\"DD_ENV\")\n        self.version = version or os.environ.get(\"DD_VERSION\")\n        self.tags = _parse_tags_str(tags or os.environ.get(\"DD_TAGS\"))\n        self._agent_writer = None\n\n    @property\n    def agent_writer(self):\n        if self._agent_writer is None:\n            url_parsed = urlparse(self.agent_url)\n            if url_parsed.scheme in (\"http\", \"https\"):\n                self._agent_writer = AgentWriter(\n                    hostname=url_parsed.hostname,\n                    port=url_parsed.port,\n                    https=url_parsed.scheme == \"https\",\n                )\n            elif url_parsed.scheme == \"unix\":\n                self._agent_writer = AgentWriter(uds_path=url_parsed.path)\n            else:\n                raise ValueError(\n                    \"Unknown scheme `%s` for agent URL\" % url_parsed.scheme\n                )\n        return self._agent_writer\n\n    def export(self, spans):\n        datadog_spans = self._translate_to_datadog(spans)\n\n        self.agent_writer.write(spans=datadog_spans)\n\n        return SpanExportResult.SUCCESS\n\n    def shutdown(self):\n        if self.agent_writer.started:\n            self.agent_writer.stop()\n            self.agent_writer.join(self.agent_writer.exit_timeout)\n\n    # pylint: disable=too-many-locals\n    def _translate_to_datadog(self, spans):\n        datadog_spans = []\n\n        for span in spans:\n            trace_id, parent_id, span_id = _get_trace_ids(span)\n\n            # datadog Span is initialized with a reference to the tracer which is\n            # used to record the span when it is finished. We can skip ignore this\n            # because we are not calling the finish method and explictly set the\n            # duration.\n            tracer = None\n\n            # extract resource attributes to be used as tags as well as potential service name\n            [\n                resource_tags,\n                resource_service_name,\n            ] = _extract_tags_from_resource(span.resource)\n\n            datadog_span = DatadogSpan(\n                tracer,\n                _get_span_name(span),\n                service=resource_service_name or self.service,\n                resource=_get_resource(span),\n                span_type=_get_span_type(span),\n                trace_id=trace_id,\n                span_id=span_id,\n                parent_id=parent_id,\n            )\n            datadog_span.start_ns = span.start_time\n            datadog_span.duration_ns = span.end_time - span.start_time\n\n            if not span.status.is_ok:\n                datadog_span.error = 1\n                if span.status.description:\n                    exc_type, exc_val = _get_exc_info(span)\n                    # no mapping for error.stack since traceback not recorded\n                    datadog_span.set_tag(\"error.msg\", exc_val)\n                    datadog_span.set_tag(\"error.type\", exc_type)\n\n            # combine resource attributes and span attributes, don't modify existing span attributes\n            combined_span_tags = {}\n            combined_span_tags.update(resource_tags)\n            combined_span_tags.update(span.attributes)\n\n            datadog_span.set_tags(combined_span_tags)\n\n            # add configured env tag\n            if self.env is not None:\n                datadog_span.set_tag(ENV_KEY, self.env)\n\n            # add configured application version tag to only root span\n            if self.version is not None and parent_id == 0:\n                datadog_span.set_tag(VERSION_KEY, self.version)\n\n            # add configured global tags\n            datadog_span.set_tags(self.tags)\n\n            # add origin to root span\n            origin = _get_origin(span)\n            if origin and parent_id == 0:\n                datadog_span.set_tag(DD_ORIGIN, origin)\n\n            sampling_rate = _get_sampling_rate(span)\n            if sampling_rate is not None:\n                datadog_span.set_metric(SAMPLE_RATE_METRIC_KEY, sampling_rate)\n\n            # span events and span links are not supported\n\n            datadog_spans.append(datadog_span)\n\n        return datadog_spans\n\n\ndef _get_trace_ids(span):\n    \"\"\"Extract tracer ids from span\"\"\"\n    ctx = span.get_span_context()\n    trace_id = ctx.trace_id\n    span_id = ctx.span_id\n\n    if isinstance(span.parent, trace_api.Span):\n        parent_id = span.parent.get_span_context().span_id\n    elif isinstance(span.parent, trace_api.SpanContext):\n        parent_id = span.parent.span_id\n    else:\n        parent_id = 0\n\n    trace_id = _convert_trace_id_uint64(trace_id)\n\n    return trace_id, parent_id, span_id\n\n\ndef _convert_trace_id_uint64(otel_id):\n    \"\"\"Convert 128-bit int used for trace_id to 64-bit unsigned int\"\"\"\n    return otel_id & 0xFFFFFFFFFFFFFFFF\n\n\ndef _get_span_name(span):\n    \"\"\"Get span name by using instrumentation and kind while backing off to\n    span.name\n    \"\"\"\n    instrumentation_name = (\n        span.instrumentation_info.name if span.instrumentation_info else None\n    )\n    span_kind_name = span.kind.name if span.kind else None\n    name = (\n        \"{}.{}\".format(instrumentation_name, span_kind_name)\n        if instrumentation_name and span_kind_name\n        else span.name\n    )\n    return name\n\n\ndef _get_resource(span):\n    \"\"\"Get resource name for span\"\"\"\n    if \"http.method\" in span.attributes:\n        route = span.attributes.get(\"http.route\")\n        return (\n            span.attributes[\"http.method\"] + \" \" + route\n            if route\n            else span.attributes[\"http.method\"]\n        )\n\n    return span.name\n\n\ndef _get_span_type(span):\n    \"\"\"Get Datadog span type\"\"\"\n    instrumentation_name = (\n        span.instrumentation_info.name if span.instrumentation_info else None\n    )\n    span_type = _INSTRUMENTATION_SPAN_TYPES.get(instrumentation_name)\n    return span_type\n\n\ndef _get_exc_info(span):\n    \"\"\"Parse span status description for exception type and value\"\"\"\n    exc_type, exc_val = span.status.description.split(\":\", 1)\n    return exc_type, exc_val.strip()\n\n\ndef _get_origin(span):\n    ctx = span.get_span_context()\n    origin = ctx.trace_state.get(DD_ORIGIN)\n    return origin\n\n\ndef _get_sampling_rate(span):\n    ctx = span.get_span_context()\n    tracer_provider = trace_api.get_tracer_provider()\n    if not hasattr(tracer_provider, \"sampler\"):\n        return None\n    sampler = tracer_provider.sampler\n    return (\n        sampler.rate\n        if ctx.trace_flags.sampled\n        and isinstance(sampler, sampling.TraceIdRatioBased)\n        else None\n    )\n\n\ndef _parse_tags_str(tags_str):\n    \"\"\"Parse a string of tags typically provided via environment variables.\n\n    The expected string is of the form::\n        \"key1:value1,key2:value2\"\n\n    :param tags_str: A string of the above form to parse tags from.\n    :return: A dict containing the tags that were parsed.\n    \"\"\"\n    parsed_tags = {}\n    if not tags_str:\n        return parsed_tags\n\n    for tag in tags_str.split(\",\"):\n        try:\n            key, value = tag.split(\":\", 1)\n\n            # Validate the tag\n            if key == \"\" or value == \"\" or value.endswith(\":\"):\n                raise ValueError\n        except ValueError:\n            logger.error(\n                \"Malformed tag in tag pair '%s' from tag string '%s'.\",\n                tag,\n                tags_str,\n            )\n        else:\n            parsed_tags[key] = value\n\n    return parsed_tags\n\n\ndef _extract_tags_from_resource(resource):\n    \"\"\"Parse tags from resource.attributes, except service.name which\n    has special significance within datadog\"\"\"\n    tags = {}\n    service_name = None\n    if not (resource and getattr(resource, \"attributes\", None)):\n        return [tags, service_name]\n\n    for attribute_key, attribute_value in resource.attributes.items():\n        if attribute_key == SERVICE_NAME_TAG:\n            service_name = attribute_value\n        else:\n            tags[attribute_key] = attribute_value\n    return [tags, service_name]\n", "path": "exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport os\nfrom urllib.parse import urlparse\n\nfrom ddtrace.ext import SpanTypes as DatadogSpanTypes\nfrom ddtrace.internal.writer import AgentWriter\nfrom ddtrace.span import Span as DatadogSpan\n\nimport opentelemetry.trace as trace_api\nfrom opentelemetry.exporter.datadog.constants import (\n    DD_ORIGIN,\n    ENV_KEY,\n    SAMPLE_RATE_METRIC_KEY,\n    SERVICE_NAME_TAG,\n    VERSION_KEY,\n)\nfrom opentelemetry.sdk.trace import sampling\nfrom opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult\n\nlogger = logging.getLogger(__name__)\n\n\nDEFAULT_AGENT_URL = \"http://localhost:8126\"\n_INSTRUMENTATION_SPAN_TYPES = {\n    \"opentelemetry.instrumentation.aiohttp-client\": DatadogSpanTypes.HTTP,\n    \"opentelemetry.instrumentation.asgi\": DatadogSpanTypes.WEB,\n    \"opentelemetry.instrumentation.dbapi\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.django\": DatadogSpanTypes.WEB,\n    \"opentelemetry.instrumentation.flask\": DatadogSpanTypes.WEB,\n    \"opentelemetry.instrumentation.grpc\": DatadogSpanTypes.GRPC,\n    \"opentelemetry.instrumentation.jinja2\": DatadogSpanTypes.TEMPLATE,\n    \"opentelemetry.instrumentation.mysql\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.psycopg2\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.pymemcache\": DatadogSpanTypes.CACHE,\n    \"opentelemetry.instrumentation.pymongo\": DatadogSpanTypes.MONGODB,\n    \"opentelemetry.instrumentation.pymysql\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.redis\": DatadogSpanTypes.REDIS,\n    \"opentelemetry.instrumentation.requests\": DatadogSpanTypes.HTTP,\n    \"opentelemetry.instrumentation.sqlalchemy\": DatadogSpanTypes.SQL,\n    \"opentelemetry.instrumentation.wsgi\": DatadogSpanTypes.WEB,\n}\n\n\nclass DatadogSpanExporter(SpanExporter):\n    \"\"\"Datadog span exporter for OpenTelemetry.\n\n    Args:\n        agent_url: The url of the Datadog Agent or use ``DD_TRACE_AGENT_URL`` environment variable\n        service: The service name to be used for the application or use ``DD_SERVICE`` environment variable\n        env: Set the application\u2019s environment or use ``DD_ENV`` environment variable\n        version: Set the application\u2019s version or use ``DD_VERSION`` environment variable\n        tags: A list (formatted as a comma-separated string) of default tags to be added to every span or use ``DD_TAGS`` environment variable\n    \"\"\"\n\n    def __init__(\n        self, agent_url=None, service=None, env=None, version=None, tags=None\n    ):\n        self.agent_url = (\n            agent_url\n            if agent_url\n            else os.environ.get(\"DD_TRACE_AGENT_URL\", DEFAULT_AGENT_URL)\n        )\n        self.service = service or os.environ.get(\"DD_SERVICE\")\n        self.env = env or os.environ.get(\"DD_ENV\")\n        self.version = version or os.environ.get(\"DD_VERSION\")\n        self.tags = _parse_tags_str(tags or os.environ.get(\"DD_TAGS\"))\n        self._agent_writer = None\n\n    @property\n    def agent_writer(self):\n        if self._agent_writer is None:\n            url_parsed = urlparse(self.agent_url)\n            if url_parsed.scheme in (\"http\", \"https\"):\n                self._agent_writer = AgentWriter(\n                    hostname=url_parsed.hostname,\n                    port=url_parsed.port,\n                    https=url_parsed.scheme == \"https\",\n                )\n            elif url_parsed.scheme == \"unix\":\n                self._agent_writer = AgentWriter(uds_path=url_parsed.path)\n            else:\n                raise ValueError(\n                    \"Unknown scheme `%s` for agent URL\" % url_parsed.scheme\n                )\n        return self._agent_writer\n\n    def export(self, spans):\n        datadog_spans = self._translate_to_datadog(spans)\n\n        self.agent_writer.write(spans=datadog_spans)\n\n        return SpanExportResult.SUCCESS\n\n    def shutdown(self):\n        if self.agent_writer.started:\n            self.agent_writer.stop()\n            self.agent_writer.join(self.agent_writer.exit_timeout)\n\n    # pylint: disable=too-many-locals\n    def _translate_to_datadog(self, spans):\n        datadog_spans = []\n\n        for span in spans:\n            trace_id, parent_id, span_id = _get_trace_ids(span)\n\n            # datadog Span is initialized with a reference to the tracer which is\n            # used to record the span when it is finished. We can skip ignore this\n            # because we are not calling the finish method and explictly set the\n            # duration.\n            tracer = None\n\n            # extract resource attributes to be used as tags as well as potential service name\n            [\n                resource_tags,\n                resource_service_name,\n            ] = _extract_tags_from_resource(span.resource)\n\n            datadog_span = DatadogSpan(\n                tracer,\n                _get_span_name(span),\n                service=resource_service_name or self.service,\n                resource=_get_resource(span),\n                span_type=_get_span_type(span),\n                trace_id=trace_id,\n                span_id=span_id,\n                parent_id=parent_id,\n            )\n            datadog_span.start_ns = span.start_time\n            datadog_span.duration_ns = span.end_time - span.start_time\n\n            if not span.status.is_ok:\n                datadog_span.error = 1\n                if span.status.description:\n                    exc_type, exc_val = _get_exc_info(span)\n                    # no mapping for error.stack since traceback not recorded\n                    datadog_span.set_tag(\"error.msg\", exc_val)\n                    datadog_span.set_tag(\"error.type\", exc_type)\n\n            # combine resource attributes and span attributes, don't modify existing span attributes\n            combined_span_tags = {}\n            combined_span_tags.update(resource_tags)\n            combined_span_tags.update(span.attributes)\n\n            datadog_span.set_tags(combined_span_tags)\n\n            # add configured env tag\n            if self.env is not None:\n                datadog_span.set_tag(ENV_KEY, self.env)\n\n            # add configured application version tag to only root span\n            if self.version is not None and parent_id == 0:\n                datadog_span.set_tag(VERSION_KEY, self.version)\n\n            # add configured global tags\n            datadog_span.set_tags(self.tags)\n\n            # add origin to root span\n            origin = _get_origin(span)\n            if origin and parent_id == 0:\n                datadog_span.set_tag(DD_ORIGIN, origin)\n\n            sampling_rate = _get_sampling_rate(span)\n            if sampling_rate is not None:\n                datadog_span.set_metric(SAMPLE_RATE_METRIC_KEY, sampling_rate)\n\n            # span events and span links are not supported\n\n            datadog_spans.append(datadog_span)\n\n        return datadog_spans\n\n\ndef _get_trace_ids(span):\n    \"\"\"Extract tracer ids from span\"\"\"\n    ctx = span.get_span_context()\n    trace_id = ctx.trace_id\n    span_id = ctx.span_id\n\n    if isinstance(span.parent, trace_api.Span):\n        parent_id = span.parent.get_span_context().span_id\n    elif isinstance(span.parent, trace_api.SpanContext):\n        parent_id = span.parent.span_id\n    else:\n        parent_id = 0\n\n    trace_id = _convert_trace_id_uint64(trace_id)\n\n    return trace_id, parent_id, span_id\n\n\ndef _convert_trace_id_uint64(otel_id):\n    \"\"\"Convert 128-bit int used for trace_id to 64-bit unsigned int\"\"\"\n    return otel_id & 0xFFFFFFFFFFFFFFFF\n\n\ndef _get_span_name(span):\n    \"\"\"Get span name by using instrumentation and kind while backing off to\n    span.name\n    \"\"\"\n    instrumentation_name = (\n        span.instrumentation_info.name if span.instrumentation_info else None\n    )\n    span_kind_name = span.kind.name if span.kind else None\n    name = (\n        \"{}.{}\".format(instrumentation_name, span_kind_name)\n        if instrumentation_name and span_kind_name\n        else span.name\n    )\n    return name\n\n\ndef _get_resource(span):\n    \"\"\"Get resource name for span\"\"\"\n    if \"http.method\" in span.attributes:\n        route = span.attributes.get(\"http.route\")\n        return (\n            span.attributes[\"http.method\"] + \" \" + route\n            if route\n            else span.attributes[\"http.method\"]\n        )\n\n    return span.name\n\n\ndef _get_span_type(span):\n    \"\"\"Get Datadog span type\"\"\"\n    instrumentation_name = (\n        span.instrumentation_info.name if span.instrumentation_info else None\n    )\n    span_type = _INSTRUMENTATION_SPAN_TYPES.get(instrumentation_name)\n    return span_type\n\n\ndef _get_exc_info(span):\n    \"\"\"Parse span status description for exception type and value\"\"\"\n    exc_type, exc_val = span.status.description.split(\":\", 1)\n    return exc_type, exc_val.strip()\n\n\ndef _get_origin(span):\n    ctx = span.get_span_context()\n    origin = ctx.trace_state.get(DD_ORIGIN)\n    return origin\n\n\ndef _get_sampling_rate(span):\n    ctx = span.get_span_context()\n    tracer_provider = trace_api.get_tracer_provider()\n    if not hasattr(tracer_provider, \"sampler\"):\n        return None\n    sampler = tracer_provider.sampler\n    return (\n        sampler.rate\n        if ctx.trace_flags.sampled\n        and isinstance(sampler, sampling.TraceIdRatioBased)\n        else None\n    )\n\n\ndef _parse_tags_str(tags_str):\n    \"\"\"Parse a string of tags typically provided via environment variables.\n\n    The expected string is of the form::\n        \"key1:value1,key2:value2\"\n\n    :param tags_str: A string of the above form to parse tags from.\n    :return: A dict containing the tags that were parsed.\n    \"\"\"\n    parsed_tags = {}\n    if not tags_str:\n        return parsed_tags\n\n    for tag in tags_str.split(\",\"):\n        try:\n            key, value = tag.split(\":\", 1)\n\n            # Validate the tag\n            if key == \"\" or value == \"\" or value.endswith(\":\"):\n                raise ValueError\n        except ValueError:\n            logger.error(\n                \"Malformed tag in tag pair '%s' from tag string '%s'.\",\n                tag,\n                tags_str,\n            )\n        else:\n            parsed_tags[key] = value\n\n    return parsed_tags\n\n\ndef _extract_tags_from_resource(resource):\n    \"\"\"Parse tags from resource.attributes, except service.name which\n    has special significance within datadog\"\"\"\n    tags = {}\n    service_name = None\n    if not (resource and getattr(resource, \"attributes\", None)):\n        return [tags, service_name]\n\n    for attribute_key, attribute_value in resource.attributes.items():\n        if attribute_key == SERVICE_NAME_TAG:\n            service_name = attribute_value\n        else:\n            tags[attribute_key] = attribute_value\n    return [tags, service_name]\n", "path": "exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py"}]}