Skip to content

Commit eec9738

Browse files
committed
Log deeplink to trace on AgentOps dashboard.
1 parent c19d8f9 commit eec9738

2 files changed

Lines changed: 52 additions & 57 deletions

File tree

agentops/helpers/dashboard.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Helpers for interacting with the AgentOps dashboard.
3+
"""
4+
from typing import Union
5+
from termcolor import colored
6+
from opentelemetry.sdk.trace import Span
7+
from agentops.logging import logger
8+
from agentops.sdk.converters import trace_id_to_uuid
9+
10+
11+
APP_URL = "https://app.agentops.ai"
12+
13+
def get_trace_url(span: Span) -> str:
14+
"""
15+
Generate a trace URL for a direct link to the session on the AgentOps dashboard.
16+
17+
Args:
18+
span: The span to generate the URL for.
19+
20+
Returns:
21+
The session URL.
22+
"""
23+
trace_id: Union[str, int] = span.context.trace_id
24+
25+
# Convert trace_id to hex string if it's not already
26+
if isinstance(trace_id, int):
27+
trace_id = trace_id_to_uuid(trace_id)
28+
29+
return f"{APP_URL}/sessions?trace_id={trace_id}"
30+
31+
32+
def log_trace_url(span: Span) -> None:
33+
"""
34+
Log the trace URL for the AgentOps dashboard.
35+
36+
Args:
37+
span: The span to log the URL for.
38+
"""
39+
session_url = get_trace_url(span)
40+
logger.info(colored(f"\x1b[34mSession Replay: {session_url}\x1b[0m", "blue"))
41+

agentops/sdk/processors.py

Lines changed: 11 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,17 @@
44
This module contains processors for OpenTelemetry spans.
55
"""
66

7-
import copy
8-
import threading
97
import time
108
from threading import Event, Lock, Thread
11-
from typing import Any, Dict, List, Optional
9+
from typing import Dict, Optional
1210

1311
from opentelemetry.context import Context
1412
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
1513
from opentelemetry.sdk.trace.export import SpanExporter
16-
from termcolor import colored
1714

1815
import agentops.semconv as semconv
1916
from agentops.logging import logger
20-
from agentops.sdk.converters import trace_id_to_uuid, uuid_to_int16
17+
from agentops.helpers.dashboard import log_trace_url
2118
from agentops.semconv.core import CoreAttributes
2219

2320

@@ -89,14 +86,7 @@ class InternalSpanProcessor(SpanProcessor):
8986
For session spans, it prints a URL to the AgentOps dashboard.
9087
"""
9188

92-
def __init__(self, app_url: str = "https://app.agentops.ai"):
93-
"""
94-
Initialize the PrintSpanProcessor.
95-
96-
Args:
97-
app_url: The base URL for the AgentOps dashboard.
98-
"""
99-
self.app_url = app_url
89+
_root_span_id: Optional[Span] = None
10090

10191
def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
10292
"""
@@ -110,29 +100,10 @@ def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None
110100
if not span.context or not span.context.trace_flags.sampled:
111101
return
112102

113-
# Get the span kind from attributes
114-
span_kind = (
115-
span.attributes.get(semconv.SpanAttributes.AGENTOPS_SPAN_KIND, "unknown") if span.attributes else "unknown"
116-
)
117-
118-
# Print basic information about the span
119-
logger.debug(f"Started span: {span.name} (kind: {span_kind})")
120-
121-
# Special handling for session spans
122-
if span_kind == semconv.SpanKind.SESSION:
123-
trace_id = span.context.trace_id
124-
# Convert trace_id to hex string if it's not already
125-
if isinstance(trace_id, int):
126-
session_url = f"{self.app_url}/drilldown?session_id={trace_id_to_uuid(trace_id)}"
127-
logger.info(
128-
colored(
129-
f"\x1b[34mSession started: {session_url}\x1b[0m",
130-
"light_green",
131-
)
132-
)
133-
else:
134-
# Print basic information for other span kinds
135-
logger.debug(f"Ended span: {span.name} (kind: {span_kind})")
103+
if not self._root_span_id:
104+
self._root_span_id = span.context.span_id
105+
logger.debug(f"[agentops.InternalSpanProcessor] Found root span: {span.name}")
106+
log_trace_url(span)
136107

137108
def on_end(self, span: ReadableSpan) -> None:
138109
"""
@@ -145,30 +116,13 @@ def on_end(self, span: ReadableSpan) -> None:
145116
if not span.context or not span.context.trace_flags.sampled:
146117
return
147118

148-
# Get the span kind from attributes
149-
span_kind = (
150-
span.attributes.get(semconv.SpanAttributes.AGENTOPS_SPAN_KIND, "unknown") if span.attributes else "unknown"
151-
)
152-
153-
# Special handling for session spans
154-
if span_kind == semconv.SpanKind.SESSION:
155-
trace_id = span.context.trace_id
156-
# Convert trace_id to hex string if it's not already
157-
if isinstance(trace_id, int):
158-
session_url = f"{self.app_url}/drilldown?session_id={trace_id_to_uuid(trace_id)}"
159-
logger.info(
160-
colored(
161-
f"\x1b[34mSession Replay: {session_url}\x1b[0m",
162-
"blue",
163-
)
164-
)
165-
else:
166-
# Print basic information for other span kinds
167-
logger.debug(f"Ended span: {span.name} (kind: {span_kind})")
119+
if self._root_span_id and (span.context.span_id is self._root_span_id):
120+
logger.debug(f"[agentops.InternalSpanProcessor] Ending root span: {span.name}")
121+
log_trace_url(span)
168122

169123
def shutdown(self) -> None:
170124
"""Shutdown the processor."""
171-
pass
125+
self._root_span_id = None
172126

173127
def force_flush(self, timeout_millis: int = 30000) -> bool:
174128
"""Force flush the processor."""

0 commit comments

Comments
 (0)