microsoft-agents-a365-observability-core 0.2.1.dev36__tar.gz → 0.2.1.dev42__tar.gz

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.
Files changed (47) hide show
  1. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/PKG-INFO +1 -1
  2. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/exporters/agent365_exporter.py +26 -13
  3. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/exporters/utils.py +34 -0
  4. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/opentelemetry_scope.py +1 -16
  5. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/spans_scopes/output_scope.py +5 -0
  6. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365_observability_core.egg-info/PKG-INFO +1 -1
  7. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/README.md +0 -0
  8. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/__init__.py +0 -0
  9. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/agent_details.py +0 -0
  10. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/config.py +0 -0
  11. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/constants.py +0 -0
  12. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/execute_tool_scope.py +0 -0
  13. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/execution_type.py +0 -0
  14. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/exporters/__init__.py +0 -0
  15. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/exporters/agent365_exporter_options.py +0 -0
  16. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/exporters/enriched_span.py +0 -0
  17. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/exporters/enriching_span_processor.py +0 -0
  18. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/exporters/spectra_exporter_options.py +0 -0
  19. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/inference_call_details.py +0 -0
  20. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/inference_operation_type.py +0 -0
  21. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/inference_scope.py +0 -0
  22. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/invoke_agent_details.py +0 -0
  23. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/invoke_agent_scope.py +0 -0
  24. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/middleware/__init__.py +0 -0
  25. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/middleware/baggage_builder.py +0 -0
  26. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/models/__init__.py +0 -0
  27. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/models/agent_type.py +0 -0
  28. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/models/caller_details.py +0 -0
  29. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/models/operation_source.py +0 -0
  30. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/models/response.py +0 -0
  31. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/request.py +0 -0
  32. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/source_metadata.py +0 -0
  33. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/spans_scopes/__init__.py +0 -0
  34. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/tenant_details.py +0 -0
  35. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/tool_call_details.py +0 -0
  36. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/tool_type.py +0 -0
  37. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/trace_processor/__init__.py +0 -0
  38. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/trace_processor/span_processor.py +0 -0
  39. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/trace_processor/util.py +0 -0
  40. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365/observability/core/utils.py +0 -0
  41. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365_observability_core.egg-info/SOURCES.txt +0 -0
  42. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365_observability_core.egg-info/dependency_links.txt +0 -0
  43. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365_observability_core.egg-info/requires.txt +0 -0
  44. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/microsoft_agents_a365_observability_core.egg-info/top_level.txt +0 -0
  45. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/pyproject.toml +0 -0
  46. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/setup.cfg +0 -0
  47. {microsoft_agents_a365_observability_core-0.2.1.dev36 → microsoft_agents_a365_observability_core-0.2.1.dev42}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-a365-observability-core
3
- Version: 0.2.1.dev36
3
+ Version: 0.2.1.dev42
4
4
  Summary: Telemetry, tracing, and monitoring components for AI agents
5
5
  Author-email: Microsoft <support@microsoft.com>
6
6
  License: MIT
