From 72668fbb1765c5ee18b6b94df04bdfbc2d3c54aa Mon Sep 17 00:00:00 2001 From: Shyam Desigan Date: Sat, 20 Jun 2026 16:16:41 +0000 Subject: [PATCH] feat: add log span kind (agentops.log) for arbitrary trace logging --- agentops/__init__.py | 3 ++ agentops/sdk/decorators/log.py | 60 +++++++++++++++++++++++++++++ agentops/semconv/span_attributes.py | 4 ++ agentops/semconv/span_kinds.py | 1 + 4 files changed, 68 insertions(+) create mode 100644 agentops/sdk/decorators/log.py diff --git a/agentops/__init__.py b/agentops/__init__.py index 816e77443..dddfa70ce 100755 --- a/agentops/__init__.py +++ b/agentops/__init__.py @@ -27,6 +27,7 @@ from agentops.client import Client from agentops.sdk.core import TraceContext, tracer from agentops.sdk.decorators import trace, session, agent, task, workflow, operation, tool, guardrail, track_endpoint +from agentops.sdk.decorators.log import log from agentops.enums import TraceState, SUCCESS, ERROR, UNSET from opentelemetry.trace.status import StatusCode @@ -485,4 +486,6 @@ def extract_key_from_attr(attr_value: str) -> str: "validate_trace_spans", "print_validation_summary", "ValidationError", + # Log function + "log", ] diff --git a/agentops/sdk/decorators/log.py b/agentops/sdk/decorators/log.py new file mode 100644 index 000000000..27d891d65 --- /dev/null +++ b/agentops/sdk/decorators/log.py @@ -0,0 +1,60 @@ +""" +Standalone log() function for creating log-style spans in traces. + +Provides `log(name, message, level)` to record arbitrary log entries +as spans with kind="log" for observability and debugging. +""" + +from typing import Optional + +from agentops.logging import logger +from agentops.sdk.core import tracer +from agentops.semconv.span_kinds import AgentOpsSpanKindValues +from agentops.semconv.span_attributes import SpanAttributes + + +LOG_LEVELS = {"DEFAULT", "WARNING", "ERROR"} + + +def log( + name: str, + message: str, + level: str = "DEFAULT", +) -> None: + """ + Create a standalone log-style span with the given name, message, and level. + + This function is designed for ad-hoc logging within traces — e.g., recording + when a tool call limit is reached, when a decision boundary is crossed, or + any other noteworthy point during execution. + + Args: + name: Short identifier for the log entry (e.g. "xml_tool_call_limit_reached"). + message: Human-readable description of the log event. + level: Severity level. One of "DEFAULT", "WARNING", or "ERROR". + Defaults to "DEFAULT". + """ + # Validate level + if level not in LOG_LEVELS: + logger.warning(f"Invalid log level '{level}'. Falling back to 'DEFAULT'.") + level = "DEFAULT" + + if not tracer.initialized: + logger.warning("AgentOps SDK not initialized. Cannot create log span.") + return + + try: + span, ctx, token = tracer.make_span( + name, + AgentOpsSpanKindValues.LOG.value, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.LOG.value, + SpanAttributes.LOG_MESSAGE: message, + SpanAttributes.LOG_LEVEL: level, + }, + ) + span.set_attribute(SpanAttributes.LOG_MESSAGE, message) + span.set_attribute(SpanAttributes.LOG_LEVEL, level) + tracer.finalize_span(span, token) + except Exception as e: + logger.error(f"Failed to create log span: {e}") diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index 2c5f8ce1c..cebfb87dd 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -116,3 +116,7 @@ class SpanAttributes: HTTP_RESPONSE_BODY = "http.response.body" HTTP_USER_AGENT = "http.user_agent" HTTP_REQUEST_ID = "http.request_id" + + # Log-specific attributes + LOG_MESSAGE = "agentops.log.message" + LOG_LEVEL = "agentops.log.level" diff --git a/agentops/semconv/span_kinds.py b/agentops/semconv/span_kinds.py index cb3a0ba36..f62eaab2d 100644 --- a/agentops/semconv/span_kinds.py +++ b/agentops/semconv/span_kinds.py @@ -17,6 +17,7 @@ class AgentOpsSpanKindValues(Enum): TEXT = "text" GUARDRAIL = "guardrail" HTTP = "http" + LOG = "log" UNKNOWN = "unknown"