Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci/.matrix_framework.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ FRAMEWORK:
- flask-3.0
- jinja2-3
- opentelemetry-newest
- opentracing-newest
- twisted-newest
- celery-5-flask-2
- celery-5-django-4
Expand Down
1 change: 1 addition & 0 deletions .ci/.matrix_framework_fips.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ FRAMEWORK:
- flask-3.0
- jinja2-3
- opentelemetry-newest
- opentracing-newest
- twisted-newest
- celery-5-flask-2
- celery-5-django-5
Expand Down
2 changes: 2 additions & 0 deletions .ci/.matrix_framework_full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ FRAMEWORK:
- celery-5-django-4
- celery-5-django-5
- opentelemetry-newest
- opentracing-newest
- opentracing-2.0
- twisted-newest
- twisted-18
- twisted-17
Expand Down
43 changes: 43 additions & 0 deletions elasticapm/contrib/opentracing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# BSD 3-Clause License
#
# Copyright (c) 2019, Elasticsearch BV
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import warnings

from .span import OTSpan # noqa: F401
from .tracer import Tracer # noqa: F401

warnings.warn(
(
"The OpenTracing bridge is deprecated and will be removed in the next major release. "
"Please migrate to the OpenTelemetry bridge."
),
DeprecationWarning,
)
136 changes: 136 additions & 0 deletions elasticapm/contrib/opentracing/span.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# BSD 3-Clause License
#
# Copyright (c) 2019, Elasticsearch BV
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from opentracing.span import Span as OTSpanBase
from opentracing.span import SpanContext as OTSpanContextBase

from elasticapm import traces
from elasticapm.utils import get_url_dict
from elasticapm.utils.logging import get_logger

try:
# opentracing-python 2.1+
from opentracing import logs as ot_logs
from opentracing import tags
except ImportError:
# opentracing-python <2.1
from opentracing.ext import tags

ot_logs = None


logger = get_logger("elasticapm.contrib.opentracing")


class OTSpan(OTSpanBase):
def __init__(self, tracer, context, elastic_apm_ref) -> None:
super(OTSpan, self).__init__(tracer, context)
self.elastic_apm_ref = elastic_apm_ref
self.is_transaction = isinstance(elastic_apm_ref, traces.Transaction)
self.is_dropped = isinstance(elastic_apm_ref, traces.DroppedSpan)
if not context.span:
context.span = self

def log_kv(self, key_values, timestamp=None):
exc_type, exc_val, exc_tb = None, None, None
if "python.exception.type" in key_values:
exc_type = key_values["python.exception.type"]
exc_val = key_values.get("python.exception.val")
exc_tb = key_values.get("python.exception.tb")
elif ot_logs and key_values.get(ot_logs.EVENT) == tags.ERROR:
exc_type = key_values[ot_logs.ERROR_KIND]
exc_val = key_values.get(ot_logs.ERROR_OBJECT)
exc_tb = key_values.get(ot_logs.STACK)
else:
logger.debug("Can't handle non-exception type opentracing logs")
if exc_type:
agent = self.tracer._agent
agent.capture_exception(exc_info=(exc_type, exc_val, exc_tb))
return self

def set_operation_name(self, operation_name):
self.elastic_apm_ref.name = operation_name
return self

def set_tag(self, key, value):
if self.is_transaction:
if key == "type":
self.elastic_apm_ref.transaction_type = value
elif key == "result":
self.elastic_apm_ref.result = value
elif key == tags.HTTP_STATUS_CODE:
self.elastic_apm_ref.result = "HTTP {}xx".format(str(value)[0])
traces.set_context({"status_code": value}, "response")
elif key == "user.id":
traces.set_user_context(user_id=value)
elif key == "user.username":
traces.set_user_context(username=value)
elif key == "user.email":
traces.set_user_context(email=value)
elif key == tags.HTTP_URL:
traces.set_context({"url": get_url_dict(value)}, "request")
elif key == tags.HTTP_METHOD:
traces.set_context({"method": value}, "request")
elif key == tags.COMPONENT:
traces.set_context({"framework": {"name": value}}, "service")
else:
self.elastic_apm_ref.label(**{key: value})
elif not self.is_dropped:
if key.startswith("db."):
span_context = self.elastic_apm_ref.context or {}
if "db" not in span_context:
span_context["db"] = {}
if key == tags.DATABASE_STATEMENT:
span_context["db"]["statement"] = value
elif key == tags.DATABASE_USER:
span_context["db"]["user"] = value
elif key == tags.DATABASE_TYPE:
span_context["db"]["type"] = value
self.elastic_apm_ref.type = "db." + value
else:
self.elastic_apm_ref.label(**{key: value})
self.elastic_apm_ref.context = span_context
elif key == tags.SPAN_KIND:
self.elastic_apm_ref.type = value
else:
self.elastic_apm_ref.label(**{key: value})
return self

