Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,19 @@

IS_BUGSNAG_ENABLED: bool = bool(_BUGSNAG_API_KEY)
_setup_called: bool = False
_service_name: str | None = None


def _before_notify(event: bugsnag_event.Event) -> None:
"""Attach contextual logging metadata to every Bugsnag event."""
context = contextual_logging.get_all_context_metadata()
if context:
event.add_tab("tangle_context", context)

custom: dict[str, str] = {}
if _service_name:
custom["slice_name"] = _service_name

if _CUSTOM_GROUPING_KEY and event.original_error:
# Use the full chain for grouping so that "LauncherError <- TimeoutError"
# and "LauncherError <- ApiException" land in separate, stable groups.
Expand All @@ -50,7 +56,7 @@ def _before_notify(event: bugsnag_event.Event) -> None:
)
prefix = (event.metadata.get("extra") or {}).get("grouping_prefix")
key_value = f"{prefix}: {chain}" if prefix else chain
event.add_tab("custom", {_CUSTOM_GROUPING_KEY: key_value})
custom[_CUSTOM_GROUPING_KEY] = key_value
if prefix and event.errors:
try:
for error in event.errors:
Expand All @@ -72,6 +78,9 @@ def _before_notify(event: bugsnag_event.Event) -> None:
except Exception:
_logger.debug("Could not set chain title on errorClass", exc_info=True)

if custom:
event.add_tab("custom", custom)


def setup(*, service_name: str | None = None) -> None:
"""Configure the Bugsnag client.
Expand Down Expand Up @@ -102,8 +111,9 @@ def setup(*, service_name: str | None = None) -> None:
project_root=service_name,
)
bugsnag_sdk.before_notify(_before_notify)
global _setup_called
global _setup_called, _service_name
_setup_called = True
_service_name = service_name
except Exception:
_logger.exception("Failed to initialize Bugsnag")

Expand Down
25 changes: 25 additions & 0 deletions tests/instrumentation/test_bugsnag.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,31 @@ def test_before_notify_skips_error_class_prefix_gracefully_on_bad_errors_structu
bugsnag_module._before_notify(mock_event)


def test_before_notify_sets_slice_name_when_service_name_configured(monkeypatch):
monkeypatch.setenv("TANGLE_BUGSNAG_API_KEY", "test-api-key")
monkeypatch.setenv("TANGLE_ENV", "staging")

import importlib
import cloud_pipelines_backend.instrumentation.bugsnag_instrumentation as bugsnag_module

importlib.reload(bugsnag_module)

from cloud_pipelines_backend.instrumentation import contextual_logging

contextual_logging.clear_context_metadata()

with mock.patch("bugsnag.configure"), mock.patch("bugsnag.before_notify"):
bugsnag_module.setup(service_name="orchestrator")

mock_event = mock.MagicMock()
mock_event.original_error = None
mock_event.metadata = {}

bugsnag_module._before_notify(mock_event)

mock_event.add_tab.assert_called_once_with("custom", {"slice_name": "orchestrator"})


def test_before_notify_skips_empty_context(monkeypatch):
monkeypatch.setenv("TANGLE_BUGSNAG_API_KEY", "test-api-key")
monkeypatch.setenv("TANGLE_ENV", "staging")
Expand Down