Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 7ef966a

Browse files
authored
Support stamp specific redirects with Azure exporters (#1078)
1 parent 754d89a commit 7ef966a

File tree

6 files changed

+72
-6
lines changed

6 files changed

+72
-6
lines changed

contrib/opencensus-ext-azure/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
([#1062](https://github.com/census-instrumentation/opencensus-python/pull/1062))
1313
- Implement feature and instrumentation metrics via Statsbeat
1414
([#1076](https://github.com/census-instrumentation/opencensus-python/pull/1076))
15+
- Support stamp specific redirect in exporters
16+
([#1078](https://github.com/census-instrumentation/opencensus-python/pull/1078))
1517

1618
## 1.0.8
1719
Released 2021-05-13

contrib/opencensus-ext-azure/opencensus/ext/azure/common/transport.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,22 @@
2222
from azure.core.exceptions import ClientAuthenticationError
2323
from azure.identity._exceptions import CredentialUnavailableError
2424

25+
try:
26+
from urllib.parse import urlparse
27+
except ImportError:
28+
from urlparse import urlparse
29+
30+
2531
logger = logging.getLogger(__name__)
32+
33+
_MAX_CONSECUTIVE_REDIRECTS = 10
2634
_MONITOR_OAUTH_SCOPE = "https://monitor.azure.com//.default"
2735
_requests_lock = threading.Lock()
2836
_requests_map = {}
2937

3038

3139
class TransportMixin(object):
40+
3241
def _check_stats_collection(self):
3342
return not os.environ.get("APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL") and (not hasattr(self, '_is_stats') or not self._is_stats) # noqa: E501
3443

@@ -66,10 +75,7 @@ def _transmit(self, envelopes):
6675
if self.options.credential:
6776
token = self.options.credential.get_token(_MONITOR_OAUTH_SCOPE)
6877
headers["Authorization"] = "Bearer {}".format(token.token)
69-
# Use new api for aad scenario
70-
endpoint += '/v2.1/track'
71-
else:
72-
endpoint += '/v2/track'
78+
endpoint += '/v2.1/track'
7379
if self._check_stats_collection():
7480
with _requests_lock:
7581
_requests_map['count'] = _requests_map.get('count', 0) + 1 # noqa: E501
@@ -79,6 +85,7 @@ def _transmit(self, envelopes):
7985
headers=headers,
8086
timeout=self.options.timeout,
8187
proxies=json.loads(self.options.proxies),
88+
allow_redirects=False,
8289
)
8390
except requests.Timeout:
8491
logger.warning(
@@ -127,6 +134,7 @@ def _transmit(self, envelopes):
127134
except Exception:
128135
pass
129136
if response.status_code == 200:
137+
self._consecutive_redirects = 0
130138
if self._check_stats_collection():
131139
with _requests_lock:
132140
_requests_map['success'] = _requests_map.get('success', 0) + 1 # noqa: E501
@@ -209,6 +217,26 @@ def _transmit(self, envelopes):
209217
with _requests_lock:
210218
_requests_map['retry'] = _requests_map.get('retry', 0) + 1 # noqa: E501
211219
return self.options.minimum_retry_interval
220+
# Redirect
221+
if response.status_code in (307, 308):
222+
self._consecutive_redirects += 1
223+
if self._consecutive_redirects < _MAX_CONSECUTIVE_REDIRECTS:
224+
if response.headers:
225+
location = response.headers.get("location")
226+
if location:
227+
url = urlparse(location)
228+
if url.scheme and url.netloc:
229+
# Change the host to the new redirected host
230+
self.options.endpoint = "{}://{}".format(url.scheme, url.netloc) # noqa: E501
231+
# Attempt to export again
232+
return self._transmit(envelopes)
233+
logger.error(
234+
"Error parsing redirect information."
235+
)
236+
logger.error(
237+
"Error sending telemetry because of circular redirects."
238+
" Please check the integrity of your connection string."
239+
)
212240
logger.error(
213241
'Non-retryable server side error %s: %s.',
214242
response.status_code,

contrib/opencensus-ext-azure/opencensus/ext/azure/log_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ def __init__(self, **options):
6666
# start statsbeat on exporter instantiation
6767
if not os.environ.get("APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL"):
6868
statsbeat_metrics.collect_statsbeat_metrics(self.options)
69+
# For redirects
70+
self._consecutive_redirects = 0 # To prevent circular redirects
6971

7072
def _export(self, batch, event=None): # pragma: NO COVER
7173
try:

contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def __init__(self, is_stats=False, **options):
6060
)
6161
self._atexit_handler = atexit.register(self.shutdown)
6262
self.exporter_thread = None
63+
# For redirects
64+
self._consecutive_redirects = 0 # To prevent circular redirects
6365
super(MetricsExporter, self).__init__()
6466

6567
def export_metrics(self, metrics):

contrib/opencensus-ext-azure/opencensus/ext/azure/trace_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def __init__(self, **options):
7777
# start statsbeat on exporter instantiation
7878
if not os.environ.get("APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL"):
7979
statsbeat_metrics.collect_statsbeat_metrics(self.options)
80+
# For redirects
81+
self._consecutive_redirects = 0 # To prevent circular redirects
8082

8183
def span_data_to_envelope(self, sd):
8284
envelope = Envelope(

contrib/opencensus-ext-azure/tests/test_transport_mixin.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from opencensus.ext.azure.common import Options
2626
from opencensus.ext.azure.common.storage import LocalFileStorage
2727
from opencensus.ext.azure.common.transport import (
28+
_MAX_CONSECUTIVE_REDIRECTS,
2829
_MONITOR_OAUTH_SCOPE,
2930
TransportMixin,
3031
)
@@ -47,9 +48,10 @@ def func(*_args, **_kwargs):
4748

4849

4950
class MockResponse(object):
50-
def __init__(self, status_code, text):
51+
def __init__(self, status_code, text, headers=None):
5152
self.status_code = status_code
5253
self.text = text
54+
self.headers = headers
5355

5456

5557
# pylint: disable=W0212
@@ -197,7 +199,8 @@ def test_transmission_auth(self):
197199
data=data,
198200
headers=headers,
199201
timeout=10.0,
200-
proxies={}
202+
proxies={},
203+
allow_redirects=False,
201204
)
202205
credential.get_token.assert_called_with(_MONITOR_OAUTH_SCOPE)
203206
self.assertIsNone(mixin.storage.get())
@@ -318,3 +321,30 @@ def test_transmission_400(self):
318321
post.return_value = MockResponse(400, '{}')
319322
mixin._transmit_from_storage()
320323
self.assertEqual(len(os.listdir(mixin.storage.path)), 0)
324+
325+
def test_transmission_307(self):
326+
mixin = TransportMixin()
327+
mixin.options = Options()
328+
mixin._consecutive_redirects = 0
329+
mixin.options.endpoint = "test.endpoint"
330+
with LocalFileStorage(os.path.join(TEST_FOLDER, self.id())) as stor:
331+
mixin.storage = stor
332+
mixin.storage.put([1, 2, 3])
333+
with mock.patch('requests.post') as post:
334+
post.return_value = MockResponse(307, '{}', {"location": "https://example.com"}) # noqa: E501
335+
mixin._transmit_from_storage()
336+
self.assertEqual(post.call_count, _MAX_CONSECUTIVE_REDIRECTS)
337+
self.assertEqual(len(os.listdir(mixin.storage.path)), 0)
338+
self.assertEqual(mixin.options.endpoint, "https://example.com")
339+
340+
def test_transmission_307_circular_reference(self):
341+
mixin = TransportMixin()
342+
mixin.options = Options()
343+
mixin._consecutive_redirects = 0
344+
mixin.options.endpoint = "https://example.com"
345+
with mock.patch('requests.post') as post:
346+
post.return_value = MockResponse(307, '{}', {"location": "https://example.com"}) # noqa: E501
347+
result = mixin._transmit([1, 2, 3])
348+
self.assertEqual(result, -307)
349+
self.assertEqual(post.call_count, _MAX_CONSECUTIVE_REDIRECTS)
350+
self.assertEqual(mixin.options.endpoint, "https://example.com")

0 commit comments

Comments
 (0)