Skip to content

Commit

Permalink
feat: Add exporter to datadog
Browse files Browse the repository at this point in the history
  • Loading branch information
majorgreys committed Apr 14, 2020
1 parent 7c2ceba commit e3a3619
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 0 deletions.
36 changes: 36 additions & 0 deletions docs/examples/datadog_exporter/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Datadog Exporter Example
=======================

This example shows how to use OpenTelemetry to send tracing data to Datadog.

Installation
------------

.. code-block:: sh
pip install opentelemetry-api
pip install opentelemetry-sdk
pip install opentelemetry-ext-datadog
Run the Example
---------------

* Start Datadog Agent

.. code-block:: sh
docker run -d -v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /proc/:/host/proc/:ro \
-v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \
-p 127.0.0.1:8126:8126/tcp \
-e DD_API_KEY="<DATADOG_API_KEY>" \
-e DD_APM_ENABLED=true \
datadog/agent:latest
* Run the example

.. code-block:: sh
python datadog_exporter.py
The traces will be available at http://datadoghq.com/.
34 changes: 34 additions & 0 deletions docs/examples/datadog_exporter/datadog_exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
#
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os

from opentelemetry import trace
from opentelemetry.ext.datadog import DatadogSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchExportSpanProcessor

trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)

exporter = DatadogSpanExporter(agent_url="http://localhost:8126")

span_processor = BatchExportSpanProcessor(exporter)
trace.get_tracer_provider().add_span_processor(span_processor)

with tracer.start_as_current_span("foo"):
with tracer.start_as_current_span("bar"):
with tracer.start_as_current_span("baz"):
print("Hello world from OpenTelemetry Python!")
22 changes: 22 additions & 0 deletions ext/opentelemetry-ext-datadog/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
OpenTelemetry Datadog Exporter
==============================

This library allows to export tracing data to `Datadog <https://www.datadoghq.com/>`_.

Installation
------------

.. code-block:: sh
pip install opentelemetry-ext-datadog
.. _Datadog: https://www.datadoghq.com/
.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/


References
----------

* `Datadog <https://www.datadoghq.com/>`_
* `OpenTelemetry Project <https://opentelemetry.io/>`_
47 changes: 47 additions & 0 deletions ext/opentelemetry-ext-datadog/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
[metadata]
name = opentelemetry-ext-datadog
description = Datadog Exporter for OpenTelemetry
long_description = file: README.rst
long_description_content_type = text/x-rst
author = OpenTelemetry Authors
author_email = cncf-opentelemetry-contributors@lists.cncf.io
url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-datadog
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 4 - Beta
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8

[options]
python_requires = >=3.5
package_dir=
=src
packages=find_namespace:
install_requires =
opentelemetry-api
opentelemetry-sdk
ddtrace

[options.packages.find]
where = src
26 changes: 26 additions & 0 deletions ext/opentelemetry-ext-datadog/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os

import setuptools

BASE_DIR = os.path.dirname(__file__)
VERSION_FILENAME = os.path.join(
BASE_DIR, "src", "opentelemetry", "ext", "datadog", "version.py"
)
PACKAGE_INFO = {}
with open(VERSION_FILENAME) as f:
exec(f.read(), PACKAGE_INFO)

setuptools.setup(version=PACKAGE_INFO["__version__"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import logging
import os
from urllib.parse import urlparse

from ddtrace.encoding import MsgpackEncoder
from ddtrace.internal.writer import AgentWriter
from ddtrace.span import Span as DatadogSpan

import opentelemetry.trace as trace_api
from opentelemetry.sdk.trace import Span
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
from opentelemetry.trace.status import StatusCanonicalCode

log = logging.getLogger(__name__)


DEFAULT_AGENT_URL = os.environ.get(
"DD_TRACE_AGENT_URL", "http://localhost:8126"
)


class DatadogSpanExporter(SpanExporter):
def __init__(
self, agent_url=DEFAULT_AGENT_URL,
):
self.agent_url = agent_url
self._agent_writer = None

@property
def agent_writer(self):
if self._agent_writer is None:
url_parsed = urlparse(self.agent_url)
if url_parsed.scheme in ("http", "https"):
self._agent_writer = AgentWriter(
hostname=url_parsed.hostname,
port=url_parsed.port,
https=url_parsed.scheme == "https",
)
elif url_parsed.scheme == "unix":
self._agent_writer = AgentWriter(uds_path=url_parsed.path)
else:
raise ValueError(
"Unknown scheme `%s` for agent URL" % url_parsed.scheme
)
return self._agent_writer

def export(self, spans):
datadog_spans = _translate_to_datadog(spans)

self.agent_writer.write(spans=datadog_spans)

return SpanExportResult.SUCCESS

def shutdown(self):
if self.agent_writer.started:
self.agent_writer.stop()
self.agent_writer.join(self.agent_writer.exit_timeout)


def _translate_to_datadog(spans):
datadog_spans = []

for span in spans:
trace_id, parent_id, span_id = _get_trace_ids(span)

span_name = span.name

# TODO: Handle span service and resource
service = _get_service_name(span)
resource = _get_resource_name(span)

# TODO: Add span type
_ = _get_span_type(span)

start_ns = span.start_time
duration_ns = span.end_time - span.start_time

error = (
1
if span.status.canonical_code is not StatusCanonicalCode.OK
else 0
)

# TODO: Add span tags
# TODO: Add exception info

# datadog Span is initialized with a reference to the tracer which is
# used to record the span when it is finished. We can skip ignore this
# because we are not calling the finish method and explictly set the
# duration.
tracer = None

datadog_span = DatadogSpan(
tracer,
span_name,
service=service,
resource=resource,
trace_id=trace_id,
span_id=span_id,
parent_id=parent_id,
)
datadog_span.start_ns = start_ns
datadog_span.error = error
datadog_span.duration_ns = duration_ns
datadog_spans.append(datadog_span)

return datadog_spans


def _get_trace_ids(span):
ctx = span.get_context()
trace_id = ctx.trace_id
span_id = ctx.span_id

if isinstance(span.parent, trace_api.Span):
parent_id = span.parent.get_context().span_id
elif isinstance(span.parent, trace_api.SpanContext):
parent_id = span.parent.span_id
else:
parent_id = 0

trace_id = _convert_trace_id_uint64(trace_id)

return trace_id, parent_id, span_id


def _convert_trace_id_uint64(otel_id):
raw = otel_id.to_bytes(16, "big")
return int.from_bytes(raw[8:], byteorder="big")


def _get_service_name(span):
return (
span.resource.labels.get("service.name")
if getattr(span, "resource") is not None
else "service"
)


def _get_resource_name(span):
return (
span.attributes.get("resource.name")
if span.attributes.get("resource.name")
else span.name
)


def _get_span_type(span):
return (
span.attributes.get("span.type")
if span.attributes.get("span.type")
else span.name
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2019, OpenCensus Authors
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = "0.7.dev0"
Empty file.
Loading

0 comments on commit e3a3619

Please sign in to comment.