def finish(self, finish_time=None) -> None:
if self.is_transaction:
self.tracer._agent.end_transaction()
elif not self.is_dropped:
self.elastic_apm_ref.transaction.end_span()


class OTSpanContext(OTSpanContextBase):
def __init__(self, trace_parent, span=None) -> None:
self.trace_parent = trace_parent
self.span = span
131 changes: 131 additions & 0 deletions elasticapm/contrib/opentracing/tracer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# BSD 3-Clause License
#
# Copyright (c) 2019, Elasticsearch BV
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import warnings

from opentracing import Format, InvalidCarrierException, SpanContextCorruptedException, UnsupportedFormatException
from opentracing.scope_managers import ThreadLocalScopeManager
from opentracing.tracer import ReferenceType
from opentracing.tracer import Tracer as TracerBase

import elasticapm
from elasticapm import get_client, instrument, traces
from elasticapm.conf import constants
from elasticapm.contrib.opentracing.span import OTSpan, OTSpanContext
from elasticapm.utils import disttracing


class Tracer(TracerBase):
def __init__(self, client_instance=None, config=None, scope_manager=None) -> None:
self._agent = client_instance or get_client() or elasticapm.Client(config=config)
if scope_manager and not isinstance(scope_manager, ThreadLocalScopeManager):
warnings.warn(
"Currently, the Elastic APM opentracing bridge only supports the ThreadLocalScopeManager. "
"Usage of other scope managers will lead to unpredictable results."
)
self._scope_manager = scope_manager or ThreadLocalScopeManager()
if self._agent.config.instrument and self._agent.config.enabled:
instrument()

def start_active_span(
self,
operation_name,
child_of=None,
references=None,
tags=None,
start_time=None,
ignore_active_span=False,
finish_on_close=True,
):
ot_span = self.start_span(
operation_name,
child_of=child_of,
references=references,
tags=tags,
start_time=start_time,
ignore_active_span=ignore_active_span,
)
scope = self._scope_manager.activate(ot_span, finish_on_close)
return scope

def start_span(
self, operation_name=None, child_of=None, references=None, tags=None, start_time=None, ignore_active_span=False
):
if isinstance(child_of, OTSpanContext):
parent_context = child_of
elif isinstance(child_of, OTSpan):
parent_context = child_of.context
elif references and references[0].type == ReferenceType.CHILD_OF:
parent_context = references[0].referenced_context
else:
parent_context = None
transaction = traces.execution_context.get_transaction()
if not transaction:
trace_parent = parent_context.trace_parent if parent_context else None
transaction = self._agent.begin_transaction("custom", trace_parent=trace_parent)
transaction.name = operation_name
span_context = OTSpanContext(trace_parent=transaction.trace_parent)
ot_span = OTSpan(self, span_context, transaction)
else:
# to allow setting an explicit parent span, we check if the parent_context is set
# and if it is a span. In all other cases, the parent is found implicitly through the
# execution context.
parent_span_id = (
parent_context.span.elastic_apm_ref.id
if parent_context and parent_context.span and not parent_context.span.is_transaction
else None
)
span = transaction._begin_span(operation_name, None, parent_span_id=parent_span_id)
trace_parent = parent_context.trace_parent if parent_context else transaction.trace_parent
span_context = OTSpanContext(trace_parent=trace_parent.copy_from(span_id=span.id))
ot_span = OTSpan(self, span_context, span)
if tags:
for k, v in tags.items():
ot_span.set_tag(k, v)
return ot_span

def extract(self, format, carrier):
if format in (Format.HTTP_HEADERS, Format.TEXT_MAP):
trace_parent = disttracing.TraceParent.from_headers(carrier)
if not trace_parent:
raise SpanContextCorruptedException("could not extract span context from carrier")
return OTSpanContext(trace_parent=trace_parent)
raise UnsupportedFormatException

def inject(self, span_context, format, carrier):
if format in (Format.HTTP_HEADERS, Format.TEXT_MAP):
if not isinstance(carrier, dict):
raise InvalidCarrierException("carrier for {} format should be dict-like".format(format))
val = span_context.trace_parent.to_ascii()
carrier[constants.TRACEPARENT_HEADER_NAME] = val
if self._agent.config.use_elastic_traceparent_header:
carrier[constants.TRACEPARENT_LEGACY_HEADER_NAME] = val
return
raise UnsupportedFormatException
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ tornado =
tornado
starlette =
starlette
opentracing =
opentracing>=2.0.0
sanic =
sanic
opentelemetry =
Expand All @@ -79,6 +81,7 @@ markers =
gevent
eventlet
celery
opentracing
cassandra
psycopg2
mongodb
Expand Down
29 changes: 29 additions & 0 deletions tests/contrib/opentracing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# BSD 3-Clause License
#
# Copyright (c) 2019, Elasticsearch BV
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Loading
Loading