Skip to content
5 changes: 5 additions & 0 deletions api/integrations/common/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Default timeout (in seconds) for outbound HTTP requests made by integration
# wrappers. Without an explicit timeout, ``requests`` waits indefinitely, so an
# unresponsive third-party endpoint can hang the worker thread that dispatches
# the event, leading to resource exhaustion.
INTEGRATION_REQUEST_TIMEOUT_SECONDS = 10
7 changes: 6 additions & 1 deletion api/integrations/datadog/datadog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import requests

from audit.models import AuditLog
from integrations.common.constants import (
INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -46,7 +49,9 @@ def _track_event(self, event: dict) -> None: # type: ignore[type-arg]
event["source_type_name"] = FLAGSMITH_SOURCE_TYPE_NAME

response = self.session.post(
f"{self.events_url}?api_key={self.api_key}", data=json.dumps(event)
f"{self.events_url}?api_key={self.api_key}",
data=json.dumps(event),
timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
logger.debug(
"Sent event to DataDog. Response code was %s" % response.status_code
Expand Down
8 changes: 7 additions & 1 deletion api/integrations/dynatrace/dynatrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from audit.services import get_audited_instance_from_audit_log_record
from features.models import Feature, FeatureState
from features.versioning.models import EnvironmentFeatureVersion
from integrations.common.constants import (
INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper
from segments.models import Segment

Expand All @@ -29,7 +32,10 @@ def __init__(self, base_url: str, api_key: str, entity_selector: str):
def _track_event(self, event: dict) -> None: # type: ignore[type-arg]
event["entitySelector"] = self.entity_selector
response = requests.post(
self.url, headers=self._headers(), data=json.dumps(event)
self.url,
headers=self._headers(),
data=json.dumps(event),
timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
logger.debug(
"Sent event to Dynatrace. Response code was %s" % response.status_code
Expand Down
4 changes: 4 additions & 0 deletions api/integrations/grafana/grafana.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import requests

from audit.models import AuditLog
from integrations.common.constants import (
INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper
from integrations.grafana.mappers import (
map_audit_log_record_to_grafana_annotation,
Expand Down Expand Up @@ -36,6 +39,7 @@ def _track_event(self, event: dict[str, Any]) -> None:
url=self.url,
headers=self._headers(),
data=json.dumps(event),
timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)

logger.debug(
Expand Down
9 changes: 8 additions & 1 deletion api/integrations/heap/heap.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from environments.identities.models import Identity
from environments.identities.traits.models import Trait
from features.models import FeatureState
from integrations.common.constants import (
INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
from integrations.common.wrapper import AbstractBaseIdentityIntegrationWrapper

from .constants import DEFAULT_HEAP_API_URL
Expand All @@ -21,7 +24,11 @@ def __init__(self, config: HeapConfiguration):
self.url = f"{base_url}/api/track"

def _identify_user(self, user_data: dict) -> None: # type: ignore[type-arg]
response = requests.post(self.url, json=user_data)
response = requests.post(
self.url,
json=user_data,
timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
logger.debug("Sent event to Heap. Response code was: %s" % response.status_code)

def generate_user_data(
Expand Down
10 changes: 9 additions & 1 deletion api/integrations/mixpanel/mixpanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from environments.identities.models import Identity
from environments.identities.traits.models import Trait
from features.models import FeatureState
from integrations.common.constants import (
INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
from integrations.common.wrapper import AbstractBaseIdentityIntegrationWrapper

from .constants import DEFAULT_MIXPANEL_API_URL
Expand All @@ -30,7 +33,12 @@ def __init__(self, config: MixpanelConfiguration):
}

def _identify_user(self, user_data: MixpanelUserData) -> None:
response = requests.post(self.url, headers=self.headers, json=user_data)
response = requests.post(
self.url,
headers=self.headers,
json=user_data,
timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
logger.debug(
"Sent event to Mixpanel. Response code was: %s" % response.status_code
)
Expand Down
8 changes: 7 additions & 1 deletion api/integrations/new_relic/new_relic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import requests

from audit.models import AuditLog
from integrations.common.constants import (
INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper

logger = logging.getLogger(__name__)
Expand All @@ -20,7 +23,10 @@ def __init__(self, base_url: str, api_key: str, app_id: str):

def _track_event(self, event: dict) -> None: # type: ignore[type-arg]
response = requests.post(
self.url, headers=self._headers(), data=json.dumps(event)
self.url,
headers=self._headers(),
data=json.dumps(event),
timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS,
)
logger.debug(
"Sent event to NewRelic. Response code was %s" % response.status_code
Expand Down
Loading