Skip to content

Commit 7769e93

Browse files
authored
Merge branch 'main' into support_otel_traces_sampler
2 parents 0e09ec7 + 62861a2 commit 7769e93

File tree

15 files changed

+296
-18
lines changed

15 files changed

+296
-18
lines changed

sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
### Features Added
66

7+
- Added support for custom authentication audiences via the `audience` keyword argument in `AzureAppConfigurationClient` constructor to enable authentication against sovereign clouds.
8+
79
### Breaking Changes
810

911
### Bugs Fixed
1012

1113
### Other Changes
1214

1315
- Replaced deprecated `datetime.utcnow()` with timezone-aware `datetime.now(timezone.utc)`.
16+
- Improved authentication scope handling to automatically detect and use correct audience URLs for Azure Public Cloud, Azure US Government, and Azure China cloud environments.
1417

1518
## 1.7.2 (2025-10-20)
1619

sdk/appconfiguration/azure-appconfiguration/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/appconfiguration/azure-appconfiguration",
5-
"Tag": "python/appconfiguration/azure-appconfiguration_710e235678"
5+
"Tag": "python/appconfiguration/azure-appconfiguration_5a7879bd17"
66
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# ------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for
4+
# license information.
5+
# -------------------------------------------------------------------------
6+
7+
# Azure Configuration audience URLs
8+
DEFAULT_SCOPE_SUFFIX = ".default"
9+
_AZURE_PUBLIC_CLOUD_AUDIENCE = "https://appconfig.azure.com/"
10+
_AZURE_US_GOVERNMENT_AUDIENCE = "https://appconfig.azure.us/"
11+
_AZURE_CHINA_AUDIENCE = "https://appconfig.azure.cn/"
12+
13+
14+
# Endpoint suffixes for cloud detection
15+
_US_GOVERNMENT_SUFFIX_LEGACY = "azconfig.azure.us" # cspell:disable-line
16+
_US_GOVERNMENT_SUFFIX = "appconfig.azure.us"
17+
_CHINA_SUFFIX_LEGACY = "azconfig.azure.cn" # cspell:disable-line
18+
_CHINA_SUFFIX = "appconfig.azure.cn"
19+
20+
21+
def get_audience(endpoint: str) -> str:
22+
"""
23+
Gets the default audience for the given endpoint.
24+
25+
:param endpoint: The endpoint to get the default audience for.
26+
:type endpoint: str
27+
:return: The default audience for the given endpoint.
28+
:rtype: str
29+
"""
30+
# Normalize endpoint by stripping trailing slashes and converting to lowercase for suffix checks
31+
normalized_endpoint = endpoint.rstrip("/").lower()
32+
if normalized_endpoint.endswith(_US_GOVERNMENT_SUFFIX_LEGACY) or normalized_endpoint.endswith(
33+
_US_GOVERNMENT_SUFFIX
34+
):
35+
return _AZURE_US_GOVERNMENT_AUDIENCE
36+
if normalized_endpoint.endswith(_CHINA_SUFFIX_LEGACY) or normalized_endpoint.endswith(_CHINA_SUFFIX):
37+
return _AZURE_CHINA_AUDIENCE
38+
return _AZURE_PUBLIC_CLOUD_AUDIENCE

sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
ConfigurationSnapshot,
3333
ConfigurationSettingLabel,
3434
)
35+
from ._audience import get_audience, DEFAULT_SCOPE_SUFFIX
3536
from ._utils import (
3637
get_key_filter,
3738
get_label_filter,
@@ -49,6 +50,9 @@ class AzureAppConfigurationClient:
4950
:keyword api_version: Api Version. Default value is "2023-11-01". Note that overriding this default
5051
value may result in unsupported behavior.
5152
:paramtype api_version: str
53+
:keyword audience: The audience to use for authentication with Microsoft Entra. Defaults to the public Azure App
54+
Configuration audience. See the supported audience list at https://aka.ms/appconfig/client-token-audience
55+
:paramtype audience: str
5256
5357
"""
5458

@@ -65,11 +69,9 @@ def __init__(self, base_url: str, credential: TokenCredential, **kwargs: Any) ->
6569

6670
self._sync_token_policy = SyncTokenPolicy()
6771

68-
credential_scopes = kwargs.pop("credential_scopes", [f"{base_url.strip('/')}/.default"])
69-
# Ensure all scopes end with /.default
70-
kwargs["credential_scopes"] = [
71-
scope if scope.endswith("/.default") else f"{scope}/.default" for scope in credential_scopes
72-
]
72+
audience = kwargs.pop("audience", get_audience(base_url))
73+
# Ensure all scopes end with /.default and strip any trailing slashes before adding suffix
74+
kwargs["credential_scopes"] = [audience + DEFAULT_SCOPE_SUFFIX]
7375

7476
if isinstance(credential, AzureKeyCredential):
7577
id_credential = kwargs.pop("id_credential")
@@ -81,7 +83,9 @@ def __init__(self, base_url: str, credential: TokenCredential, **kwargs: Any) ->
8183
elif isinstance(credential, TokenCredential):
8284
kwargs.update(
8385
{
84-
"authentication_policy": BearerTokenCredentialPolicy(credential, *credential_scopes, **kwargs),
86+
"authentication_policy": BearerTokenCredentialPolicy(
87+
credential, *kwargs["credential_scopes"], **kwargs
88+
),
8589
}
8690
)
8791
else:

sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
ConfigurationSnapshot,
3636
ConfigurationSettingLabel,
3737
)
38+
from .._audience import get_audience, DEFAULT_SCOPE_SUFFIX
3839
from .._utils import (
3940
get_key_filter,
4041
get_label_filter,
@@ -51,6 +52,10 @@ class AzureAppConfigurationClient:
5152
:keyword api_version: Api Version. Default value is "2023-11-01". Note that overriding this default
5253
value may result in unsupported behavior.
5354
:paramtype api_version: str
55+
:keyword audience: The audience to use for authentication with Microsoft Entra. Defaults to the public Azure App
56+
Configuration audience. See the supported audience list at https://aka.ms/appconfig/client-token-audience
57+
:paramtype audience: str
58+
5459
5560
This is the async version of :class:`~azure.appconfiguration.AzureAppConfigurationClient`
5661
@@ -69,11 +74,9 @@ def __init__(self, base_url: str, credential: AsyncTokenCredential, **kwargs: An
6974

7075
self._sync_token_policy = AsyncSyncTokenPolicy()
7176

72-
credential_scopes = kwargs.pop("credential_scopes", [f"{base_url.strip('/')}/.default"])
73-
# Ensure all scopes end with /.default
74-
kwargs["credential_scopes"] = [
75-
scope if scope.endswith("/.default") else f"{scope}/.default" for scope in credential_scopes
76-
]
77+
audience = kwargs.pop("audience", get_audience(base_url))
78+
# Ensure all scopes end with /.default and strip any trailing slashes before adding suffix
79+
kwargs["credential_scopes"] = [audience + DEFAULT_SCOPE_SUFFIX]
7780

7881
if isinstance(credential, AzureKeyCredential):
7982
id_credential = kwargs.pop("id_credential")
@@ -85,7 +88,9 @@ def __init__(self, base_url: str, credential: AsyncTokenCredential, **kwargs: An
8588
elif hasattr(credential, "get_token"): # AsyncFakeCredential is not an instance of AsyncTokenCredential
8689
kwargs.update(
8790
{
88-
"authentication_policy": AsyncBearerTokenCredentialPolicy(credential, *credential_scopes, **kwargs),
91+
"authentication_policy": AsyncBearerTokenCredentialPolicy(
92+
credential, *kwargs["credential_scopes"], **kwargs
93+
),
8994
}
9095
)
9196
else:
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# -------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for
4+
# license information.
5+
# --------------------------------------------------------------------------
6+
7+
from azure.appconfiguration._audience import get_audience
8+
9+
# Expected scope constants
10+
_EXPECTED_PUBLIC_CLOUD_AUDIENCE = "https://appconfig.azure.com/"
11+
_EXPECTED_US_GOVERNMENT_AUDIENCE = "https://appconfig.azure.us/"
12+
_EXPECTED_CHINA_AUDIENCE = "https://appconfig.azure.cn/"
13+
14+
15+
class TestGetDefaultScope:
16+
"""Tests for the get_default_scope utility function."""
17+
18+
def test_get_default_scope_public_cloud_legacy(self):
19+
"""Test default scope for Azure public cloud with legacy endpoint."""
20+
endpoint = "https://example1.azconfig.azure.com" # cspell:disable-line
21+
actual_scope = get_audience(endpoint)
22+
assert actual_scope == _EXPECTED_PUBLIC_CLOUD_AUDIENCE
23+
24+
def test_get_default_scope_public_cloud(self):
25+
"""Test default scope for Azure public cloud with regular endpoint."""
26+
endpoint = "https://example1.appconfig.azure.com"
27+
actual_scope = get_audience(endpoint)
28+
assert actual_scope == _EXPECTED_PUBLIC_CLOUD_AUDIENCE
29+
30+
def test_get_default_scope_china_legacy(self):
31+
"""Test default scope for Azure China cloud with legacy endpoint."""
32+
endpoint = "https://example1.azconfig.azure.cn" # cspell:disable-line
33+
actual_scope = get_audience(endpoint)
34+
assert actual_scope == _EXPECTED_CHINA_AUDIENCE
35+
36+
def test_get_default_scope_china(self):
37+
"""Test default scope for Azure China cloud with regular endpoint."""
38+
endpoint = "https://example1.appconfig.azure.cn"
39+
actual_scope = get_audience(endpoint)
40+
assert actual_scope == _EXPECTED_CHINA_AUDIENCE
41+
42+
def test_get_default_scope_us_government_legacy(self):
43+
"""Test default scope for Azure US Government cloud with legacy endpoint."""
44+
endpoint = "https://example1.azconfig.azure.us" # cspell:disable-line
45+
actual_scope = get_audience(endpoint)
46+
assert actual_scope == _EXPECTED_US_GOVERNMENT_AUDIENCE
47+
48+
def test_get_default_scope_us_government(self):
49+
"""Test default scope for Azure US Government cloud with regular endpoint."""
50+
endpoint = "https://example1.appconfig.azure.us"
51+
actual_scope = get_audience(endpoint)
52+
assert actual_scope == _EXPECTED_US_GOVERNMENT_AUDIENCE
53+
54+
def test_default_scope_with_different_subdomain(self):
55+
"""Test that different subdomains still resolve to correct scope."""
56+
endpoint = "https://my-store-123.appconfig.azure.com"
57+
actual_scope = get_audience(endpoint)
58+
assert actual_scope == _EXPECTED_PUBLIC_CLOUD_AUDIENCE
59+
60+
def test_get_default_scope_public_cloud_with_trailing_slash(self):
61+
"""Test default scope for Azure public cloud with trailing slash."""
62+
endpoint = "https://example1.appconfig.azure.com/"
63+
actual_scope = get_audience(endpoint)
64+
assert actual_scope == _EXPECTED_PUBLIC_CLOUD_AUDIENCE
65+
66+
def test_get_default_scope_china_with_trailing_slash(self):
67+
"""Test default scope for Azure China cloud with trailing slash."""
68+
endpoint = "https://example1.appconfig.azure.cn/"
69+
actual_scope = get_audience(endpoint)
70+
assert actual_scope == _EXPECTED_CHINA_AUDIENCE
71+
72+
def test_get_default_scope_us_government_with_trailing_slash(self):
73+
"""Test default scope for Azure US Government cloud with trailing slash."""
74+
endpoint = "https://example1.appconfig.azure.us/"
75+
actual_scope = get_audience(endpoint)
76+
assert actual_scope == _EXPECTED_US_GOVERNMENT_AUDIENCE
77+
78+
def test_get_default_scope_legacy_with_trailing_slash(self):
79+
"""Test default scope for legacy endpoint with trailing slash."""
80+
endpoint = "https://example1.azconfig.azure.us/" # cspell:disable-line
81+
actual_scope = get_audience(endpoint)
82+
assert actual_scope == _EXPECTED_US_GOVERNMENT_AUDIENCE

sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
### Features Added
66
- Added support for OTEL_TRACES_SAMPLER
77
([#44535](https://github.com/Azure/azure-sdk-for-python/pull/44535))
8+
- Added ability to add additional Log Record Processors and Metric Readers via configure_azure_monitor
9+
([#44367](https://github.com/Azure/azure-sdk-for-python/pull/44367))
810

911
### Breaking Changes
1012

sdk/monitor/azure-monitor-opentelemetry/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ You can use `configure_azure_monitor` to set up instrumentation for your app to
6868
| `resource` | Specifies the OpenTelemetry [Resource][ot_spec_resource] associated with your application. Passed in [Resource Attributes][ot_spec_resource_attributes] take priority over default attributes and those from [Resource Detectors][ot_python_resource_detectors]. | [OTEL_SERVICE_NAME][ot_spec_service_name], [OTEL_RESOURCE_ATTRIBUTES][ot_spec_resource_attributes], [OTEL_EXPERIMENTAL_RESOURCE_DETECTORS][ot_python_resource_detectors] |
6969
| `span_processors` | A list of [span processors][ot_span_processor] that will perform processing on each of your spans before they are exported. Useful for filtering/modifying telemetry. | `N/A` |
7070
| `views` | A list of [views][ot_view] that will be used to customize metrics exported by the SDK. | `N/A` |
71+
| `log_record_processors` | A list of [log record processors][ot_log_record_processor] that will process log records before they are exported. | `N/A` |
72+
| `metric_readers` | A list of [metric reader][ot_metric_reader] that will process metric readers before they are exported | `N/A` |
7173
| `traces_per_second` | Configures the Rate Limited sampler by specifying the maximum number of traces to sample per second. When set, this automatically enables the rate-limited sampler. Alternatively, you can configure sampling using the `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG` environment variables as described in the table below. Please note that the sampling configuration via environment variables will have precedence over the sampling exporter/distro options. | `N/A`
7274

7375
You can configure further with [OpenTelemetry environment variables][ot_env_vars].
@@ -232,6 +234,7 @@ contact [[email protected]](mailto:[email protected]) with any additio
232234
[ot_sdk_python]: https://github.com/open-telemetry/opentelemetry-python
233235
[ot_sdk_python_metric_reader]: https://opentelemetry-python.readthedocs.io/en/latest/sdk/metrics.export.html#opentelemetry.sdk.metrics.export.MetricReader
234236
[ot_span_processor]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#span-processor
237+
[ot_log_record_processor]: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/logs/sdk.md#log-record-processor
235238
[ot_view]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view
236239
[ot_sdk_python_view_examples]: https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples/metrics/views
237240
[ot_instrumentation_django]: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-django

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
)
1313
from opentelemetry.metrics import set_meter_provider
1414
from opentelemetry.sdk.metrics import MeterProvider
15-
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
15+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader, MetricReader
1616
from opentelemetry.sdk.metrics.view import View
1717
from opentelemetry.sdk.resources import Resource
1818
from opentelemetry.sdk.trace import TracerProvider
@@ -38,6 +38,8 @@
3838
SAMPLING_RATIO_ARG,
3939
SAMPLING_TRACES_PER_SECOND_ARG,
4040
SPAN_PROCESSORS_ARG,
41+
LOG_RECORD_PROCESSORS_ARG,
42+
METRIC_READERS_ARG,
4143
VIEWS_ARG,
4244
ENABLE_TRACE_BASED_SAMPLING_ARG,
4345
SAMPLING_ARG,
@@ -105,6 +107,10 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758
105107
Attributes take priority over default attributes and those from Resource Detectors.
106108
:keyword list[~opentelemetry.sdk.trace.SpanProcessor] span_processors: List of `SpanProcessor` objects
107109
to process every span prior to exporting. Will be run sequentially.
110+
:keyword list[~opentelemetry.sdk._logs.LogRecordProcessor] log_record_processors: List of `LogRecordProcessor`
111+
objects to process every log record prior to exporting. Will be run sequentially.
112+
:keyword list[~opentelemetry.sdk.metrics.MetricReader] metric_readers: List of MetricReader objects to read and
113+
export metrics. Each reader can have its own exporter and collection interval.
108114
:keyword bool enable_live_metrics: Boolean value to determine whether to enable live metrics feature.
109115
Defaults to `False`.
110116
:keyword bool enable_performance_counters: Boolean value to determine whether to enable performance counters.
@@ -220,6 +226,8 @@ def _setup_logging(configurations: Dict[str, ConfigurationValue]):
220226
enable_performance_counters_config = configurations[ENABLE_PERFORMANCE_COUNTERS_ARG]
221227
logger_provider = LoggerProvider(resource=resource)
222228
enable_trace_based_sampling_for_logs = configurations[ENABLE_TRACE_BASED_SAMPLING_ARG]
229+
for custom_log_record_processor in configurations[LOG_RECORD_PROCESSORS_ARG]: # type: ignore
230+
logger_provider.add_log_record_processor(custom_log_record_processor) # type: ignore
223231
if configurations.get(ENABLE_LIVE_METRICS_ARG):
224232
qlp = _QuickpulseLogRecordProcessor()
225233
logger_provider.add_log_record_processor(qlp)
@@ -278,11 +286,12 @@ def _setup_logging(configurations: Dict[str, ConfigurationValue]):
278286
def _setup_metrics(configurations: Dict[str, ConfigurationValue]):
279287
resource: Resource = configurations[RESOURCE_ARG] # type: ignore
280288
views: List[View] = configurations[VIEWS_ARG] # type: ignore
289+
readers: list[MetricReader] = configurations[METRIC_READERS_ARG] # type: ignore
281290
enable_performance_counters_config = configurations[ENABLE_PERFORMANCE_COUNTERS_ARG]
282291
metric_exporter = AzureMonitorMetricExporter(**configurations)
283-
reader = PeriodicExportingMetricReader(metric_exporter)
292+
readers.append(PeriodicExportingMetricReader(metric_exporter))
284293
meter_provider = MeterProvider(
285-
metric_readers=[reader],
294+
metric_readers=readers,
286295
resource=resource,
287296
views=views,
288297
)

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
RESOURCE_ARG = "resource"
2525
SAMPLING_RATIO_ARG = "sampling_ratio"
2626
SPAN_PROCESSORS_ARG = "span_processors"
27+
LOG_RECORD_PROCESSORS_ARG = "log_record_processors"
28+
METRIC_READERS_ARG = "metric_readers"
2729
VIEWS_ARG = "views"
2830
RATE_LIMITED_SAMPLER = "microsoft.rate_limited"
2931
FIXED_PERCENTAGE_SAMPLER = "microsoft.fixed.percentage"

0 commit comments

Comments
 (0)