55import tempfile
66from enum import Enum
77from typing import List , Any
8+ from urllib .parse import urlparse
89
910from opentelemetry .sdk .trace .export import SpanExportResult
1011
1112from azure .core .exceptions import HttpResponseError , ServiceRequestError
12- from azure .core .pipeline .policies import ContentDecodePolicy , HttpLoggingPolicy , RequestIdPolicy
13+ from azure .core .pipeline .policies import ContentDecodePolicy , HttpLoggingPolicy , RedirectPolicy , RequestIdPolicy
1314from azure .monitor .opentelemetry .exporter ._generated import AzureMonitorClient
1415from azure .monitor .opentelemetry .exporter ._generated ._configuration import AzureMonitorClientConfiguration
1516from azure .monitor .opentelemetry .exporter ._generated .models import TelemetryItem
@@ -43,6 +44,7 @@ def __init__(self, **kwargs: Any) -> None:
4344 self ._instrumentation_key = parsed_connection_string .instrumentation_key
4445 self ._timeout = 10.0 # networking timeout in seconds
4546 self ._api_version = kwargs .get ('api_version' ) or _SERVICE_API_LATEST
47+ self ._consecutive_redirects = 0 # To prevent circular redirects
4648
4749 temp_suffix = self ._instrumentation_key or ""
4850 default_storage_path = os .path .join (
@@ -56,7 +58,8 @@ def __init__(self, **kwargs: Any) -> None:
5658 config .user_agent_policy ,
5759 config .proxy_policy ,
5860 ContentDecodePolicy (** kwargs ),
59- config .redirect_policy ,
61+ # Handle redirects in exporter, set new endpoint if redirected
62+ RedirectPolicy (permit_redirects = False ),
6063 config .retry_policy ,
6164 config .authentication_policy ,
6265 config .custom_hook_policy ,
@@ -100,6 +103,7 @@ def _transmit(self, envelopes: List[TelemetryItem]) -> ExportResult:
100103 try :
101104 track_response = self .client .track (envelopes )
102105 if not track_response .errors :
106+ self ._consecutive_redirects = 0
103107 logger .info ("Transmission succeeded: Item received: %s. Items accepted: %s" ,
104108 track_response .items_received , track_response .items_accepted )
105109 return ExportResult .SUCCESS
@@ -120,11 +124,33 @@ def _transmit(self, envelopes: List[TelemetryItem]) -> ExportResult:
120124 envelopes_to_store = [x .as_dict ()
121125 for x in resend_envelopes ]
122126 self .storage .put (envelopes_to_store )
127+ self ._consecutive_redirects = 0
123128 return ExportResult .FAILED_RETRYABLE
124129
125130 except HttpResponseError as response_error :
126131 if _is_retryable_code (response_error .status_code ):
127132 return ExportResult .FAILED_RETRYABLE
133+ if _is_redirect_code (response_error .status_code ):
134+ self ._consecutive_redirects = self ._consecutive_redirects + 1
135+ if self ._consecutive_redirects < self .client ._config .redirect_policy .max_redirects : # pylint: disable=W0212
136+ if response_error .response and response_error .response .headers :
137+ location = response_error .response .headers .get ("location" )
138+ if location :
139+ url = urlparse (location )
140+ if url .scheme and url .netloc :
141+ # Change the host to the new redirected host
142+ self .client ._config .host = "{}://{}" .format (url .scheme , url .netloc ) # pylint: disable=W0212
143+ # Attempt to export again
144+ return self ._transmit (envelopes )
145+ logger .error (
146+ "Error parsing redirect information."
147+ )
148+ return ExportResult .FAILED_NOT_RETRYABLE
149+ logger .error (
150+ "Error sending telemetry because of circular redirects." \
151+ "Please check the integrity of your connection string."
152+ )
153+ return ExportResult .FAILED_NOT_RETRYABLE
128154 return ExportResult .FAILED_NOT_RETRYABLE
129155 except ServiceRequestError as request_error :
130156 # Errors when we're fairly sure that the server did not receive the
@@ -140,9 +166,20 @@ def _transmit(self, envelopes: List[TelemetryItem]) -> ExportResult:
140166 return ExportResult .FAILED_NOT_RETRYABLE
141167 return ExportResult .FAILED_NOT_RETRYABLE
142168 # No spans to export
169+ self ._consecutive_redirects = 0
143170 return ExportResult .SUCCESS
144171
145172
173+ def _is_redirect_code (response_code : int ) -> bool :
174+ """
175+ Determine if response is a redirect response.
176+ """
177+ return bool (response_code in (
178+ 307 , # Temporary redirect
179+ 308 , # Permanent redirect
180+ ))
181+
182+
146183def _is_retryable_code (response_code : int ) -> bool :
147184 """
148185 Determine if response is retryable
0 commit comments