-
Notifications
You must be signed in to change notification settings - Fork 551
feat(tracing): Initial tracing experiments #342
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
Changes from all commits
815a959
c88ee11
45bf167
8cd5eb6
82cf773
6936d03
94438ec
e818437
e2d406b
2cded9b
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 |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import re | ||
import uuid | ||
|
||
_traceparent_header_format_re = re.compile( | ||
"^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})" "(-.*)?[ \t]*$" | ||
) | ||
|
||
|
||
class _EnvironHeaders(object): | ||
def __init__(self, environ): | ||
self.environ = environ | ||
|
||
def get(self, key): | ||
return self.environ.get("HTTP_" + key.replace("-", "_").upper()) | ||
|
||
|
||
class SpanContext(object): | ||
def __init__(self, trace_id, span_id, recorded=False, parent=None): | ||
self.trace_id = trace_id | ||
self.span_id = span_id | ||
self.recorded = recorded | ||
self.parent = None | ||
|
||
def __repr__(self): | ||
return "%s(trace_id=%r, span_id=%r, recorded=%r)" % (< 10000 /td> | ||
self.__class__.__name__, | ||
self.trace_id, | ||
self.span_id, | ||
self.recorded, | ||
) | ||
|
||
@classmethod | ||
def start_trace(cls, recorded=False): | ||
return cls( | ||
trace_id=uuid.uuid4().hex, span_id=uuid.uuid4().hex[16:], recorded=recorded | ||
) | ||
|
||
def new_span(self): | ||
if self.trace_id is None: | ||
return SpanContext.start_trace() | ||
return SpanContext( | ||
trace_id=self.trace_id, | ||
span_id=uuid.uuid4().hex[16:], | ||
parent=self, | ||
recorded=self.recorded, | ||
) | ||
|
||
@classmethod | ||
def continue_from_environ(cls, environ): | ||
return cls.continue_from_headers(_EnvironHeaders(environ)) | ||
|
||
@classmethod | ||
def continue_from_headers(cls, headers): | ||
parent = cls.from_traceparent(headers.get("sentry-trace")) | ||
if parent is None: | ||
return cls.start_trace() | ||
return parent.new_span() | ||
|
||
def iter_headers(self): | ||
yield "sentry-trace", self.to_traceparent() | ||
|
||
@classmethod | ||
def from_traceparent(cls, traceparent): | ||
if not traceparent: | ||
return None | ||
|
||
match = _traceparent_header_format_re.match(traceparent) | ||
if match is None: | ||
return None | ||
|
||
version, trace_id, span_id, trace_options, extra = match.groups() | ||
|
||
if int(trace_id, 16) == 0 or int(span_id, 16) == 0: | ||
return None | ||
|
||
version = int(version, 16) | ||
if version == 0: | ||
if extra: | ||
return None | ||
elif version == 255: | ||
return None | ||
|
||
options = int(trace_options, 16) | ||
|
||
return cls(trace_id=trace_id, span_id=span_id, recorded=options & 1 != 0) | ||
|
||
def to_traceparent(self): | ||
return "%02x-%s-%s-%02x" % ( | ||
0, | ||
self.trace_id, | ||
self.span_id, | ||
self.recorded and 1 or 0, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,9 @@ | |
|
||
pytest.importorskip("celery") | ||
|
||
from sentry_sdk import Hub | ||
from sentry_sdk import Hub, configure_scope | ||
from sentry_sdk.integrations.celery import CeleryIntegration | ||
from sentry_sdk.tracing import SpanContext | ||
|
||
from celery import Celery, VERSION | ||
from celery.bin import worker | ||
|
@@ -22,8 +23,8 @@ def inner(signal, f): | |
|
||
@pytest.fixture | ||
def init_celery(sentry_init): | ||
def inner(): | ||
sentry_init(integrations=[CeleryIntegration()]) | ||
def inner(propagate_traces=True): | ||
sentry_init(integrations=[CeleryIntegration(propagate_traces=propagate_traces)]) | ||
celery = Celery(__name__) | ||
if VERSION < (4,): | ||
celery.conf.CELERY_ALWAYS_EAGER = True | ||
|
@@ -47,9 +48,15 @@ def dummy_task(x, y): | |
foo = 42 # noqa | ||
return x / y | ||
|
||
span_context = SpanContext.start_trace() | ||
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. Is there a reason why we just can do "new SpanContext"? 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. For prosperity from a conversation in person: Mostly just so that the ctor will not attempt to auto generate. We can change this, that was just an early draft. |
||
with configure_scope() as scope: | ||
scope.set_span_context(span_context) | ||
dummy_task.delay(1, 2) | ||
dummy_task.delay(1, 0) | ||
|
||
event, = events | ||
assert event["contexts"]["trace"]["trace_id"] == span_context.trace_id | ||
assert event["contexts"]["trace"]["span_id"] != span_context.span_id | ||
assert event["transaction"] == "dummy_task" | ||
assert event["extra"]["celery-job"] == { | ||
"args": [1, 0], | ||
|
@@ -63,6 +70,26 @@ def dummy_task(x, y): | |
assert exception["stacktrace"]["frames"][0]["vars"]["foo"] == "42" | ||
|
||
|
||
def test_simple_no_propagation(capture_events, init_celery): | ||
celery = init_celery(propagate_traces=False) | ||
events = capture_events() | ||
|
||
@celery.task(name="dummy_task") | ||
def dummy_task(): | ||
1 / 0 | ||
|
||
span_context = SpanContext.start_trace() | ||
with configure_scope() as scope: | ||
scope.set_span_context(span_context) | ||
dummy_task.delay() | ||
|
||
event, = events | ||
assert event["contexts"]["trace"]["trace_id"] != span_context.trace_id | ||
assert event["transaction"] == "dummy_task" | ||
exception, = event["exception"]["values"] | ||
assert exception["type"] == "ZeroDivisionError" | ||
|
||
|
||
def test_ignore_expected(capture_events, celery): | ||
events = capture_events() | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.