Skip to content

Commit 0c9803a

Browse files
authored
Do not create a span when task is triggered by Celery Beat (#2510)
We create a span for submitting a Celery task for execution (when apply_async() is called). In cases where web frameworks are calling apply_async() this is fine, because the web framework created a transaction where the span is attached. When Celery Beat wakes up and is calling apply_async() this is not good, because there is no transaction and then the span ID of the newly created span will be given to the task as parent_span_id leading to orphaned transactions. So in case apply_async() is called by Celery Beat, we do not create a span for submitting the task for execution.
1 parent 5a6b5d4 commit 0c9803a

File tree

2 files changed

+69
-5
lines changed

2 files changed

+69
-5
lines changed

sentry_sdk/integrations/celery.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from typing import TypeVar
3131
from typing import Union
3232

33+
from sentry_sdk.tracing import Span
3334
from sentry_sdk._types import EventProcessor, Event, Hint, ExcInfo
3435

3536
F = TypeVar("F", bound=Callable[..., Any])
@@ -133,6 +134,16 @@ def _now_seconds_since_epoch():
133134
return time.time()
134135

135136

137+
class NoOpMgr:
138+
def __enter__(self):
139+
# type: () -> None
140+
return None
141+
142+
def __exit__(self, exc_type, exc_value, traceback):
143+
# type: (Any, Any, Any) -> None
144+
return None
145+
146+
136147
def _wrap_apply_async(f):
137148
# type: (F) -> F
138149
@wraps(f)
@@ -154,11 +165,26 @@ def apply_async(*args, **kwargs):
154165
if not propagate_traces:
155166
return f(*args, **kwargs)
156167

157-
with hub.start_span(
158-
op=OP.QUEUE_SUBMIT_CELERY, description=args[0].name
159-
) as span:
168+
try:
169+
task_started_from_beat = args[1][0] == "BEAT"
170+
except IndexError:
171+
task_started_from_beat = False
172+
173+
task = args[0]
174+
175+
span_mgr = (
176+
hub.start_span(op=OP.QUEUE_SUBMIT_CELERY, description=task.name)
177+
if not task_started_from_beat
178+
else NoOpMgr()
179+
) # type: Union[Span, NoOpMgr]
180+
181+
with span_mgr as span:
160182
with capture_internal_exceptions():
161-
headers = dict(hub.iter_trace_propagation_headers(span))
183+
headers = (
184+
dict(hub.iter_trace_propagation_headers(span))
185+
if span is not None
186+
else {}
187+
)
162188
if integration.monitor_beat_tasks:
163189
headers.update(
164190
{

tests/integrations/celery/test_celery.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import pytest
44

55
from sentry_sdk import Hub, configure_scope, start_transaction, get_current_span
6-
from sentry_sdk.integrations.celery import CeleryIntegration, _get_headers
6+
from sentry_sdk.integrations.celery import (
7+
CeleryIntegration,
8+
_get_headers,
9+
_wrap_apply_async,
10+
)
711

812
from sentry_sdk._compat import text_type
913

@@ -555,3 +559,37 @@ def dummy_task(self, message):
555559
headers={"sentry-propagate-traces": False},
556560
).get()
557561
assert transaction_trace_id != task_transaction_id
562+
563+
564+
def test_apply_async_manually_span(sentry_init):
565+
sentry_init(
566+
integrations=[CeleryIntegration()],
567+
)
568+
569+
def dummy_function(*args, **kwargs):
570+
headers = kwargs.get("headers")
571+
assert "sentry-trace" in headers
572+
assert "baggage" in headers
573+
574+
wrapped = _wrap_apply_async(dummy_function)
575+
wrapped(mock.MagicMock(), (), headers={})
576+
577+
578+
def test_apply_async_from_beat_no_span(sentry_init):
579+
sentry_init(
580+
integrations=[CeleryIntegration()],
581+
)
582+
583+
def dummy_function(*args, **kwargs):
584+
headers = kwargs.get("headers")
585+
assert "sentry-trace" not in headers
586+
assert "baggage" not in headers
587+
588+
wrapped = _wrap_apply_async(dummy_function)
589+
wrapped(
590+
mock.MagicMock(),
591+
[
592+
"BEAT",
593+
],
594+
headers={},
595+
)

0 commit comments

Comments
 (0)