Skip to content

Commit aac4aa7

Browse files
ntindleAbhi1992002
authored andcommitted
feat(backend): Add Sentry user and tag tracking to node execution (#11170)
Integrates Sentry SDK to set user and contextual tags during node execution for improved error tracking and user count analytics. Ensures Sentry context is properly set and restored, and exceptions are captured with relevant context before scope restoration. <!-- Clearly explain the need for these changes: --> ### Changes 🏗️ Adds sentry tracking to block failures <!-- Concisely describe all of the changes made in this pull request: --> ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [x] Test to make sure the userid and block details show up in Sentry - [x] make sure other errors aren't contaminated <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Added conditional support for feature flags when configured, enabling targeted rollouts and experiments without impacting unconfigured environments. - Chores - Enhanced error monitoring with richer contextual data during node execution to improve stability and diagnostics. - Updated metrics initialization to dynamically include feature flag integrations when available, without altering behavior for unconfigured setups. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 12e0994 commit aac4aa7

File tree

3 files changed

+40
-2
lines changed

3 files changed

+40
-2
lines changed

autogpt_platform/backend/backend/executor/manager.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from datetime import datetime, timedelta, timezone
1111
from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast
1212

13+
import sentry_sdk
1314
from pika.adapters.blocking_connection import BlockingChannel
1415
from pika.spec import Basic, BasicProperties
1516
from prometheus_client import Gauge, start_http_server
@@ -224,6 +225,23 @@ async def execute_node(
224225
extra_exec_kwargs[field_name] = credentials
225226

226227
output_size = 0
228+
229+
# sentry tracking nonsense to get user counts for blocks because isolation scopes don't work :(
230+
scope = sentry_sdk.get_current_scope()
231+
232+
# save the tags
233+
original_user = scope._user
234+
original_tags = dict(scope._tags) if scope._tags else {}
235+
# Set user ID for error tracking
236+
scope.set_user({"id": user_id})
237+
238+
scope.set_tag("graph_id", graph_id)
239+
scope.set_tag("node_id", node_id)
240+
scope.set_tag("block_name", node_block.name)
241+
scope.set_tag("block_id", node_block.id)
242+
for k, v in (data.user_context or UserContext(timezone="UTC")).model_dump().items():
243+
scope.set_tag(f"user_context.{k}", v)
244+
227245
try:
228246
async for output_name, output_data in node_block.execute(
229247
input_data, **extra_exec_kwargs
@@ -232,6 +250,12 @@ async def execute_node(
232250
output_size += len(json.dumps(output_data))
233251
log_metadata.debug("Node produced output", **{output_name: output_data})
234252
yield output_name, output_data
253+
except Exception:
254+
# Capture exception WITH context still set before restoring scope
255+
sentry_sdk.capture_exception(scope=scope)
256+
sentry_sdk.flush() # Ensure it's sent before we restore scope
257+
# Re-raise to maintain normal error flow
258+
raise
235259
finally:
236260
# Ensure credentials are released even if execution fails
237261
if creds_lock and (await creds_lock.locked()) and (await creds_lock.owned()):
@@ -246,6 +270,10 @@ async def execute_node(
246270
execution_stats.input_size = input_size
247271
execution_stats.output_size = output_size
248272

273+
# Restore scope AFTER error has been captured
274+
scope._user = original_user
275+
scope._tags = original_tags
276+
249277

250278
async def _enqueue_next_nodes(
251279
db_client: "DatabaseManagerAsyncClient",
@@ -570,7 +598,6 @@ async def persist_output(output_name: str, output_data: Any) -> None:
570598
await persist_output(
571599
"error", str(stats.error) or type(stats.error).__name__
572600
)
573-
574601
return status
575602

576603
@func_retry

autogpt_platform/backend/backend/util/feature_flag.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ class Flag(str, Enum):
3737
AGENT_ACTIVITY = "agent-activity"
3838

3939

40+
def is_configured() -> bool:
41+
"""Check if LaunchDarkly is configured with an SDK key."""
42+
return bool(settings.secrets.launch_darkly_sdk_key)
43+
44+
4045
def get_client() -> LDClient:
4146
"""Get the LaunchDarkly client singleton."""
4247
if not _is_initialized:

autogpt_platform/backend/backend/util/metrics.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
from pydantic import SecretStr
66
from sentry_sdk.integrations.anthropic import AnthropicIntegration
77
from sentry_sdk.integrations.asyncio import AsyncioIntegration
8+
from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration
89
from sentry_sdk.integrations.logging import LoggingIntegration
910

11+
from backend.util.feature_flag import get_client, is_configured
1012
from backend.util.settings import Settings
1113

1214
settings = Settings()
@@ -19,6 +21,9 @@ class DiscordChannel(str, Enum):
1921

2022
def sentry_init():
2123
sentry_dsn = settings.secrets.sentry_dsn
24+
integrations = []
25+
if is_configured():
26+
integrations.append(LaunchDarklyIntegration(get_client()))
2227
sentry_sdk.init(
2328
dsn=sentry_dsn,
2429
traces_sample_rate=1.0,
@@ -31,7 +36,8 @@ def sentry_init():
3136
AnthropicIntegration(
3237
include_prompts=False,
3338
),
34-
],
39+
]
40+
+ integrations,
3541
)
3642

3743

0 commit comments

Comments
 (0)