-
Notifications
You must be signed in to change notification settings - Fork 20
bugsnag: propagate cloud_provider into contextual logging for queued executions #268
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
base: bugsnag/service-slice
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -25,6 +25,10 @@ | |
| from contextlib import contextmanager | ||
| from typing import Any, Optional | ||
|
|
||
| from .. import backend_types_sql as bts | ||
|
|
||
| _CLOUD_PROVIDER_ANNOTATION_KEY = "cloud-pipelines.net/orchestration/cloud_provider" | ||
|
Collaborator
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.
Module docstring's metadata-key list should mention After this PR, Fix: add one line to the docstring's "Common metadata keys" list:
Collaborator
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. nebius_launchers.py has this same exact constant. Would it make sense to create a shared config/model/etc file that both of them reference against? Or some way to not duplicate the variable? |
||
|
|
||
| # Single context variable to store all metadata as a dictionary | ||
| _context_metadata: contextvars.ContextVar[dict[str, Any]] = contextvars.ContextVar( | ||
| "context_metadata", default={} | ||
|
|
@@ -125,3 +129,21 @@ def logging_context(**metadata: Any): | |
| finally: | ||
| # Restore previous metadata | ||
| _context_metadata.set(prev_metadata) | ||
|
|
||
|
|
||
| def execution_logging_context(execution: bts.ExecutionNode): | ||
|
Collaborator
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.
No unit tests for The new function has several non-trivial branches: Fix: add a handful of cases in def test_execution_logging_context_no_annotations():
exc = make_execution_node(id="e1", task_spec={})
with contextual_logging.execution_logging_context(exc):
assert contextual_logging.get_context_metadata("execution_id") == "e1"
assert contextual_logging.get_context_metadata("cloud_provider") is None
def test_execution_logging_context_with_cloud_provider():
exc = make_execution_node(
id="e1",
task_spec={"annotations": {"cloud-pipelines.net/orchestration/cloud_provider": "gcp"}},
)
with contextual_logging.execution_logging_context(exc):
assert contextual_logging.get_context_metadata("cloud_provider") == "gcp"
def test_execution_logging_context_annotations_is_none():
# Regression guard for None handling (see other comment).
exc = make_execution_node(id="e1", task_spec={"annotations": None})
with contextual_logging.execution_logging_context(exc):
assert contextual_logging.get_context_metadata("cloud_provider") is None |
||
| """Return a logging context populated with metadata for *execution*. | ||
|
|
||
| Always sets ``execution_id``. Also sets ``cloud_provider`` when the | ||
| ``cloud-pipelines.net/orchestration/cloud_provider`` annotation is present | ||
| on the task spec. | ||
| """ | ||
| ctx: dict[str, str] = {"execution_id": execution.id} | ||
| cloud_provider = ( | ||
| (execution.task_spec or {}) | ||
| .get("annotations", {}) | ||
|
Collaborator
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.
cloud_provider = (
(execution.task_spec or {})
.get("annotations", {}) # returns None, NOT {}, if annotations is None
.get(_CLOUD_PROVIDER_ANNOTATION_KEY)
)
Fix: chain cloud_provider = (
((execution.task_spec or {}).get("annotations") or {})
.get(_CLOUD_PROVIDER_ANNOTATION_KEY)
) |
||
| .get(_CLOUD_PROVIDER_ANNOTATION_KEY) | ||
| ) | ||
| if cloud_provider is not None: | ||
| ctx["cloud_provider"] = cloud_provider | ||
| return logging_context(**ctx) | ||
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.
New
instrumentation→ core import is a circular-import hazard.The
instrumentationpackage was previously dependency-free w.r.t. core model code —bugsnag_instrumentation.py,error_normalization.py, and the rest don't import from... This is the first instrumentation → core import. Ifbackend_types_sqlever wants to call back into instrumentation for logging (a plausible future change — e.g. logging inside an__post_init__or validator), you get an import cycle.The only runtime use of
btshere is the type annotation onexecution: bts.ExecutionNode. That doesn't need a real import.Fix: guard with
TYPE_CHECKING:bts.ExecutionNodebecomes a forward reference resolved by the type checker, with zero runtime cost and no cycle risk.