Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
3ec54a9
Add class stubs for otel bridge
basepi Nov 19, 2021
2713b13
Switch execution_context to use stacks and store span extra data
basepi Dec 1, 2021
2e6b3d4
Add stub for span.py
basepi Dec 1, 2021
781dc51
Switch to tuples for execution_context
basepi Jan 13, 2022
e44e195
Switch span ending to unset_span instead of setting parent
basepi Jan 13, 2022
f7810b6
Update elasticapm/context/contextvars.py
basepi Jan 14, 2022
2bbc87c
Use `with_extra` when fetching spans
basepi Jan 18, 2022
9bbd996
Implement otel Span wrapper
basepi Jan 24, 2022
4da03fd
More Tracer work
basepi Jan 26, 2022
1db14bc
Transaction creation + utils
basepi Jan 27, 2022
00b2696
Handle transaction activation/deactivation
basepi Feb 2, 2022
2c43687
Add context api
basepi Feb 3, 2022
7776f67
Remove `extra` from execution_context
basepi Feb 3, 2022
11bb7f4
Simplify to make Hound happy
basepi Feb 3, 2022
649aac5
Handle SpanKind + otel attributes properly
basepi Feb 3, 2022
a0c08fe
Add transaction type inference
basepi Feb 7, 2022
477ae51
Add span type/subtype
basepi Feb 8, 2022
3b10883
Handle otel attributes for exceptions
basepi Feb 8, 2022
d62ce9a
Handle end_time for otel spans
basepi Feb 8, 2022
f99f6ac
Handle tracestate and traceflags
basepi Feb 8, 2022
b6a33fd
Handle set_status_on_exception
basepi Feb 8, 2022
55b65ae
Handle remote tracestate
basepi Feb 8, 2022
360e89b
Instrument on Tracer instantiation
basepi Feb 8, 2022
108361d
Merge remote-tracking branch 'upstream/main' into otel
basepi Feb 8, 2022
cbcd719
Add first test (and many fixes for it)
basepi Feb 15, 2022
b8b1f32
Remove compat
basepi Feb 15, 2022
4bb37d3
Fix other failing tests
basepi Feb 15, 2022
513278e
Add another test (plus more fixes)
basepi Feb 15, 2022
27d3fcf
Merge remote-tracking branch 'upstream/main' into otel
basepi Feb 16, 2022
2678177
Remove contextvars/threadlocal test for py3.6
basepi Feb 16, 2022
cd15f03
Don't make Tracer a singleton (should fix tests?)
basepi Feb 16, 2022
6031d8b
Add test for spans without auto-attach
basepi Feb 16, 2022
f2d2754
Rename otel_tests.py to tests.py
basepi Mar 1, 2022
9e9ea9c
Add more tests
basepi Mar 1, 2022
2e0c2ea
Docs + a couple of changes to more closely match opentracing bridge
basepi Mar 1, 2022
5fc8c0d
Merge remote-tracking branch 'upstream/main' into otel
basepi Mar 1, 2022
a6bbc37
Deprecate opentracing bridge
basepi Mar 2, 2022
bff1d31
More docs
basepi Mar 2, 2022
b29b2ce
Move opentelemetry to its own matrix
basepi Mar 2, 2022
bd74132
Merge remote-tracking branch 'upstream/main' into otel
basepi Mar 2, 2022
8ff7fcd
Merge branch 'main' into otel
basepi Mar 3, 2022
0d934a2
Review comments for context.py
basepi Mar 7, 2022
dfb6534
Reintroduce test_execution_context_backing
basepi Mar 7, 2022
e3e2fc3
Fix a typo
basepi Mar 7, 2022
b9ff8d6
Add note about extra get_client() check
basepi Mar 7, 2022
8343fc9
Use `end_time` correctly
basepi Mar 8, 2022
File filter

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/.jenkins_framework.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ FRAMEWORK:
- flask-1.1
- flask-2.0
- jinja2-3
- opentelemetry-newest
- opentracing-newest
- twisted-18
- celery-4-flask-1.0
Expand Down
1 change: 1 addition & 0 deletions .ci/.jenkins_framework_full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ FRAMEWORK:
- celery-5-flask-2
- celery-5-django-3
- celery-5-django-4
- opentelemetry-newest
- opentracing-newest
- opentracing-2.0
- twisted-18
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ endif::[]
//===== Bug fixes
//

=== Unreleased

// Unreleased changes go here
// When the next release happens, nest these changes under the "Python Agent version 6.x" heading
[float]
===== Features

