Skip to content

Commit 39b5453

Browse files
authored
Add support for customizing config for A365 (#98)
* Add support for customizing config for A365 * Fix lint
1 parent fc05a66 commit 39b5453

5 files changed

Lines changed: 147 additions & 1 deletion

File tree

MIGRATION_A365.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ use_microsoft_opentelemetry(
257257
a365_suppress_invoke_agent_input=True,
258258
a365_enable_observability_exporter=True,
259259
a365_observability_scope_override="api://<app-id>/.default",
260+
a365_max_queue_size=4096,
261+
a365_scheduled_delay_ms=2000,
262+
a365_exporter_timeout_ms=15000,
263+
a365_max_export_batch_size=256,
260264
)
261265
```
262266

@@ -275,6 +279,10 @@ use_microsoft_opentelemetry(
275279
| `cluster_category` | `a365_cluster_category` kwarg or `A365_CLUSTER_CATEGORY` env var |
276280
| `exporter_options` | Individual kwargs or env vars (see below) |
277281
| `suppress_invoke_agent_input` | `a365_suppress_invoke_agent_input` kwarg or `A365_SUPPRESS_INVOKE_AGENT_INPUT` env var |
282+
| `exporter_options.max_queue_size` | `a365_max_queue_size` kwarg |
283+
| `exporter_options.scheduled_delay_ms` | `a365_scheduled_delay_ms` kwarg |
284+
| `exporter_options.exporter_timeout_ms` | `a365_exporter_timeout_ms` kwarg |
285+
| `exporter_options.max_export_batch_size` | `a365_max_export_batch_size` kwarg |
278286
| _(env var only previously)_ `ENABLE_A365_OBSERVABILITY_EXPORTER` | `a365_enable_observability_exporter` kwarg or `ENABLE_A365_OBSERVABILITY_EXPORTER` env var |
279287
| _(env var only previously)_ `A365_OBSERVABILITY_SCOPE_OVERRIDE` | `a365_observability_scope_override` kwarg or `A365_OBSERVABILITY_SCOPE_OVERRIDE` env var |
280288

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ See the [A365 guide](docs/A365_DOCUMENTATION.md) for A365-specific configuration
110110
| `a365_suppress_invoke_agent_input` | `bool` | `False` | Strip input messages from InvokeAgent spans. |
111111
| `a365_enable_observability_exporter` | `bool` | `None` | Enable the A365 HTTP exporter. Also read from `ENABLE_A365_OBSERVABILITY_EXPORTER` env var. Defaults to `false` when neither is set. |
112112
| `a365_observability_scope_override` | `str` | `False` | Override the default Entra scope used by the built-in token resolvers. Also read from `A365_OBSERVABILITY_SCOPE_OVERRIDE`. |
113+
| `a365_max_queue_size` | `int` | `2048` | Maximum queue size for the A365 batch span processor. |
114+
| `a365_scheduled_delay_ms` | `int` | `5000` | Delay between A365 export batches (ms). |
115+
| `a365_exporter_timeout_ms` | `int` | `30000` | Timeout for a single A365 export operation (ms). |
116+
| `a365_max_export_batch_size` | `int` | `512` | Maximum batch size for a single A365 export operation. |
113117

114118
> For A365 token resolver patterns, baggage, and scope classes, see the [A365 guide](docs/A365_DOCUMENTATION.md).
115119

src/microsoft/opentelemetry/_constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,7 @@
104104
A365_SUPPRESS_INVOKE_AGENT_INPUT_ARG = "a365_suppress_invoke_agent_input"
105105
A365_ENABLE_OBSERVABILITY_EXPORTER_ARG = "a365_enable_observability_exporter"
106106
A365_OBSERVABILITY_SCOPE_OVERRIDE_ARG = "a365_observability_scope_override"
107+
A365_MAX_QUEUE_SIZE_ARG = "a365_max_queue_size"
108+
A365_SCHEDULED_DELAY_MS_ARG = "a365_scheduled_delay_ms"
109+
A365_EXPORTER_TIMEOUT_MS_ARG = "a365_exporter_timeout_ms"
110+
A365_MAX_EXPORT_BATCH_SIZE_ARG = "a365_max_export_batch_size"

src/microsoft/opentelemetry/_distro.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
A365_SUPPRESS_INVOKE_AGENT_INPUT_ARG,
4040
A365_ENABLE_OBSERVABILITY_EXPORTER_ARG,
4141
A365_OBSERVABILITY_SCOPE_OVERRIDE_ARG,
42+
A365_MAX_QUEUE_SIZE_ARG,
43+
A365_SCHEDULED_DELAY_MS_ARG,
44+
A365_EXPORTER_TIMEOUT_MS_ARG,
45+
A365_MAX_EXPORT_BATCH_SIZE_ARG,
4246
ENABLE_AZURE_MONITOR_ARG,
4347
ENABLE_CONSOLE_ARG,
4448
INSTRUMENTATION_OPTIONS_ARG,
@@ -136,6 +140,18 @@ def use_microsoft_opentelemetry(**kwargs: object) -> None: # pylint: disable=to
136140
A365 observability service. Equivalent to setting the
137141
``A365_OBSERVABILITY_SCOPE_OVERRIDE`` environment variable. When provided,
138142
this kwarg overrides the env var.
143+
:keyword int a365_max_queue_size:
144+
Maximum queue size for the A365 batch span processor. Defaults to 2048
145+
when omitted (BatchSpanProcessor default).
146+
:keyword int a365_scheduled_delay_ms:
147+
Delay between A365 export batches in milliseconds. Defaults to 5000
148+
when omitted (BatchSpanProcessor default).
149+
:keyword int a365_exporter_timeout_ms:
150+
Timeout for a single A365 export operation in milliseconds. Defaults to
151+
30000 when omitted (BatchSpanProcessor default).
152+
:keyword int a365_max_export_batch_size:
153+
Maximum batch size for a single A365 export operation. Defaults to 512
154+
when omitted (BatchSpanProcessor default).
139155
:keyword bool enable_console:
140156
Enable console exporter for traces, metrics, and logs (development
141157
only). Mirrors ``ExportTarget.Console`` from the .NET distro.
@@ -167,6 +183,10 @@ def use_microsoft_opentelemetry(**kwargs: object) -> None: # pylint: disable=to
167183
a365_suppress_invoke_agent_input = kwargs.pop(A365_SUPPRESS_INVOKE_AGENT_INPUT_ARG, None)
168184
a365_enable_observability_exporter = kwargs.pop(A365_ENABLE_OBSERVABILITY_EXPORTER_ARG, None)
169185
a365_observability_scope_override = kwargs.pop(A365_OBSERVABILITY_SCOPE_OVERRIDE_ARG, None)
186+
a365_max_queue_size = kwargs.pop(A365_MAX_QUEUE_SIZE_ARG, None)
187+
a365_scheduled_delay_ms = kwargs.pop(A365_SCHEDULED_DELAY_MS_ARG, None)
188+
a365_exporter_timeout_ms = kwargs.pop(A365_EXPORTER_TIMEOUT_MS_ARG, None)
189+
a365_max_export_batch_size = kwargs.pop(A365_MAX_EXPORT_BATCH_SIZE_ARG, None)
170190

171191
enable_spectra: bool = bool(kwargs.pop(ENABLE_SPECTRA_ARG, False))
172192
spectra_endpoint = kwargs.pop(SPECTRA_ENDPOINT_ARG, None)
@@ -218,6 +238,10 @@ def use_microsoft_opentelemetry(**kwargs: object) -> None: # pylint: disable=to
218238
suppress_invoke_agent_input=a365_suppress_invoke_agent_input,
219239
enable_observability_exporter=a365_enable_observability_exporter,
220240
observability_scope_override=a365_observability_scope_override,
241+
max_queue_size=a365_max_queue_size,
242+
scheduled_delay_ms=a365_scheduled_delay_ms,
243+
exporter_timeout_ms=a365_exporter_timeout_ms,
244+
max_export_batch_size=a365_max_export_batch_size,
221245
)
222246

223247
# ---- Console exporters (dev-only, mirrors ExportTarget.Console) ----
@@ -287,6 +311,10 @@ def _append_a365_components(
287311
suppress_invoke_agent_input: Any = None,
288312
enable_observability_exporter: Any = None,
289313
observability_scope_override: Any = None,
314+
max_queue_size: Any = None,
315+
scheduled_delay_ms: Any = None,
316+
exporter_timeout_ms: Any = None,
317+
max_export_batch_size: Any = None,
290318
) -> None:
291319
"""Build and append Agent365 span processors to ``otel_kwargs``.
292320
@@ -370,10 +398,23 @@ def _append_a365_components(
370398
use_s2s_endpoint=resolved_use_s2s,
371399
)
372400

373-
# Enriching batch processor wrapping the exporter
401+
# Enriching batch processor wrapping the exporter.
402+
# Only forward batch parameters when the user explicitly supplied
403+
# them so that BatchSpanProcessor uses its own defaults otherwise.
404+
batch_kwargs: Dict[str, Any] = {}
405+
if max_queue_size is not None:
406+
batch_kwargs["max_queue_size"] = max_queue_size
407+
if scheduled_delay_ms is not None:
408+
batch_kwargs["schedule_delay_millis"] = scheduled_delay_ms
409+
if exporter_timeout_ms is not None:
410+
batch_kwargs["export_timeout_millis"] = exporter_timeout_ms
411+
if max_export_batch_size is not None:
412+
batch_kwargs["max_export_batch_size"] = max_export_batch_size
413+
374414
batch_processor = _EnrichingBatchSpanProcessor(
375415
exporter,
376416
suppress_invoke_agent_input=resolved_suppress_input,
417+
**batch_kwargs,
377418
)
378419

379420
otel_kwargs[SPAN_PROCESSORS_ARG].append(batch_processor)

tests/test_distro.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ def token_fn(aid, tid):
387387
a365_suppress_invoke_agent_input=True,
388388
a365_enable_observability_exporter=False,
389389
a365_observability_scope_override="api://custom-scope/.default",
390+
a365_max_queue_size=5000,
391+
a365_scheduled_delay_ms=2000,
392+
a365_exporter_timeout_ms=13000,
393+
a365_max_export_batch_size=534,
390394
)
391395
a365_mock.assert_called_once()
392396
_, kwargs = a365_mock.call_args
@@ -396,6 +400,10 @@ def token_fn(aid, tid):
396400
self.assertEqual(kwargs["suppress_invoke_agent_input"], True)
397401
self.assertEqual(kwargs["enable_observability_exporter"], False)
398402
self.assertEqual(kwargs["observability_scope_override"], "api://custom-scope/.default")
403+
self.assertEqual(kwargs["max_queue_size"], 5000)
404+
self.assertEqual(kwargs["scheduled_delay_ms"], 2000)
405+
self.assertEqual(kwargs["exporter_timeout_ms"], 13000)
406+
self.assertEqual(kwargs["max_export_batch_size"], 534)
399407

400408
@patch("microsoft.opentelemetry._distro._append_a365_components")
401409
def test_a365_not_called_when_disabled(self, a365_mock):
@@ -682,6 +690,87 @@ def test_observability_scope_override_defaults_to_none(self, default_resolver_mo
682690
default_resolver_mock.assert_called_once_with(scope_override=None)
683691

684692

693+
class TestA365BatchProcessorKwargs(unittest.TestCase):
694+
"""Tests for a365_max_queue_size / scheduled_delay_ms / exporter_timeout_ms /
695+
max_export_batch_size kwargs forwarding to _EnrichingBatchSpanProcessor."""
696+
697+
def _build(self, **kwargs):
698+
"""Run _append_a365_components with the exporter enabled and return the
699+
captured kwargs that were passed to _EnrichingBatchSpanProcessor."""
700+
with (
701+
patch(
702+
"microsoft.opentelemetry.a365.core.exporters.utils._create_default_token_resolver",
703+
return_value=lambda aid, tid: "token",
704+
),
705+
patch("microsoft.opentelemetry.a365.core.exporters.agent365_exporter._Agent365Exporter"),
706+
patch(
707+
"microsoft.opentelemetry.a365.core.exporters.enriching_span_processor._EnrichingBatchSpanProcessor"
708+
) as proc_mock,
709+
):
710+
otel_kwargs = {"span_processors": []}
711+
_append_a365_components(
712+
True,
713+
otel_kwargs,
714+
enable_observability_exporter=True,
715+
**kwargs,
716+
)
717+
proc_mock.assert_called_once()
718+
return proc_mock.call_args.kwargs
719+
720+
def test_batch_kwargs_omitted_when_user_does_not_pass_them(self):
721+
"""When the user passes none of the batch kwargs, the processor is
722+
constructed without any of them so BatchSpanProcessor uses its own
723+
defaults."""
724+
proc_kwargs = self._build()
725+
self.assertNotIn("max_queue_size", proc_kwargs)
726+
self.assertNotIn("schedule_delay_millis", proc_kwargs)
727+
self.assertNotIn("export_timeout_millis", proc_kwargs)
728+
self.assertNotIn("max_export_batch_size", proc_kwargs)
729+
730+
def test_max_queue_size_forwarded(self):
731+
proc_kwargs = self._build(max_queue_size=4096)
732+
self.assertEqual(proc_kwargs["max_queue_size"], 4096)
733+
self.assertNotIn("schedule_delay_millis", proc_kwargs)
734+
self.assertNotIn("export_timeout_millis", proc_kwargs)
735+
self.assertNotIn("max_export_batch_size", proc_kwargs)
736+
737+
def test_scheduled_delay_ms_forwarded_with_renamed_key(self):
738+
"""a365_scheduled_delay_ms maps to BatchSpanProcessor's schedule_delay_millis."""
739+
proc_kwargs = self._build(scheduled_delay_ms=2000)
740+
self.assertEqual(proc_kwargs["schedule_delay_millis"], 2000)
741+
self.assertNotIn("scheduled_delay_ms", proc_kwargs)
742+
743+
def test_exporter_timeout_ms_forwarded_with_renamed_key(self):
744+
"""a365_exporter_timeout_ms maps to BatchSpanProcessor's export_timeout_millis."""
745+
proc_kwargs = self._build(exporter_timeout_ms=15000)
746+
self.assertEqual(proc_kwargs["export_timeout_millis"], 15000)
747+
self.assertNotIn("exporter_timeout_ms", proc_kwargs)
748+
749+
def test_max_export_batch_size_forwarded(self):
750+
proc_kwargs = self._build(max_export_batch_size=256)
751+
self.assertEqual(proc_kwargs["max_export_batch_size"], 256)
752+
753+
def test_all_batch_kwargs_forwarded_together(self):
754+
proc_kwargs = self._build(
755+
max_queue_size=4096,
756+
scheduled_delay_ms=2000,
757+
exporter_timeout_ms=15000,
758+
max_export_batch_size=256,
759+
)
760+
self.assertEqual(proc_kwargs["max_queue_size"], 4096)
761+
self.assertEqual(proc_kwargs["schedule_delay_millis"], 2000)
762+
self.assertEqual(proc_kwargs["export_timeout_millis"], 15000)
763+
self.assertEqual(proc_kwargs["max_export_batch_size"], 256)
764+
# Always-forwarded enriching kwarg is preserved.
765+
self.assertIn("suppress_invoke_agent_input", proc_kwargs)
766+
767+
def test_zero_value_is_forwarded(self):
768+
"""Falsy-but-not-None integer values must still be forwarded (not dropped)."""
769+
proc_kwargs = self._build(max_queue_size=0, max_export_batch_size=0)
770+
self.assertEqual(proc_kwargs["max_queue_size"], 0)
771+
self.assertEqual(proc_kwargs["max_export_batch_size"], 0)
772+
773+
685774
class TestA365Components(unittest.TestCase):
686775
"""Tests for A365 enable_a365 flag and _append_a365_components."""
687776

0 commit comments

Comments
 (0)