-
-
Notifications
You must be signed in to change notification settings - Fork 9
ref(o11y): Support sentry-sdk configured with the streaming trace lifecycle
#767
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
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,88 @@ | ||
| from contextlib import nullcontext | ||
| from typing import Any, ContextManager | ||
|
|
||
| import sentry_sdk | ||
| from sentry_sdk.scope import Scope | ||
| from sentry_sdk.traces import StreamedSpan | ||
| from sentry_sdk.tracing import NoOpSpan, Span, Transaction | ||
| from sentry_sdk.tracing_utils import has_span_streaming_enabled | ||
|
|
||
|
|
||
| def start_transaction( | ||
| name: str, | ||
| op: str, | ||
| origin: str, | ||
| attributes: dict[str, Any], | ||
| headers: dict[str, Any], | ||
| sampling_context: dict[str, Any], | ||
| ) -> Transaction | NoOpSpan | StreamedSpan | ContextManager[Any]: | ||
| """Start a transaction, or a span if span streaming is enabled.""" | ||
| span = None | ||
| try: | ||
| is_span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) | ||
| if is_span_streaming: | ||
| sentry_sdk.traces.continue_trace(headers) | ||
| Scope.set_custom_sampling_context(sampling_context) | ||
|
|
||
| return sentry_sdk.traces.start_span( | ||
| name=name, | ||
| attributes={ | ||
| "sentry.op": op, | ||
| "sentry.origin": origin, | ||
| **attributes, | ||
| }, | ||
| ) | ||
|
|
||
| transaction = sentry_sdk.continue_trace( | ||
| environ_or_headers=headers, | ||
| op=op, | ||
| name=name, | ||
| origin=origin, | ||
| ) | ||
|
|
||
| span = sentry_sdk.start_transaction(transaction, custom_sampling_context=sampling_context) | ||
| for key, value in attributes.items(): | ||
| span.set_data(key, value) | ||
| except Exception: | ||
| pass | ||
|
|
||
| if span is None: | ||
| return nullcontext() | ||
| return span | ||
|
|
||
|
|
||
| def start_span( | ||
| name: str, op: str, origin: str, attributes: dict[str, Any] | ||
| ) -> Span | StreamedSpan | ContextManager[Any]: | ||
| """Start a span in the currently active trace lifecycle.""" | ||
| try: | ||
| is_span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) | ||
|
|
||
| # Early return to avoid large increase in span volume. | ||
| # Mirrors transaction-based tracing, in which `start_span()` no-ops when there is no active transaction. | ||
| if is_span_streaming and sentry_sdk.traces.get_current_span() is None: | ||
| return nullcontext() | ||
|
|
||
| if is_span_streaming: | ||
| return sentry_sdk.traces.start_span( | ||
| name=name, | ||
| attributes={ | ||
| "sentry.op": op, | ||
| "sentry.origin": origin, | ||
| **attributes, | ||
| }, | ||
| ) | ||
|
alexander-alderman-webb marked this conversation as resolved.
|
||
|
|
||
| span = sentry_sdk.start_span( | ||
| op=op, | ||
| name=name, | ||
| origin=origin, | ||
| ) | ||
| for key, value in attributes.items(): | ||
| span.set_data(key, value) | ||
|
|
||
| return span | ||
| except Exception: | ||
| pass | ||
|
|
||
| return nullcontext() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,10 +32,12 @@ | |
| ) | ||
| from sentry_sdk.consts import OP, SPANDATA | ||
| from sentry_sdk.crons import MonitorStatus, capture_checkin | ||
| from sentry_sdk.tracing import Span | ||
|
|
||
| from taskbroker_client.app import import_app | ||
| from taskbroker_client.constants import CompressionType | ||
| from taskbroker_client.retry import NoRetriesRemainingError | ||
| from taskbroker_client.sdk import start_span, start_transaction | ||
| from taskbroker_client.state import clear_current_task, current_task, set_current_task | ||
| from taskbroker_client.task import Task | ||
| from taskbroker_client.types import ContextHook, InflightTaskActivation, ProcessingResult | ||
|
|
@@ -612,45 +614,49 @@ def _execute_activation( | |
| kwargs = parameters.get("kwargs", {}) | ||
|
|
||
| headers = dict(activation.headers) | ||
| transaction = sentry_sdk.continue_trace( | ||
| environ_or_headers=headers, | ||
| op="queue.task.taskworker", | ||
| name=activation.taskname, | ||
| origin="taskworker", | ||
| ) | ||
| sampling_context = { | ||
| "taskworker": { | ||
| "task": activation.taskname, | ||
| } | ||
| } | ||
|
|
||
| with ( | ||
| metrics.track_memory_usage( | ||
| "taskworker.worker.memory_change", | ||
| tags={"namespace": activation.namespace, "taskname": activation.taskname}, | ||
| ), | ||
| sentry_sdk.isolation_scope(), | ||
| sentry_sdk.start_transaction(transaction, custom_sampling_context=sampling_context), | ||
| start_transaction( | ||
| name=activation.taskname, | ||
| op="queue.task.taskworker", | ||
| origin="taskworker", | ||
| attributes={ | ||
| "taskworker-task.id": activation.id, | ||
| }, | ||
| headers=headers, | ||
| sampling_context={ | ||
| "taskworker": { | ||
| "task": activation.taskname, | ||
| } | ||
| }, | ||
| ) as transaction, | ||
| ): | ||
| transaction.set_data( | ||
| "taskworker-task", {"args": args, "kwargs": kwargs, "id": activation.id} | ||
| ) | ||
| # Do not attach on StreamedSpan because eager serialization increases memory use. | ||
|
Member
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. More because I'm curious - how significant is the increase in memory due to the eager serialization?
Contributor
Author
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. Context is here: #757 (comment) |
||
| if isinstance(transaction, Span): | ||
| transaction.set_data("taskworker-task.args", args) | ||
| transaction.set_data("taskworker-task.kwargs", kwargs) | ||
|
|
||
| task_added_time = activation.received_at.ToDatetime().timestamp() | ||
| # latency attribute needs to be in milliseconds | ||
| latency = (time.time() - task_added_time) * 1000 | ||
|
|
||
| with sentry_sdk.start_span( | ||
| op=OP.QUEUE_PROCESS, | ||
| with start_span( | ||
| name=activation.taskname, | ||
| op=OP.QUEUE_PROCESS, | ||
| origin="taskworker", | ||
| ) as span: | ||
| span.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, activation.namespace) | ||
| span.set_data(SPANDATA.MESSAGING_MESSAGE_ID, activation.id) | ||
| span.set_data(SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) | ||
| span.set_data( | ||
| SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, activation.retry_state.attempts | ||
| ) | ||
| span.set_data(SPANDATA.MESSAGING_SYSTEM, "taskworker") | ||
|
|
||
| attributes={ | ||
| SPANDATA.MESSAGING_DESTINATION_NAME: activation.namespace, | ||
| SPANDATA.MESSAGING_MESSAGE_ID: activation.id, | ||
| SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY: latency, | ||
| SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT: activation.retry_state.attempts, | ||
| SPANDATA.MESSAGING_SYSTEM: "taskworker", | ||
| }, | ||
| ): | ||
| # TODO(taskworker) remove this when doing cleanup | ||
| # The `__start_time` parameter is spliced into task parameters by | ||
| # sentry.celery.SentryTask._add_metadata and needs to be removed | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
Looking at this method and the
start_spanmethod below - I don't fully understand what types of exceptions we're guarding against (and from where, the streamed span or transaction logic) that we need this null context.Could you provide some more context on why we need the try/except above along with this fallback?
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.
We used a shim without the
try...exceptinsentryitself. There's nothing in particular that I'd expect to blow up. We could also get rid of thetry...except🤷