@@ -23,6 +23,7 @@ from .utils import (
23
23
  hex_span_id,
24
24
  hex_trace_id,
25
25
  kind_name,
26
+ parse_retry_after,
26
27
  partition_by_identity,
27
28
  status_name,
28
29
  truncate_span,
@@ -79,9 +80,9 @@ class _Agent365Exporter(SpanExporter):
79
80
  logger.info("No spans with tenant/agent identity found; nothing exported.")
80
81
  return SpanExportResult.SUCCESS
81
82
 
82
- # Debug: Log number of groups and total span count
83
+ # Log number of groups and total span count
83
84
  total_spans = sum(len(activities) for activities in groups.values())
84
- logger.info(
85
+ logger.debug(
85
86
  f"Found {len(groups)} identity groups with {total_spans} total spans to export"
86
87
  )
87
88
 
@@ -98,8 +99,8 @@ class _Agent365Exporter(SpanExporter):
98
99
 
99
100
  url = build_export_url(endpoint, agent_id, tenant_id, self._use_s2s_endpoint)
100
101
 
101
- # Debug: Log endpoint being used
102
- logger.info(
102
+ # Log endpoint details at DEBUG to avoid leaking IDs in production logs
103
+ logger.debug(
103
104
  f"Exporting {len(activities)} spans to endpoint: {url} "
104
105
  f"(tenant: {tenant_id}, agent: {agent_id})"
105
106
  )
@@ -108,10 +109,16 @@ class _Agent365Exporter(SpanExporter):
108
109
  try:
109
110
  token = self._token_resolver(agent_id, tenant_id)
110
111
  if token:
112
+ # Warn if sending bearer token over non-HTTPS connection
113
+ if not url.lower().startswith("https://"):
114
+ logger.warning(
115
+ "Bearer token is being sent over a non-HTTPS connection. "
116
+ "This may expose credentials in transit."
117
+ )
111
118
  headers["authorization"] = f"Bearer {token}"
112
- logger.info(f"Token resolved successfully for agent {agent_id}")
119
+ logger.debug(f"Token resolved successfully for agent {agent_id}")
113
120
  else:
114
- logger.info(f"No token returned for agent {agent_id}")
121
+ logger.debug(f"No token returned for agent {agent_id}")
115
122
  except Exception as e:
116
123
  # If token resolution fails, treat as failure for this group
117
124
  logger.error(
@@ -174,7 +181,7 @@ class _Agent365Exporter(SpanExporter):
174
181
 
175
182
  # 2xx => success
176
183
  if 200 <= resp.status_code < 300:
177
- logger.info(
184
+ logger.debug(
178
185
  f"HTTP {resp.status_code} success on attempt {attempt + 1}. "
179
186
  f"Correlation ID: {correlation_id}. "
180
187
  f"Response: {self._truncate_text(resp.text, 200)}"
@@ -186,12 +193,19 @@ class _Agent365Exporter(SpanExporter):
186
193
 
187
194
  # Retry transient
188
195
  if resp.status_code in (408, 429) or 500 <= resp.status_code < 600:
196
+ # Respect Retry-After header for 429 responses
197
+ retry_after = parse_retry_after(resp.headers)
189
198
  if attempt < DEFAULT_MAX_RETRIES:
190
- time.sleep(0.2 * (attempt + 1))
199
+ if retry_after is not None:
200
+ time.sleep(min(retry_after, 60.0))
201
+ else:
202
+ # Exponential backoff with base 0.5s
203
+ time.sleep(0.5 * (2**attempt))
191
204
  continue
192
205
  # Final attempt failed
193
206
  logger.error(
194
- f"HTTP {resp.status_code} final failure after {DEFAULT_MAX_RETRIES + 1} attempts. "
207
+ f"HTTP {resp.status_code} final failure after "
208
+ f"{DEFAULT_MAX_RETRIES + 1} attempts. "
195
209
  f"Correlation ID: {correlation_id}. "
196
210
  f"Response: {response_text}"
197
211
  )
@@ -206,12 +220,11 @@ class _Agent365Exporter(SpanExporter):
206
220
 
207
221
  except requests.RequestException as e:
208
222
  if attempt < DEFAULT_MAX_RETRIES:
209
- time.sleep(0.2 * (attempt + 1))
223
+ # Exponential backoff with base 0.5s
224
+ time.sleep(0.5 * (2**attempt))
210
225
  continue
211
226
  # Final attempt failed
212
- logger.error(
213
- f"Request failed after {DEFAULT_MAX_RETRIES + 1} attempts with exception: {e}"
214
- )
227
+ logger.error(f"Request failed after {DEFAULT_MAX_RETRIES + 1} attempts: {e}")
215
228
  return False
216
229
  return False
217
230
 
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) Microsoft Corporation.
2
2
  # Licensed under the MIT License.
3
3
 
4
+ from __future__ import annotations
5
+
4
6
  import json
5
7
  import logging
6
8
  import os
@@ -194,6 +196,13 @@ def get_validated_domain_override() -> str | None:
194
196
  logger.warning(f"Invalid domain override '{domain_override}': {e}")
195
197
  return None
196
198
 
199
+ # Warn when using insecure HTTP — telemetry data and bearer tokens may be exposed
200
+ if domain_override.lower().startswith("http://"):
201
+ logger.warning(
202
+ "Domain override uses insecure HTTP. Telemetry data (including "
203
+ "bearer tokens) will be transmitted in cleartext."
204
+ )
205
+
197
206
  return domain_override
198
207
 
199
208
 
@@ -223,6 +232,31 @@ def build_export_url(
223
232
  return f"https://{endpoint}{endpoint_path}?api-version=1"
224
233
 
225
234
 
235
+ def parse_retry_after(headers: dict[str, str]) -> float | None:
236
+ """Parse the ``Retry-After`` header value.
237
+
238
+ Only numeric (seconds) values are supported. HTTP-date values
239
+ (e.g. ``Wed, 21 Oct 2025 07:28:00 GMT``) are intentionally ignored
240
+ and treated as absent, falling back to exponential backoff.
241
+
242
+ Args:
243
+ headers: Response headers mapping.
244
+
245
+ Returns:
246
+ The number of seconds to wait, or ``None`` if the header is
247
+ absent, non-numeric, or otherwise invalid.
248
+ """
249
+ retry_after = headers.get("Retry-After")
250
+ if retry_after is None:
251
+ return None
252
+ try:
253
+ return float(retry_after)
254
+ except (ValueError, TypeError):
255
+ # Intentionally ignore HTTP-date formatted Retry-After values;
256
+ # callers should fall back to exponential backoff.
257
+ return None
258
+
259
+
226
260
  def is_agent365_exporter_enabled() -> bool:
227
261
  """Check if Agent 365 exporter is enabled."""
228
262
  # Check environment variable
@@ -9,7 +9,7 @@ from datetime import datetime
9
9
  from threading import Lock
10
10
  from typing import TYPE_CHECKING, Any
11
11
 
12
- from opentelemetry import baggage, context, trace
12
+ from opentelemetry import context, trace
13
13
  from opentelemetry.trace import (
14
14
  Span,
15
15
  SpanKind,
@@ -241,21 +241,6 @@ class OpenTelemetryScope:
241
241
  if value is not None and self._span and self._is_telemetry_enabled():
242
242
  self._span.set_attribute(name, value)
243
243
 
244
- def add_baggage(self, key: str, value: str) -> None:
245
- """Add baggage to the current context.
246
-
247
- Args:
248
- key: The baggage key
249
- value: The baggage value
250
- """
251
- # Set baggage in the current context
252
- if self._is_telemetry_enabled():
253
- # Set baggage on the current context
254
- # This will be inherited by child spans created within this context
255
- baggage_context = baggage.set_baggage(key, value)
256
- # The context needs to be made current for child spans to inherit the baggage
257
- context.attach(baggage_context)
258
-
259
244
  def record_attributes(self, attributes: dict[str, Any] | list[tuple[str, Any]]) -> None:
260
245
  """Record multiple attribute key/value pairs for telemetry tracking.
261
246
 
@@ -16,6 +16,8 @@ OUTPUT_OPERATION_NAME = "output_messages"
16
16
  class OutputScope(OpenTelemetryScope):
17
17
  """Provides OpenTelemetry tracing scope for output messages."""
18
18
 
19
+ _MAX_OUTPUT_MESSAGES = 5000
20
+
19
21
  @staticmethod
20
22
  def start(
21
23
  agent_details: AgentDetails,
@@ -82,9 +84,12 @@ class OutputScope(OpenTelemetryScope):
82
84
  """Records the output messages for telemetry tracking.
83
85
 
84
86
  Appends the provided messages to the accumulated output messages list.
87
+ The list is capped at _MAX_OUTPUT_MESSAGES to prevent unbounded memory growth.
85
88
 
86
89
  Args:
87
90
  messages: List of output messages to append
88
91
  """
89
92
  self._output_messages.extend(messages)
93
+ if len(self._output_messages) > self._MAX_OUTPUT_MESSAGES:
94
+ self._output_messages = self._output_messages[-self._MAX_OUTPUT_MESSAGES :]
90
95
  self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, safe_json_dumps(self._output_messages))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-a365-observability-core
3
- Version: 0.2.1.dev36
3
+ Version: 0.2.1.dev42
4
4
  Summary: Telemetry, tracing, and monitoring components for AI agents
5
5
  Author-email: Microsoft <support@microsoft.com>
6
6
  License: MIT