| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- import json
- import urllib3
- from sentry_sdk.integrations import Integration
- from sentry_sdk.api import set_context
- from sentry_sdk.utils import logger
- from typing import TYPE_CHECKING
- if TYPE_CHECKING:
- from typing import Dict
- CONTEXT_TYPE = "cloud_resource"
- HTTP_TIMEOUT = 2.0
- AWS_METADATA_HOST = "169.254.169.254"
- AWS_TOKEN_URL = "http://{}/latest/api/token".format(AWS_METADATA_HOST)
- AWS_METADATA_URL = "http://{}/latest/dynamic/instance-identity/document".format(
- AWS_METADATA_HOST
- )
- GCP_METADATA_HOST = "metadata.google.internal"
- GCP_METADATA_URL = "http://{}/computeMetadata/v1/?recursive=true".format(
- GCP_METADATA_HOST
- )
- class CLOUD_PROVIDER: # noqa: N801
- """
- Name of the cloud provider.
- see https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud/
- """
- ALIBABA = "alibaba_cloud"
- AWS = "aws"
- AZURE = "azure"
- GCP = "gcp"
- IBM = "ibm_cloud"
- TENCENT = "tencent_cloud"
- class CLOUD_PLATFORM: # noqa: N801
- """
- The cloud platform.
- see https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud/
- """
- AWS_EC2 = "aws_ec2"
- GCP_COMPUTE_ENGINE = "gcp_compute_engine"
- class CloudResourceContextIntegration(Integration):
- """
- Adds cloud resource context to the Senty scope
- """
- identifier = "cloudresourcecontext"
- cloud_provider = ""
- aws_token = ""
- http = urllib3.PoolManager(timeout=HTTP_TIMEOUT)
- gcp_metadata = None
- def __init__(self, cloud_provider: str = "") -> None:
- CloudResourceContextIntegration.cloud_provider = cloud_provider
- @classmethod
- def _is_aws(cls) -> bool:
- try:
- r = cls.http.request(
- "PUT",
- AWS_TOKEN_URL,
- headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"},
- )
- if r.status != 200:
- return False
- cls.aws_token = r.data.decode()
- return True
- except urllib3.exceptions.TimeoutError:
- logger.debug(
- "AWS metadata service timed out after %s seconds", HTTP_TIMEOUT
- )
- return False
- except Exception as e:
- logger.debug("Error checking AWS metadata service: %s", str(e))
- return False
- @classmethod
- def _get_aws_context(cls) -> "Dict[str, str]":
- ctx = {
- "cloud.provider": CLOUD_PROVIDER.AWS,
- "cloud.platform": CLOUD_PLATFORM.AWS_EC2,
- }
- try:
- r = cls.http.request(
- "GET",
- AWS_METADATA_URL,
- headers={"X-aws-ec2-metadata-token": cls.aws_token},
- )
- if r.status != 200:
- return ctx
- data = json.loads(r.data.decode("utf-8"))
- try:
- ctx["cloud.account.id"] = data["accountId"]
- except Exception:
- pass
- try:
- ctx["cloud.availability_zone"] = data["availabilityZone"]
- except Exception:
- pass
- try:
- ctx["cloud.region"] = data["region"]
- except Exception:
- pass
- try:
- ctx["host.id"] = data["instanceId"]
- except Exception:
- pass
- try:
- ctx["host.type"] = data["instanceType"]
- except Exception:
- pass
- except urllib3.exceptions.TimeoutError:
- logger.debug(
- "AWS metadata service timed out after %s seconds", HTTP_TIMEOUT
- )
- except Exception as e:
- logger.debug("Error fetching AWS metadata: %s", str(e))
- return ctx
- @classmethod
- def _is_gcp(cls) -> bool:
- try:
- r = cls.http.request(
- "GET",
- GCP_METADATA_URL,
- headers={"Metadata-Flavor": "Google"},
- )
- if r.status != 200:
- return False
- cls.gcp_metadata = json.loads(r.data.decode("utf-8"))
- return True
- except urllib3.exceptions.TimeoutError:
- logger.debug(
- "GCP metadata service timed out after %s seconds", HTTP_TIMEOUT
- )
- return False
- except Exception as e:
- logger.debug("Error checking GCP metadata service: %s", str(e))
- return False
- @classmethod
- def _get_gcp_context(cls) -> "Dict[str, str]":
- ctx = {
- "cloud.provider": CLOUD_PROVIDER.GCP,
- "cloud.platform": CLOUD_PLATFORM.GCP_COMPUTE_ENGINE,
- }
- try:
- if cls.gcp_metadata is None:
- r = cls.http.request(
- "GET",
- GCP_METADATA_URL,
- headers={"Metadata-Flavor": "Google"},
- )
- if r.status != 200:
- return ctx
- cls.gcp_metadata = json.loads(r.data.decode("utf-8"))
- try:
- ctx["cloud.account.id"] = cls.gcp_metadata["project"]["projectId"]
- except Exception:
- pass
- try:
- ctx["cloud.availability_zone"] = cls.gcp_metadata["instance"][
- "zone"
- ].split("/")[-1]
- except Exception:
- pass
- try:
- # only populated in google cloud run
- ctx["cloud.region"] = cls.gcp_metadata["instance"]["region"].split("/")[
- -1
- ]
- except Exception:
- pass
- try:
- ctx["host.id"] = cls.gcp_metadata["instance"]["id"]
- except Exception:
- pass
- except urllib3.exceptions.TimeoutError:
- logger.debug(
- "GCP metadata service timed out after %s seconds", HTTP_TIMEOUT
- )
- except Exception as e:
- logger.debug("Error fetching GCP metadata: %s", str(e))
- return ctx
- @classmethod
- def _get_cloud_provider(cls) -> str:
- if cls._is_aws():
- return CLOUD_PROVIDER.AWS
- if cls._is_gcp():
- return CLOUD_PROVIDER.GCP
- return ""
- @classmethod
- def _get_cloud_resource_context(cls) -> "Dict[str, str]":
- cloud_provider = (
- cls.cloud_provider
- if cls.cloud_provider != ""
- else CloudResourceContextIntegration._get_cloud_provider()
- )
- if cloud_provider in context_getters.keys():
- return context_getters[cloud_provider]()
- return {}
- @staticmethod
- def setup_once() -> None:
- cloud_provider = CloudResourceContextIntegration.cloud_provider
- unsupported_cloud_provider = (
- cloud_provider != "" and cloud_provider not in context_getters.keys()
- )
- if unsupported_cloud_provider:
- logger.warning(
- "Invalid value for cloud_provider: %s (must be in %s). Falling back to autodetection...",
- CloudResourceContextIntegration.cloud_provider,
- list(context_getters.keys()),
- )
- context = CloudResourceContextIntegration._get_cloud_resource_context()
- if context != {}:
- set_context(CONTEXT_TYPE, context)
- # Map with the currently supported cloud providers
- # mapping to functions extracting the context
- context_getters = {
- CLOUD_PROVIDER.AWS: CloudResourceContextIntegration._get_aws_context,
- CLOUD_PROVIDER.GCP: CloudResourceContextIntegration._get_gcp_context,
- }
|