Skip to content

Opentracing bridge #388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
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
2 changes: 2 additions & 0 deletions docs/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ include::./flask.asciidoc[Flask support]

include::./metrics.asciidoc[Metrics]

include::./opentracing.asciidoc[OpenTracing]

include::./advanced-topics.asciidoc[Advanced Topics]

include::./api.asciidoc[API documentation]
Expand Down
134 changes: 134 additions & 0 deletions docs/opentracing.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
ifdef::env-github[]
NOTE: For the best reading experience,
please view this documentation at https://www.elastic.co/guide/en/apm/agent/java[elastic.co]
endif::[]

[[opentracing-bridge]]
== Elastic APM OpenTracing bridge

The Elastic APM OpenTracing bridge allows to create Elastic APM `Transactions` and `Spans`,
using the OpenTracing API.
In other words,
it translates the calls to the OpenTracing API to Elastic APM and thus allows for reusing existing instrumentation.

The first span of a service will be converted to an Elastic APM
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first span of a service trace / operation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saying "trace" here might be misleading, as a trace represents the request through multiple services.

But I agree that it is not super clear what is meant here. I'd love if opentracing would define something like an entry span, but they do not.

{apm-overview-ref-v}/transactions.html[`Transaction`],
subsequent spans are mapped to Elastic APM
{apm-overview-ref-v}/transaction-spans.html[`Span`].

[float]
[[opentracing-getting-started]]
=== Getting started
The first step in getting started with the OpenTracing API bridge is to install the `opentracing` library:

[source,bash]
----
pip install elastic-apm[opentracing]
----

Or if you already have installed `elastic-apm`


[source,bash]
----
pip install opentracing>=2.0.0
----


[float]
[[opentracing-init-tracer]]
=== Initialize tracer

[source,python]
----
from elasticapm.contrib.opentracing import Tracer

tracer = Tracer();
----

`Tracer` accepts the following optional arguments:

* `client_instance`: an already instantiated Elastic APM client
* `config`: a configuration dictionary, which will be used to instantiate a new Elastic APM client,
e.g. `{"SERVER_URL": "https://example.org"}. See <<configuration, configuration>> for more information.
* `scope_manager`: a scope manager instance. Defaults to `ThreadLocalScopeManager` (see


[float]
[[opentracing-elastic-apm-tags]]
=== Elastic APM specific tags

Elastic APM defines some tags which are not included in the OpenTracing API but are relevant in the context of Elastic APM.

- `type` - sets the type of the transaction,
for example `request`, `ext` or `db`
- `user.id` - sets the user id,
appears in the "User" tab in the transaction details in the Elastic APM UI
- `user.email` - sets the user email,
appears in the "User" tab in the transaction details in the Elastic APM UI
- `user.username` - sets the user name,
appears in the "User" tab in the transaction details in the Elastic APM UI
- `result` - sets the result of the transaction. Overrides the default value of `success`.

NOTE: these tags need to be set on the first span of an operation (e.g. an incoming request of your webapp).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users still can set any arbitrary tags on spans, right? only that they will not be mapped to know fields. Ill rephrase L61 to something like: "Some tags are well defined in Elastic APM. If you defined these tags in the first span of an operation, the will be automatically mapped to defined fields..." and remove this Note


[float]
[[opentracing-caveats]]
=== Caveats
Not all features of the OpenTracing API are supported.

[float]
[[opentracing-scope-managers]]
==== Scope Managers
Currently, only the `ThreadLocalScopeManager` is supported.
Using other scope managers will lead to unpredictable and possibly app-breaking behaviour.

[float]
[[opentracing-instrumentation]]
==== Instrumentation

It is recommended to not use the built-in instrumentations of Elastic APM together with third-party OpenTracing instrumentations
like https://pypi.org/project/opentracing_instrumentation/[opentracing_instrumentation] in the same service.
If you would like to use such instrumentations, we recommend to disable the built-in instrumentation using the <<config-instrument,`instrument`>> config option.

[float]
[[opentracing-propagation]]
==== Context propagation
This bridge only supports the formats `Format.Builtin.TEXT_MAP` and `Format.Builtin.HTTP_HEADERS`.
`Format.Builtin.BINARY` is currently not supported.

[float]
[[opentracing-references]]
==== Span References
Currently, this bridge only supports `child_of` references.
Other references,
like `follows_from` are not supported yet.

[float]
[[opentracing-baggage]]
==== Baggage
The `Span.set_baggage_item(key, value)` method is not supported.
Baggage items are silently dropped.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how this exactly happens? i was expecting to have set_baggage_item method implemented as a noop, but i don't find it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We inherit the noop-method from the super class

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use a test, for some more explicitness 🙈

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4ca8859 (plus another few tests plus a fix for an issue I found thanks to those tests. "write more tests" is the lesson here I guess)


[float]
[[opentracing-logs]]
==== Logs
Only exception logging is supported.
Logging an Exception on the OpenTracing span will create an Elastic APM
{apm-overview-ref-v}/errors.html[`Error`].
Example:

[source,python]
----
with tracer.start_active_span("transaction") as tx_scope:
try:
raise ValueError("oops")
except ValueError:
exc_type, exc_val, exc_tb = sys.exc_info()[:3]
tx_scope.span.log_kv({
"python.exception.type": exc_type,
"python.exception.val": exc_val,
"python.exception.tb": exc_tb
})
----

4 changes: 2 additions & 2 deletions elasticapm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from elasticapm.conf import Config, constants
from elasticapm.conf.constants import ERROR
from elasticapm.metrics.base_metrics import MetricsRegistry
from elasticapm.traces import Tracer, get_transaction
from elasticapm.traces import Tracer, execution_context
from elasticapm.utils import cgroup, compat, is_master_process, stacks, varmap
from elasticapm.utils.encoding import keyword_field, shorten, transform
from elasticapm.utils.module_import import import_string
Expand Down Expand Up @@ -292,7 +292,7 @@ def _build_msg_for_logging(
"""
Captures, processes and serializes an event into a dict object
"""
transaction = get_transaction()
transaction = execution_context.get_transaction()
if transaction:
transaction_context = deepcopy(transaction.context)
else:
Expand Down
12 changes: 12 additions & 0 deletions elasticapm/context/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class BaseContext(object):
def set_transaction(self, transaction):
raise NotImplementedError

def get_transaction(self, clear=False):
raise NotImplementedError

def set_span(self, span):
raise NotImplementedError

def get_span(self):
raise NotImplementedError
42 changes: 22 additions & 20 deletions elasticapm/context/contextvars.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
from __future__ import absolute_import

import contextvars
from elasticapm.context.base import BaseContext

elasticapm_transaction_var = contextvars.ContextVar("elasticapm_transaction_var")
elasticapm_span_var = contextvars.ContextVar("elasticapm_span_var")

class ContextVarsContext(BaseContext):
elasticapm_transaction_var = contextvars.ContextVar("elasticapm_transaction_var")
elasticapm_span_var = contextvars.ContextVar("elasticapm_span_var")

def get_transaction(clear=False):
try:
transaction = elasticapm_transaction_var.get()
if clear:
set_transaction(None)
return transaction
except LookupError:
return None
def get_transaction(self, clear=False):
try:
transaction = self.elasticapm_transaction_var.get()
if clear:
self.set_transaction(None)
return transaction
except LookupError:
return None

def set_transaction(self, transaction):
self.elasticapm_transaction_var.set(transaction)

def set_transaction(transaction):
elasticapm_transaction_var.set(transaction)
def get_span(self):
try:
return self.elasticapm_span_var.get()
except LookupError:
return None

def set_span(self, span):
self.elasticapm_span_var.set(span)

def get_span():
try:
return elasticapm_span_var.get()
except LookupError:
return None


def set_span(span):
elasticapm_span_var.set(span)
execution_context = ContextVarsContext()
41 changes: 22 additions & 19 deletions elasticapm/context/threadlocal.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import threading

thread_local = threading.local()
thread_local.transaction = None
elasticapm_span_var = None
from elasticapm.context.base import BaseContext


def get_transaction(clear=False):
"""
Get the transaction registered for the current thread.
class ThreadLocalContext(BaseContext):
thread_local = threading.local()
thread_local.transaction = None
thread_local.span = None

:return:
:rtype: Transaction
"""
transaction = getattr(thread_local, "transaction", None)
if clear:
thread_local.transaction = None
return transaction
def get_transaction(self, clear=False):
"""
Get the transaction registered for the current thread.

:return:
:rtype: Transaction
"""
transaction = getattr(self.thread_local, "transaction", None)
if clear:
self.thread_local.transaction = None
return transaction

def set_transaction(transaction):
thread_local.transaction = transaction
def set_transaction(self, transaction):
self.thread_local.transaction = transaction

def get_span(self):
return getattr(self.thread_local, "span", None)

def get_span():
return getattr(thread_local, "span", None)
def set_span(self, span):
self.thread_local.span = span


def set_span(span):
thread_local.span = span
execution_context = ThreadLocalContext()
4 changes: 2 additions & 2 deletions elasticapm/contrib/django/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from elasticapm.traces import get_transaction
from elasticapm.traces import execution_context


def rum_tracing(request):
transaction = get_transaction()
transaction = execution_context.get_transaction()
if transaction and transaction.trace_parent:
return {
"apm": {
Expand Down
4 changes: 2 additions & 2 deletions elasticapm/contrib/flask/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from elasticapm.conf import constants, setup_logging
from elasticapm.contrib.flask.utils import get_data_from_request, get_data_from_response
from elasticapm.handlers.logging import LoggingHandler
from elasticapm.traces import get_transaction
from elasticapm.traces import execution_context
from elasticapm.utils import build_name_with_http_method_prefix
from elasticapm.utils.disttracing import TraceParent

Expand Down Expand Up @@ -144,7 +144,7 @@ def rum_tracing():
"""
Adds APM related IDs to the context used for correlating the backend transaction with the RUM transaction
"""
transaction = get_transaction()
transaction = execution_context.get_transaction()
if transaction and transaction.trace_parent:
return {
"apm": {
Expand Down
2 changes: 2 additions & 0 deletions elasticapm/contrib/opentracing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .span import OTSpan # noqa: F401
from .tracer import Tracer # noqa: F401
Loading