-
Notifications
You must be signed in to change notification settings - Fork 422
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
[sampling] add distributed tracing capabilities #325
Changes from all commits
6bef657
c5d9620
0f474e9
1a88131
1a43e6f
fcfa9ce
6d2b167
dd829db
16a0371
c055da6
bf9bee6
9dc5e9e
c433275
02f2c72
72800da
8648258
99e4967
0f76d49
abaa8d3
d355cea
b258765
00cb9f6
52442d5
7f85e15
5a0b09a
3d91855
d95d117
2c9275b
b4b97e9
d6e0961
e8f4387
0286531
97c5984
73f42a9
b9b9d87
5b9d9d2
9f9b269
a69f9c9
61ad28c
48897c0
7a7804a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
FILTERS_KEY = 'FILTERS' | ||
SAMPLING_PRIORITY_KEY = '_sampling_priority_v1' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,25 +20,31 @@ class Context(object): | |
|
||
This data structure is thread-safe. | ||
""" | ||
def __init__(self, trace_id=None, span_id=None, sampled=True): | ||
def __init__(self, trace_id=None, span_id=None, sampled=True, sampling_priority=None): | ||
""" | ||
Initialize a new thread-safe ``Context``. | ||
|
||
:param int trace_id: trace_id of parent span | ||
:param int span_id: span_id of parent span | ||
""" | ||
self._trace = [] | ||
self._sampled = sampled | ||
self._finished_spans = 0 | ||
self._current_span = None | ||
self._lock = threading.Lock() | ||
self._parent_span_id = span_id | ||
|
||
self._parent_trace_id = trace_id | ||
self._parent_span_id = span_id | ||
self._sampled = sampled | ||
self._sampling_priority = sampling_priority | ||
|
||
def get_context_attributes(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to change the private API? that was set as internal because a possible refactoring could make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't especially defend nor like this API. |
||
""" | ||
Return the context propagatable attributes. | ||
|
||
def _get_parent_span_ids(self): | ||
""" Returns tuple of base trace_id, span_id for distributed tracing.""" | ||
Useful to propagate context to an external element. | ||
""" | ||
with self._lock: | ||
return self._parent_trace_id, self._parent_span_id | ||
return self._parent_trace_id, self._parent_span_id, self._sampling_priority | ||
|
||
def get_current_span(self): | ||
""" | ||
|
@@ -50,13 +56,28 @@ def get_current_span(self): | |
with self._lock: | ||
return self._current_span | ||
|
||
def _set_current_span(self, span): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the class is meant to be thread-safe, whatever API we provide, it should use the internal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was just not to dupe code between |
||
""" | ||
Set current span internally. | ||
|
||
Non-safe if not used with a lock. For internal Context usage only. | ||
""" | ||
self._current_span = span | ||
if span: | ||
self._parent_trace_id = span.trace_id | ||
self._parent_span_id = span.span_id | ||
self._sampled = span.sampled | ||
self._sampling_priority = span.get_sampling_priority() | ||
else: | ||
self._parent_span_id = None | ||
|
||
def add_span(self, span): | ||
""" | ||
Add a span to the context trace list, keeping it as the last active span. | ||
""" | ||
with self._lock: | ||
self._current_span = span | ||
self._sampled = span.sampled | ||
self._set_current_span(span) | ||
|
||
self._trace.append(span) | ||
span._context = self | ||
|
||
|
@@ -67,7 +88,7 @@ def close_span(self, span): | |
""" | ||
with self._lock: | ||
self._finished_spans += 1 | ||
self._current_span = span._parent | ||
self._set_current_span(span._parent) | ||
|
||
# notify if the trace is not closed properly; this check is executed only | ||
# if the tracer debug_logging is enabled and when the root span is closed | ||
|
@@ -114,9 +135,11 @@ def get(self): | |
sampled = self._sampled | ||
# clean the current state | ||
self._trace = [] | ||
self._sampled = False | ||
self._finished_spans = 0 | ||
self._current_span = None | ||
self._parent_trace_id = None | ||
self._parent_span_id = None | ||
self._sampling_priority = None | ||
self._sampled = True | ||
return trace, sampled | ||
else: | ||
return None, None | ||
|
@@ -145,9 +168,7 @@ def set(self, ctx): | |
def get(self): | ||
ctx = getattr(self._locals, 'context', None) | ||
if not ctx: | ||
# create a new Context if it's not available; this action | ||
# is done once because the Context has the reset() method | ||
# to reuse the same instance | ||
# create a new Context if it's not available | ||
ctx = Context() | ||
self._locals.context = ctx | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
from ..asyncio import context_provider | ||
from ...ext import AppTypes, http | ||
from ...compat import stringify | ||
from ...context import Context | ||
|
||
|
||
CONFIG_KEY = 'datadog_trace' | ||
|
@@ -11,6 +12,7 @@ | |
|
||
PARENT_TRACE_HEADER_ID = 'x-datadog-trace-id' | ||
PARENT_SPAN_HEADER_ID = 'x-datadog-parent-id' | ||
SAMPLING_PRIORITY_HEADER_ID = 'x-datadog-sampling-priority' | ||
|
||
|
||
@asyncio.coroutine | ||
|
@@ -29,24 +31,30 @@ def attach_context(request): | |
service = app[CONFIG_KEY]['service'] | ||
distributed_tracing = app[CONFIG_KEY]['distributed_tracing_enabled'] | ||
|
||
context = tracer.get_call_context() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically this must be Also, creating manually a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When implementing, I only looked at So what is the proper thing to do there? |
||
|
||
# Create a new context based on the propagated information | ||
# Do not fill context with distributed sampling if the tracer is disabled | ||
# because the would call the callee to generate references to data which | ||
# has never been sent to agent. | ||
if tracer.enabled and distributed_tracing: | ||
trace_id = int(request.headers.get(PARENT_TRACE_HEADER_ID, 0)) | ||
parent_span_id = int(request.headers.get(PARENT_SPAN_HEADER_ID, 0)) | ||
sampling_priority = request.headers.get(SAMPLING_PRIORITY_HEADER_ID) | ||
# keep sampling priority as None if not propagated, to support older client versions on the parent side | ||
if sampling_priority: | ||
sampling_priority = int(sampling_priority) | ||
|
||
context = Context(trace_id=trace_id, span_id=parent_span_id, sampling_priority=sampling_priority) | ||
|
||
# trace the handler | ||
request_span = tracer.trace( | ||
request_span = tracer.start_span( | ||
'aiohttp.request', | ||
service=service, | ||
span_type=http.TYPE, | ||
child_of=context, | ||
) | ||
|
||
if distributed_tracing: | ||
# set parent trace/span IDs if present: | ||
# http://pypi.datadoghq.com/trace/docs/#distributed-tracing | ||
parent_trace_id = request.headers.get(PARENT_TRACE_HEADER_ID) | ||
if parent_trace_id is not None: | ||
request_span.trace_id = int(parent_trace_id) | ||
|
||
parent_span_id = request.headers.get(PARENT_SPAN_HEADER_ID) | ||
if parent_span_id is not None: | ||
request_span.parent_id = int(parent_span_id) | ||
|
||
# attach the context and the root span to the request; the Context | ||
# may be freely used by the application code | ||
request[REQUEST_CONTEXT_KEY] = request_span.context | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There @palazzem I'd like to have your opinion on this. I thought it was OK to cascade down from v0.4 to v0.3 to v0.2 but I'm open to other possibilities. The advantage of doing this is that here I just care about the endpoint, but the question of knowing wether we have a JSON as an answer or a plain
OK\n
string is done later. This enables low coupling between this chunk of code and the one handling the JSON.