5
5
import tempfile
6
6
from enum import Enum
7
7
from typing import List , Any
8
+ from urllib .parse import urlparse
8
9
9
10
from opentelemetry .sdk .trace .export import SpanExportResult
10
11
11
12
from 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
13
14
from azure .monitor .opentelemetry .exporter ._generated import AzureMonitorClient
14
15
from azure .monitor .opentelemetry .exporter ._generated ._configuration import AzureMonitorClientConfiguration
15
16
from azure .monitor .opentelemetry .exporter ._generated .models import TelemetryItem
@@ -43,6 +44,7 @@ def __init__(self, **kwargs: Any) -> None:
43
44
self ._instrumentation_key = parsed_connection_string .instrumentation_key
44
45
self ._timeout = 10.0 # networking timeout in seconds
45
46
self ._api_version = kwargs .get ('api_version' ) or _SERVICE_API_LATEST
47
+ self ._consecutive_redirects = 0 # To prevent circular redirects
46
48
47
49
temp_suffix = self ._instrumentation_key or ""
48
50
default_storage_path = os .path .join (
@@ -56,7 +58,8 @@ def __init__(self, **kwargs: Any) -> None:
56
58
config .user_agent_policy ,
57
59
config .proxy_policy ,
58
60
ContentDecodePolicy (** kwargs ),
59
- config .redirect_policy ,
61
+ # Handle redirects in exporter, set new endpoint if redirected
62
+ RedirectPolicy (permit_redirects = False ),
60
63
config .retry_policy ,
61
64
config .authentication_policy ,
62
65
config .custom_hook_policy ,
@@ -100,6 +103,7 @@ def _transmit(self, envelopes: List[TelemetryItem]) -> ExportResult:
100
103
try :
101
104
track_response = self .client .track (envelopes )
102
105
if not track_response .errors :
106
+ self ._consecutive_redirects = 0
103
107
logger .info ("Transmission succeeded: Item received: %s. Items accepted: %s" ,
104
108
track_response .items_received , track_response .items_accepted )
105
109
return ExportResult .SUCCESS
@@ -120,11 +124,33 @@ def _transmit(self, envelopes: List[TelemetryItem]) -> ExportResult:
120
124
envelopes_to_store = [x .as_dict ()
121
125
for x in resend_envelopes ]
122
126
self .storage .put (envelopes_to_store )
127
+ self ._consecutive_redirects = 0
123
128
return ExportResult .FAILED_RETRYABLE
124
129
125
130
except HttpResponseError as response_error :
126
131
if _is_retryable_code (response_error .status_code ):
127
132
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
128
154
return ExportResult .FAILED_NOT_RETRYABLE
129
155
except ServiceRequestError as request_error :
130
156
# Errors when we're fairly sure that the server did not receive the
@@ -140,9 +166,20 @@ def _transmit(self, envelopes: List[TelemetryItem]) -> ExportResult:
140
166
return ExportResult .FAILED_NOT_RETRYABLE
141
167
return ExportResult .FAILED_NOT_RETRYABLE
142
168
# No spans to export
169
+ self ._consecutive_redirects = 0
143
170
return ExportResult .SUCCESS
144
171
145
172
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
+
146
183
def _is_retryable_code (response_code : int ) -> bool :
147
184
"""
148
185
Determine if response is retryable
0 commit comments