agnt5 0.2.8a2__cp310-abi3-manylinux_2_34_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of agnt5 might be problematic. Click here for more details.

agnt5/_telemetry.py ADDED
@@ -0,0 +1,167 @@
1
+ """
2
+ OpenTelemetry integration for Python logging.
3
+
4
+ This module bridges Python's standard logging to Rust's tracing/OpenTelemetry system,
5
+ ensuring all logs from ctx.logger are sent to both the console and OTLP exporters.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+
12
+ class OpenTelemetryHandler(logging.Handler):
13
+ """
14
+ Custom logging handler that forwards Python logs to Rust OpenTelemetry system.
15
+
16
+ This handler routes all Python log records through the Rust `log_from_python()`
17
+ function, which integrates with the tracing ecosystem. This ensures:
18
+
19
+ 1. Logs are sent to OpenTelemetry OTLP exporter
20
+ 2. Logs appear in console output (via Rust's fmt layer)
21
+ 3. Logs inherit span context (invocation.id, trace_id, etc.)
22
+ 4. Structured logging with proper attributes
23
+
24
+ The Rust side handles both console output and OTLP export, so we only
25
+ need one handler on the Python side.
26
+ """
27
+
28
+ def __init__(self, level=logging.NOTSET):
29
+ """Initialize the OpenTelemetry handler.
30
+
31
+ Args:
32
+ level: Minimum log level to process (default: NOTSET processes all)
33
+ """
34
+ super().__init__(level)
35
+
36
+ # Import Rust bridge function
37
+ try:
38
+ from ._core import log_from_python
39
+ self._log_from_python = log_from_python
40
+ except ImportError as e:
41
+ # Fallback if Rust core not available (development/testing)
42
+ import warnings
43
+ warnings.warn(
44
+ f"Failed to import Rust telemetry bridge: {e}. "
45
+ "Logs will not be sent to OpenTelemetry.",
46
+ RuntimeWarning
47
+ )
48
+ self._log_from_python = None
49
+
50
+ def emit(self, record: logging.LogRecord):
51
+ """
52
+ Process a log record and forward to Rust telemetry.
53
+
54
+ Args:
55
+ record: Python logging record to process
56
+ """
57
+ if self._log_from_python is None:
58
+ # No Rust bridge available, silently skip
59
+ return
60
+
61
+ # Filter out gRPC internal logs to avoid noise
62
+ # These are low-level HTTP/2 protocol logs that aren't useful for application debugging
63
+ if record.name.startswith(('grpc.', 'h2.', '_grpc_', 'h2-')):
64
+ return
65
+
66
+ try:
67
+ # Format the message (applies any formatters)
68
+ message = self.format(record)
69
+
70
+ # Include exception traceback if present (from logger.exception() or exc_info=True)
71
+ if record.exc_info:
72
+ # formatException() returns the formatted traceback string
73
+ exc_text = self.formatException(record.exc_info)
74
+ message = f"{message}\n{exc_text}"
75
+
76
+ # Forward to Rust tracing system
77
+ # Rust side will:
78
+ # - Add to current span context (inherits invocation.id)
79
+ # - Send to OTLP exporter
80
+ # - Print to console via fmt layer
81
+ self._log_from_python(
82
+ level=record.levelname,
83
+ message=message,
84
+ target=record.name,
85
+ module_path=record.module,
86
+ filename=record.pathname,
87
+ line=record.lineno
88
+ )
89
+ except Exception:
90
+ # Don't let logging errors crash the application
91
+ # Use handleError to report the issue via logging system
92
+ self.handleError(record)
93
+
94
+
95
+ def setup_context_logger(logger: logging.Logger, log_level: Optional[int] = None) -> None:
96
+ """
97
+ Configure a Context logger with OpenTelemetry integration.
98
+
99
+ This function:
100
+ 1. Removes any existing handlers (avoid duplicates)
101
+ 2. Adds OpenTelemetry handler for OTLP + console output (when Worker is running)
102
+ 3. Adds console handler for local testing (fallback)
103
+ 4. Sets appropriate log level
104
+ 5. Disables propagation to avoid duplicate logs
105
+
106
+ Args:
107
+ logger: Logger instance to configure
108
+ log_level: Optional log level (default: DEBUG)
109
+ """
110
+ # Remove existing handlers to avoid duplicate logs
111
+ logger.handlers.clear()
112
+
113
+ # Add OpenTelemetry handler (for Worker/platform execution)
114
+ otel_handler = OpenTelemetryHandler()
115
+ otel_handler.setLevel(logging.DEBUG)
116
+
117
+ # Use simple formatter - Rust side handles structured logging
118
+ formatter = logging.Formatter('%(message)s')
119
+ otel_handler.setFormatter(formatter)
120
+
121
+ logger.addHandler(otel_handler)
122
+
123
+ # Add console handler for local testing (fallback when Rust bridge not available)
124
+ # This ensures logs appear when testing functions locally without Worker
125
+ console_handler = logging.StreamHandler()
126
+ console_handler.setLevel(logging.DEBUG)
127
+
128
+ # Console format includes level, message, and exception info if present
129
+ # exc_info=True in the format string means "include traceback if present"
130
+ console_formatter = logging.Formatter(
131
+ '[%(levelname)s] %(message)s',
132
+ # Python automatically appends exception traceback when exc_info is set
133
+ )
134
+ console_handler.setFormatter(console_formatter)
135
+
136
+ logger.addHandler(console_handler)
137
+
138
+ # Set log level (default to DEBUG to let handlers filter)
139
+ if log_level is None:
140
+ log_level = logging.DEBUG
141
+ logger.setLevel(log_level)
142
+
143
+ # Don't propagate to root logger (we handle everything ourselves)
144
+ logger.propagate = False
145
+
146
+
147
+ def setup_module_logger(module_name: str, log_level: Optional[int] = None) -> logging.Logger:
148
+ """
149
+ Create and configure a logger for a module with OpenTelemetry integration.
150
+
151
+ Convenience function for setting up loggers in SDK modules.
152
+
153
+ Args:
154
+ module_name: Name of the module (e.g., "agnt5.worker")
155
+ log_level: Optional log level (default: INFO for modules)
156
+
157
+ Returns:
158
+ Configured logger instance
159
+ """
160
+ logger = logging.getLogger(module_name)
161
+
162
+ # For module loggers, default to INFO level
163
+ if log_level is None:
164
+ log_level = logging.INFO
165
+
166
+ setup_context_logger(logger, log_level)
167
+ return logger