Skip to content

Commit e3a3619

Browse files
committed
feat: Add exporter to datadog
1 parent 7c2ceba commit e3a3619

10 files changed

Lines changed: 468 additions & 0 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Datadog Exporter Example
2+
=======================
3+
4+
This example shows how to use OpenTelemetry to send tracing data to Datadog.
5+
6+
Installation
7+
------------
8+
9+
.. code-block:: sh
10+
11+
pip install opentelemetry-api
12+
pip install opentelemetry-sdk
13+
pip install opentelemetry-ext-datadog
14+
15+
Run the Example
16+
---------------
17+
18+
* Start Datadog Agent
19+
20+
.. code-block:: sh
21+
22+
docker run -d -v /var/run/docker.sock:/var/run/docker.sock:ro \
23+
-v /proc/:/host/proc/:ro \
24+
-v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \
25+
-p 127.0.0.1:8126:8126/tcp \
26+
-e DD_API_KEY="<DATADOG_API_KEY>" \
27+
-e DD_APM_ENABLED=true \
28+
datadog/agent:latest
29+
30+
* Run the example
31+
32+
.. code-block:: sh
33+
34+
python datadog_exporter.py
35+
36+
The traces will be available at http://datadoghq.com/.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
import os
17+
18+
from opentelemetry import trace
19+
from opentelemetry.ext.datadog import DatadogSpanExporter
20+
from opentelemetry.sdk.trace import TracerProvider
21+
from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
22+
23+
trace.set_tracer_provider(TracerProvider())
24+
tracer = trace.get_tracer(__name__)
25+
26+
exporter = DatadogSpanExporter(agent_url="http://localhost:8126")
27+
28+
span_processor = BatchExportSpanProcessor(exporter)
29+
trace.get_tracer_provider().add_span_processor(span_processor)
30+
31+
with tracer.start_as_current_span("foo"):
32+
with tracer.start_as_current_span("bar"):
33+
with tracer.start_as_current_span("baz"):
34+
print("Hello world from OpenTelemetry Python!")
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
OpenTelemetry Datadog Exporter
2+
==============================
3+
4+
This library allows to export tracing data to `Datadog <https://www.datadoghq.com/>`_.
5+
6+
Installation
7+
------------
8+
9+
.. code-block:: sh
10+
11+
pip install opentelemetry-ext-datadog
12+
13+
14+
.. _Datadog: https://www.datadoghq.com/
15+
.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
16+
17+
18+
References
19+
----------
20+
21+
* `Datadog <https://www.datadoghq.com/>`_
22+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
[metadata]
16+
name = opentelemetry-ext-datadog
17+
description = Datadog Exporter for OpenTelemetry
18+
long_description = file: README.rst
19+
long_description_content_type = text/x-rst
20+
author = OpenTelemetry Authors
21+
author_email = cncf-opentelemetry-contributors@lists.cncf.io
22+
url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-datadog
23+
platforms = any
24+
license = Apache-2.0
25+
classifiers =
26+
Development Status :: 4 - Beta
27+
Intended Audience :: Developers
28+
License :: OSI Approved :: Apache Software License
29+
Programming Language :: Python
30+
Programming Language :: Python :: 3
31+
Programming Language :: Python :: 3.5
32+
Programming Language :: Python :: 3.6
33+
Programming Language :: Python :: 3.7
34+
Programming Language :: Python :: 3.8
35+
36+
[options]
37+
python_requires = >=3.5
38+
package_dir=
39+
=src
40+
packages=find_namespace:
41+
install_requires =
42+
opentelemetry-api
43+
opentelemetry-sdk
44+
ddtrace
45+
46+
[options.packages.find]
47+
where = src
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
import os
15+
16+
import setuptools
17+
18+
BASE_DIR = os.path.dirname(__file__)
19+
VERSION_FILENAME = os.path.join(
20+
BASE_DIR, "src", "opentelemetry", "ext", "datadog", "version.py"
21+
)
22+
PACKAGE_INFO = {}
23+
with open(VERSION_FILENAME) as f:
24+
exec(f.read(), PACKAGE_INFO)
25+
26+
setuptools.setup(version=PACKAGE_INFO["__version__"])
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import logging
2+
import os
3+
from urllib.parse import urlparse
4+
5+
from ddtrace.encoding import MsgpackEncoder
6+
from ddtrace.internal.writer import AgentWriter
7+
from ddtrace.span import Span as DatadogSpan
8+
9+
import opentelemetry.trace as trace_api
10+
from opentelemetry.sdk.trace import Span
11+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
12+
from opentelemetry.trace.status import StatusCanonicalCode
13+
14+
log = logging.getLogger(__name__)
15+
16+
17+
DEFAULT_AGENT_URL = os.environ.get(
18+
"DD_TRACE_AGENT_URL", "http://localhost:8126"
19+
)
20+
21+
22+
class DatadogSpanExporter(SpanExporter):
23+
def __init__(
24+
self, agent_url=DEFAULT_AGENT_URL,
25+
):
26+
self.agent_url = agent_url
27+
self._agent_writer = None
28+
29+
@property
30+
def agent_writer(self):
31+
if self._agent_writer is None:
32+
url_parsed = urlparse(self.agent_url)
33+
if url_parsed.scheme in ("http", "https"):
34+
self._agent_writer = AgentWriter(
35+
hostname=url_parsed.hostname,
36+
port=url_parsed.port,
37+
https=url_parsed.scheme == "https",
38+
)
39+
elif url_parsed.scheme == "unix":
40+
self._agent_writer = AgentWriter(uds_path=url_parsed.path)
41+
else:
42+
raise ValueError(
43+
"Unknown scheme `%s` for agent URL" % url_parsed.scheme
44+
)
45+
return self._agent_writer
46+
47+
def export(self, spans):
48+
datadog_spans = _translate_to_datadog(spans)
49+
50+
self.agent_writer.write(spans=datadog_spans)
51+
52+
return SpanExportResult.SUCCESS
53+
54+
def shutdown(self):
55+
if self.agent_writer.started:
56+
self.agent_writer.stop()
57+
self.agent_writer.join(self.agent_writer.exit_timeout)
58+
59+
60+
def _translate_to_datadog(spans):
61+
datadog_spans = []
62+
63+
for span in spans:
64+
trace_id, parent_id, span_id = _get_trace_ids(span)
65+
66+
span_name = span.name
67+
68+
# TODO: Handle span service and resource
69+
service = _get_service_name(span)
70+
resource = _get_resource_name(span)
71+
72+
# TODO: Add span type
73+
_ = _get_span_type(span)
74+
75+
start_ns = span.start_time
76+
duration_ns = span.end_time - span.start_time
77+
78+
error = (
79+
1
80+
if span.status.canonical_code is not StatusCanonicalCode.OK
81+
else 0
82+
)
83+
84+
# TODO: Add span tags
85+
# TODO: Add exception info
86+
87+
# datadog Span is initialized with a reference to the tracer which is
88+
# used to record the span when it is finished. We can skip ignore this
89+
# because we are not calling the finish method and explictly set the
90+
# duration.
91+
tracer = None
92+
93+
datadog_span = DatadogSpan(
94+
tracer,
95+
span_name,
96+
service=service,
97+
resource=resource,
98+
trace_id=trace_id,
99+
span_id=span_id,
100+
parent_id=parent_id,
101+
)
102+
datadog_span.start_ns = start_ns
103+
datadog_span.error = error
104+
datadog_span.duration_ns = duration_ns
105+
datadog_spans.append(datadog_span)
106+
107+
return datadog_spans
108+
109+
110+
def _get_trace_ids(span):
111+
ctx = span.get_context()
112+
trace_id = ctx.trace_id
113+
span_id = ctx.span_id
114+
115+
if isinstance(span.parent, trace_api.Span):
116+
parent_id = span.parent.get_context().span_id
117+
elif isinstance(span.parent, trace_api.SpanContext):
118+
parent_id = span.parent.span_id
119+
else:
120+
parent_id = 0
121+
122+
trace_id = _convert_trace_id_uint64(trace_id)
123+
124+
return trace_id, parent_id, span_id
125+
126+
127+
def _convert_trace_id_uint64(otel_id):
128+
raw = otel_id.to_bytes(16, "big")
129+
return int.from_bytes(raw[8:], byteorder="big")
130+
131+
132+
def _get_service_name(span):
133+
return (
134+
span.resource.labels.get("service.name")
135+
if getattr(span, "resource") is not None
136+
else "service"
137+
)
138+
139+
140+
def _get_resource_name(span):
141+
return (
142+
span.attributes.get("resource.name")
143+
if span.attributes.get("resource.name")
144+
else span.name
145+
)
146+
147+
148+
def _get_span_type(span):
149+
return (
150+
span.attributes.get("span.type")
151+
if span.attributes.get("span.type")
152+
else span.name
153+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2019, OpenCensus Authors
2+
# Copyright The OpenTelemetry Authors
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
__version__ = "0.7.dev0"

ext/opentelemetry-ext-datadog/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)