* Add OpenTelemetry API bridge {pull}1411[#1411]

//[float]
//===== Bug fixes


[[release-notes-6.x]]
Expand Down
2 changes: 1 addition & 1 deletion docs/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ include::./api.asciidoc[]

include::./metrics.asciidoc[]

include::./opentracing.asciidoc[]
include::./opentelemetry.asciidoc[]

include::./logging.asciidoc[]

Expand Down
77 changes: 77 additions & 0 deletions docs/opentelemetry.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
[[opentelemetry-bridge]]
== OpenTelemetry API Bridge

The Elastic APM OpenTelemetry bridge allows you to create Elastic APM `Transactions` and `Spans`,
using the OpenTelemetry API. This allows users to utilize the Elastic APM agent's
automatic instrumentations, while keeping custom instrumentations vendor neutral.

If a span is created while there is no transaction active, it will result in an
Elastic APM {apm-guide-ref}/transactions.html[`Transaction`]. Inner spans
are mapped to Elastic APM {apm-guide-ref}/transaction-spans.html[`Span`].

[float]
[[opentelemetry-getting-started]]
=== Getting started
The first step in getting started with the OpenTelemetry bridge is to install the `opentelemetry` libraries:

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

Or if you already have installed `elastic-apm`:


[source,bash]
----
pip install opentelemetry-api opentelemetry-sdk
----


[float]
[[opentelemetry-usage]]
=== Usage

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

tracer = Tracer(__name__);
with tracer.start_as_current_span("test"):
# Do some work
----

or

[source,python]
----
from elasticapm.contrib.opentelemetry import trace

tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("test"):
# Do some work
----


`Tracer` and `get_tracer()` accept the following optional arguments:

* `elasticapm_client`: 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.

The `Tracer` object mirrors the upstream interface on the
https://opentelemetry-python.readthedocs.io/en/latest/api/trace.html#opentelemetry.trace.Tracer[OpenTelemetry `Tracer` object.]


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

Processors, exporters, metrics, logs, span events, and span links are not supported.

Additionally, due to implementation details, the global context API only works
when a span is included in the activated context, and tokens are not used.
Instead, the global context works as a stack, and when a context is detached the
previously-active context will automatically be activated.

129 changes: 0 additions & 129 deletions docs/opentracing.asciidoc

This file was deleted.

13 changes: 11 additions & 2 deletions elasticapm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,17 +283,20 @@ def queue(self, event_type, data, flush=False):
flush = False
self._transport.queue(event_type, data, flush)

def begin_transaction(self, transaction_type, trace_parent=None, start=None):
def begin_transaction(self, transaction_type, trace_parent=None, start=None, auto_activate=True):
"""
Register the start of a transaction on the client

:param transaction_type: type of the transaction, e.g. "request"
:param trace_parent: an optional TraceParent object for distributed tracing
:param start: override the start timestamp, mostly useful for testing
:param auto_activate: whether to set this transaction in execution_context
:return: the started transaction object
"""
if self.config.is_recording:
return self.tracer.begin_transaction(transaction_type, trace_parent=trace_parent, start=start)
return self.tracer.begin_transaction(
transaction_type, trace_parent=trace_parent, start=start, auto_activate=auto_activate
)

def end_transaction(self, name=None, result="", duration=None):
"""
Expand Down Expand Up @@ -474,6 +477,12 @@ def _build_msg_for_logging(
event_data["context"] = context
if transaction and transaction.labels:
context["tags"] = deepcopy(transaction.labels)
# No intake for otel.attributes, so make them labels
if "otel_attributes" in context:
if context.get("tags"):
context["tags"].update(context.pop("otel_attributes"))
else:
context["tags"] = context.pop("otel_attributes")

# if '.' not in event_type:
# Assume it's a builtin
Expand Down
28 changes: 28 additions & 0 deletions elasticapm/context/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,41 @@

class BaseContext(object):
def set_transaction(self, transaction):
"""
Set the transaction for the current execution context
"""
raise NotImplementedError

def get_transaction(self, clear=False):
"""
Get the transaction for the current execution context

If clear=True, also set the transaction to None for the current
execution context.
"""
raise NotImplementedError

def set_span(self, span):
"""
Set the active span for the current execution context.

The previously-activated span will be saved to be re-activated later.
"""
raise NotImplementedError

def get_span(self):
"""
Get the active span for the current execution context.
"""
raise NotImplementedError

def unset_span(self, clear_all=False):
"""
De-activate the current span. If a span was previously active, it will
become active again.

Returns the deactivated span.

If clear_all=True, all spans will be cleared and no span will be active.
"""
raise NotImplementedError
47 changes: 41 additions & 6 deletions elasticapm/context/contextvars.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@

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

def get_transaction(self, clear=False):
"""
Get the transaction for the current execution context

If clear=True, also set the transaction to None for the current
execution context.
"""
try:
transaction = self.elasticapm_transaction_var.get()
if clear:
Expand All @@ -50,16 +56,45 @@ def get_transaction(self, clear=False):
return None

def set_transaction(self, transaction):
"""
Set the transaction for the current execution context
"""
self.elasticapm_transaction_var.set(transaction)

def get_span(self):
try:
return self.elasticapm_span_var.get()
except LookupError:
return None
"""
Get the active span for the current execution context.
"""
spans = self.elasticapm_spans_var.get()
return spans[-1] if spans else None

def set_span(self, span):
self.elasticapm_span_var.set(span)
"""
Set the active span for the current execution context.

The previously-activated span will be saved to be re-activated later.
"""
spans = self.elasticapm_spans_var.get()
self.elasticapm_spans_var.set(spans + (span,))

def unset_span(self, clear_all=False):
"""
De-activate the current span. If a span was previously active, it will
become active again.

Returns the deactivated span.

If clear_all=True, all spans will be cleared and no span will be active.
"""
spans = self.elasticapm_spans_var.get()
span = None
if spans:
span = spans[-1]
if clear_all:
self.elasticapm_spans_var.set(())
else:
self.elasticapm_spans_var.set(spans[0:-1])
return span


execution_context = ContextVarsContext()
Loading