Skip to content

Commit 8e820e8

Browse files
committed
Implemented Opentracing bridge (#388)
This implements a functional, albeit still limited OpenTracing bridge. See Caveats section in the docs for a list of limitations. closes #388 closes #249 commit a1e9582 Author: Benjamin Wohlwend <[email protected]> Date: Mon Jan 21 13:31:00 2019 +0100 rename context to slightly less overloaded execution_context commit b6e5a27 Author: Benjamin Wohlwend <[email protected]> Date: Mon Jan 21 12:09:52 2019 +0100 make the internal context API a bit more explicit
1 parent 6c7cb72 commit 8e820e8

File tree

26 files changed

+680
-79
lines changed

26 files changed

+680
-79
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[Check the diff](https://github.com/elastic/apm-agent-python/compare/v4.0.3...master)
55

66
* Added support for collecting system and process metrics (#361)
7+
* Added an OpenTracing bridge (#388)
78
* Added `transaction.sampled` to errors (#371)
89
* Added `transaction.type` to errors (#391)
910
* Added parsing of `/proc/self/cgroup` to capture container meta data (#352)

docs/index.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ include::./flask.asciidoc[Flask support]
1919

2020
include::./metrics.asciidoc[Metrics]
2121

22+
include::./opentracing.asciidoc[OpenTracing]
23+
2224
include::./advanced-topics.asciidoc[Advanced Topics]
2325

2426
include::./api.asciidoc[API documentation]

docs/opentracing.asciidoc

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
ifdef::env-github[]
2+
NOTE: For the best reading experience,
3+
please view this documentation at https://www.elastic.co/guide/en/apm/agent/java[elastic.co]
4+
endif::[]
5+
6+
[[opentracing-bridge]]
7+
== Elastic APM OpenTracing bridge
8+
9+
The Elastic APM OpenTracing bridge allows to create Elastic APM `Transactions` and `Spans`,
10+
using the OpenTracing API.
11+
In other words,
12+
it translates the calls to the OpenTracing API to Elastic APM and thus allows for reusing existing instrumentation.
13+
14+
The first span of a service will be converted to an Elastic APM
15+
{apm-overview-ref-v}/transactions.html[`Transaction`],
16+
subsequent spans are mapped to Elastic APM
17+
{apm-overview-ref-v}/transaction-spans.html[`Span`].
18+
19+
[float]
20+
[[opentracing-getting-started]]
21+
=== Getting started
22+
The first step in getting started with the OpenTracing API bridge is to install the `opentracing` library:
23+
24+
[source,bash]
25+
----
26+
pip install elastic-apm[opentracing]
27+
----
28+
29+
Or if you already have installed `elastic-apm`
30+
31+
32+
[source,bash]
33+
----
34+
pip install opentracing>=2.0.0
35+
----
36+
37+
38+
[float]
39+
[[opentracing-init-tracer]]
40+
=== Initialize tracer
41+
42+
[source,python]
43+
----
44+
from elasticapm.contrib.opentracing import Tracer
45+
46+
tracer = Tracer();
47+
----
48+
49+
`Tracer` accepts the following optional arguments:
50+
51+
* `client_instance`: an already instantiated Elastic APM client
52+
* `config`: a configuration dictionary, which will be used to instantiate a new Elastic APM client,
53+
e.g. `{"SERVER_URL": "https://example.org"}. See <<configuration, configuration>> for more information.
54+
* `scope_manager`: a scope manager instance. Defaults to `ThreadLocalScopeManager` (see
55+
56+
57+
[float]
58+
[[opentracing-elastic-apm-tags]]
59+
=== Elastic APM specific tags
60+
61+
Elastic APM defines some tags which are not included in the OpenTracing API but are relevant in the context of Elastic APM.
62+
63+
- `type` - sets the type of the transaction,
64+
for example `request`, `ext` or `db`
65+
- `user.id` - sets the user id,
66+
appears in the "User" tab in the transaction details in the Elastic APM UI
67+
- `user.email` - sets the user email,
68+
appears in the "User" tab in the transaction details in the Elastic APM UI
69+
- `user.username` - sets the user name,
70+
appears in the "User" tab in the transaction details in the Elastic APM UI
71+
- `result` - sets the result of the transaction. Overrides the default value of `success`.
72+
73+
NOTE: these tags need to be set on the first span of an operation (e.g. an incoming request of your webapp).
74+
75+
[float]
76+
[[opentracing-caveats]]
77+
=== Caveats
78+
Not all features of the OpenTracing API are supported.
79+
80+
[float]
81+
[[opentracing-scope-managers]]
82+
==== Scope Managers
83+
Currently, only the `ThreadLocalScopeManager` is supported.
84+
Using other scope managers will lead to unpredictable and possibly app-breaking behaviour.
85+
86+
[float]
87+
[[opentracing-instrumentation]]
88+
==== Instrumentation
89+
90+
It is recommended to not use the built-in instrumentations of Elastic APM together with third-party OpenTracing instrumentations
91+
like https://pypi.org/project/opentracing_instrumentation/[opentracing_instrumentation] in the same service.
92+
If you would like to use such instrumentations, we recommend to disable the built-in instrumentation using the <<config-instrument,`instrument`>> config option.
93+
94+
[float]
95+
[[opentracing-propagation]]
96+
==== Context propagation
97+
This bridge only supports the formats `Format.Builtin.TEXT_MAP` and `Format.Builtin.HTTP_HEADERS`.
98+
`Format.Builtin.BINARY` is currently not supported.
99+
100+
[float]
101+
[[opentracing-references]]
102+
==== Span References
103+
Currently, this bridge only supports `child_of` references.
104+
Other references,
105+
like `follows_from` are not supported yet.
106+
107+
[float]
108+
[[opentracing-baggage]]
109+
==== Baggage
110+
The `Span.set_baggage_item(key, value)` method is not supported.
111+
Baggage items are silently dropped.
112+
113+
[float]
114+
[[opentracing-logs]]
115+
==== Logs
116+
Only exception logging is supported.
117+
Logging an Exception on the OpenTracing span will create an Elastic APM
118+
{apm-overview-ref-v}/errors.html[`Error`].
119+
Example:
120+
121+
[source,python]
122+
----
123+
with tracer.start_active_span("transaction") as tx_scope:
124+
try:
125+
raise ValueError("oops")
126+
except ValueError:
127+
exc_type, exc_val, exc_tb = sys.exc_info()[:3]
128+
tx_scope.span.log_kv({
129+
"python.exception.type": exc_type,
130+
"python.exception.val": exc_val,
131+
"python.exception.tb": exc_tb
132+
})
133+
----
134+

elasticapm/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from elasticapm.conf import Config, constants
2626
from elasticapm.conf.constants import ERROR
2727
from elasticapm.metrics.base_metrics import MetricsRegistry
28-
from elasticapm.traces import Tracer, get_transaction
28+
from elasticapm.traces import Tracer, execution_context
2929
from elasticapm.utils import cgroup, compat, is_master_process, stacks, varmap
3030
from elasticapm.utils.encoding import keyword_field, shorten, transform
3131
from elasticapm.utils.module_import import import_string
@@ -292,7 +292,7 @@ def _build_msg_for_logging(
292292
"""
293293
Captures, processes and serializes an event into a dict object
294294
"""
295-
transaction = get_transaction()
295+
transaction = execution_context.get_transaction()
296296
if transaction:
297297
transaction_context = deepcopy(transaction.context)
298298
else:

elasticapm/context/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class BaseContext(object):
2+
def set_transaction(self, transaction):
3+
raise NotImplementedError
4+
5+
def get_transaction(self, clear=False):
6+
raise NotImplementedError
7+
8+
def set_span(self, span):
9+
raise NotImplementedError
10+
11+
def get_span(self):
12+
raise NotImplementedError

elasticapm/context/contextvars.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
from __future__ import absolute_import
22

33
import contextvars
4+
from elasticapm.context.base import BaseContext
45

5-
elasticapm_transaction_var = contextvars.ContextVar("elasticapm_transaction_var")
6-
elasticapm_span_var = contextvars.ContextVar("elasticapm_span_var")
76

7+
class ContextVarsContext(BaseContext):
8+
elasticapm_transaction_var = contextvars.ContextVar("elasticapm_transaction_var")
9+
elasticapm_span_var = contextvars.ContextVar("elasticapm_span_var")
810

9-
def get_transaction(clear=False):
10-
try:
11-
transaction = elasticapm_transaction_var.get()
12-
if clear:
13-
set_transaction(None)
14-
return transaction
15-
except LookupError:
16-
return None
11+
def get_transaction(self, clear=False):
12+
try:
13+
transaction = self.elasticapm_transaction_var.get()
14+
if clear:
15+
self.set_transaction(None)
16+
return transaction
17+
except LookupError:
18+
return None
1719

20+
def set_transaction(self, transaction):
21+
self.elasticapm_transaction_var.set(transaction)
1822

19-
def set_transaction(transaction):
20-
elasticapm_transaction_var.set(transaction)
23+
def get_span(self):
24+
try:
25+
return self.elasticapm_span_var.get()
26+
except LookupError:
27+
return None
2128

29+
def set_span(self, span):
30+
self.elasticapm_span_var.set(span)
2231

23-
def get_span():
24-
try:
25-
return elasticapm_span_var.get()
26-
except LookupError:
27-
return None
2832

29-
30-
def set_span(span):
31-
elasticapm_span_var.set(span)
33+
execution_context = ContextVarsContext()

elasticapm/context/threadlocal.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
11
import threading
22

3-
thread_local = threading.local()
4-
thread_local.transaction = None
5-
elasticapm_span_var = None
3+
from elasticapm.context.base import BaseContext
64

75

8-
def get_transaction(clear=False):
9-
"""
10-
Get the transaction registered for the current thread.
6+
class ThreadLocalContext(BaseContext):
7+
thread_local = threading.local()
8+
thread_local.transaction = None
9+
thread_local.span = None
1110

12-
:return:
13-
:rtype: Transaction
14-
"""
15-
transaction = getattr(thread_local, "transaction", None)
16-
if clear:
17-
thread_local.transaction = None
18-
return transaction
11+
def get_transaction(self, clear=False):
12+
"""
13+
Get the transaction registered for the current thread.
1914
15+
:return:
16+
:rtype: Transaction
17+
"""
18+
transaction = getattr(self.thread_local, "transaction", None)
19+
if clear:
20+
self.thread_local.transaction = None
21+
return transaction
2022

21-
def set_transaction(transaction):
22-
thread_local.transaction = transaction
23+
def set_transaction(self, transaction):
24+
self.thread_local.transaction = transaction
2325

26+
def get_span(self):
27+
return getattr(self.thread_local, "span", None)
2428

25-
def get_span():
26-
return getattr(thread_local, "span", None)
29+
def set_span(self, span):
30+
self.thread_local.span = span
2731

2832

29-
def set_span(span):
30-
thread_local.span = span
33+
execution_context = ThreadLocalContext()

elasticapm/contrib/django/context_processors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from elasticapm.traces import get_transaction
1+
from elasticapm.traces import execution_context
22

33

44
def rum_tracing(request):
5-
transaction = get_transaction()
5+
transaction = execution_context.get_transaction()
66
if transaction and transaction.trace_parent:
77
return {
88
"apm": {

elasticapm/contrib/flask/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from elasticapm.conf import constants, setup_logging
2323
from elasticapm.contrib.flask.utils import get_data_from_request, get_data_from_response
2424
from elasticapm.handlers.logging import LoggingHandler
25-
from elasticapm.traces import get_transaction
25+
from elasticapm.traces import execution_context
2626
from elasticapm.utils import build_name_with_http_method_prefix
2727
from elasticapm.utils.disttracing import TraceParent
2828

@@ -144,7 +144,7 @@ def rum_tracing():
144144
"""
145145
Adds APM related IDs to the context used for correlating the backend transaction with the RUM transaction
146146
"""
147-
transaction = get_transaction()
147+
transaction = execution_context.get_transaction()
148148
if transaction and transaction.trace_parent:
149149
return {
150150
"apm": {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .span import OTSpan # noqa: F401
2+
from .tracer import Tracer # noqa: F401

0 commit comments

Comments
 (0)