Skip to content

Commit b3aa6bd

Browse files
authored
Merge branch 'main' into flask-excluded_urls
2 parents 0e62052 + 1157eb2 commit b3aa6bd

File tree

13 files changed

+851
-0
lines changed

13 files changed

+851
-0
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.4.0-0.23b0...HEAD)
9+
- `opentelemetry-sdk-extension-aws` Add AWS resource detectors to extension package
10+
([#586](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/586))
911

1012
### Changed
1113
- Enable explicit `excluded_urls` argument in `opentelemetry-instrumentation-flask`

Diff for: sdk-extension/opentelemetry-sdk-extension-aws/README.rst

+32
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,40 @@ Or by setting this propagator in your instrumented application:
6565
6666
set_global_textmap(AwsXRayFormat())
6767
68+
Usage (AWS Resource Detectors)
69+
------------------------------
70+
71+
Use the provided `Resource Detectors` to automatically populate attributes under the `resource`
72+
namespace of each generated span.
73+
74+
For example, if tracing with OpenTelemetry on an AWS EC2 instance, you can automatically
75+
populate `resource` attributes by creating a `TraceProvider` using the `AwsEc2ResourceDetector`:
76+
77+
.. code-block:: python
78+
79+
import opentelemetry.trace as trace
80+
from opentelemetry.sdk.trace import TracerProvider
81+
from opentelemetry.sdk.extension.aws.resource.ec2 import (
82+
AwsEc2ResourceDetector,
83+
)
84+
from opentelemetry.sdk.resources import get_aggregated_resources
85+
86+
trace.set_tracer_provider(
87+
TracerProvider(
88+
resource=get_aggregated_resources(
89+
[
90+
AwsEc2ResourceDetector(),
91+
]
92+
),
93+
)
94+
)
95+
96+
Refer to each detectors' docstring to determine any possible requirements for that
97+
detector.
98+
6899
References
69100
----------
70101

71102
* `OpenTelemetry Project <https://opentelemetry.io/>`_
72103
* `AWS X-Ray Trace IDs Format <https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids>`_
104+
* `OpenTelemetry Specification for Resource Attributes <https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions>`_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opentelemetry.sdk.extension.aws.resource._lambda import (
16+
AwsLambdaResourceDetector,
17+
)
18+
from opentelemetry.sdk.extension.aws.resource.beanstalk import (
19+
AwsBeanstalkResourceDetector,
20+
)
21+
from opentelemetry.sdk.extension.aws.resource.ec2 import AwsEc2ResourceDetector
22+
from opentelemetry.sdk.extension.aws.resource.ecs import AwsEcsResourceDetector
23+
from opentelemetry.sdk.extension.aws.resource.eks import AwsEksResourceDetector
24+
25+
__all__ = [
26+
"AwsBeanstalkResourceDetector",
27+
"AwsEc2ResourceDetector",
28+
"AwsEcsResourceDetector",
29+
"AwsEksResourceDetector",
30+
"AwsLambdaResourceDetector",
31+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
from os import environ
17+
18+
from opentelemetry.sdk.resources import Resource, ResourceDetector
19+
from opentelemetry.semconv.resource import (
20+
CloudPlatformValues,
21+
CloudProviderValues,
22+
ResourceAttributes,
23+
)
24+
25+
logger = logging.getLogger(__name__)
26+
27+
28+
class AwsLambdaResourceDetector(ResourceDetector):
29+
"""Detects attribute values only available when the app is running on AWS
30+
Lambda and returns them in a Resource.
31+
32+
Uses Lambda defined runtime enivronment variables. See more: https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
33+
"""
34+
35+
def detect(self) -> "Resource":
36+
try:
37+
return Resource(
38+
{
39+
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
40+
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_LAMBDA.value,
41+
ResourceAttributes.CLOUD_REGION: environ["AWS_REGION"],
42+
ResourceAttributes.FAAS_NAME: environ[
43+
"AWS_LAMBDA_FUNCTION_NAME"
44+
],
45+
ResourceAttributes.FAAS_VERSION: environ[
46+
"AWS_LAMBDA_FUNCTION_VERSION"
47+
],
48+
ResourceAttributes.FAAS_INSTANCE: environ[
49+
"AWS_LAMBDA_LOG_STREAM_NAME"
50+
],
51+
ResourceAttributes.FAAS_MAX_MEMORY: int(
52+
environ["AWS_LAMBDA_FUNCTION_MEMORY_SIZE"]
53+
),
54+
}
55+
)
56+
# pylint: disable=broad-except
57+
except Exception as exception:
58+
if self.raise_on_error:
59+
raise exception
60+
61+
logger.warning("%s failed: %s", self.__class__.__name__, exception)
62+
return Resource.get_empty()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
import logging
17+
import os
18+
19+
from opentelemetry.sdk.resources import Resource, ResourceDetector
20+
from opentelemetry.semconv.resource import (
21+
CloudPlatformValues,
22+
CloudProviderValues,
23+
ResourceAttributes,
24+
)
25+
26+
logger = logging.getLogger(__name__)
27+
28+
29+
class AwsBeanstalkResourceDetector(ResourceDetector):
30+
"""Detects attribute values only available when the app is running on AWS
31+
Elastic Beanstalk and returns them in a Resource.
32+
33+
NOTE: Requires enabling X-Ray on Beanstalk Environment. See more here: https://docs.aws.amazon.com/xray/latest/devguide/xray-services-beanstalk.html
34+
"""
35+
36+
def detect(self) -> "Resource":
37+
if os.name == "nt":
38+
conf_file_path = (
39+
"C:\\Program Files\\Amazon\\XRay\\environment.conf"
40+
)
41+
else:
42+
conf_file_path = "/var/elasticbeanstalk/xray/environment.conf"
43+
44+
try:
45+
with open(conf_file_path) as conf_file:
46+
parsed_data = json.load(conf_file)
47+
48+
return Resource(
49+
{
50+
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
51+
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_ELASTIC_BEANSTALK.value,
52+
ResourceAttributes.SERVICE_NAME: CloudPlatformValues.AWS_ELASTIC_BEANSTALK.value,
53+
ResourceAttributes.SERVICE_INSTANCE_ID: parsed_data[
54+
"deployment_id"
55+
],
56+
ResourceAttributes.SERVICE_NAMESPACE: parsed_data[
57+
"environment_name"
58+
],
59+
ResourceAttributes.SERVICE_VERSION: parsed_data[
60+
"version_label"
61+
],
62+
}
63+
)
64+
# pylint: disable=broad-except
65+
except Exception as exception:
66+
if self.raise_on_error:
67+
raise exception
68+
69+
logger.warning("%s failed: %s", self.__class__.__name__, exception)
70+
return Resource.get_empty()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
import logging
17+
from urllib.request import Request, urlopen
18+
19+
from opentelemetry.sdk.resources import Resource, ResourceDetector
20+
from opentelemetry.semconv.resource import (
21+
CloudPlatformValues,
22+
CloudProviderValues,
23+
ResourceAttributes,
24+
)
25+
26+
logger = logging.getLogger(__name__)
27+
28+
_AWS_METADATA_TOKEN_HEADER = "X-aws-ec2-metadata-token"
29+
_GET_METHOD = "GET"
30+
31+
32+
def _aws_http_request(method, path, headers):
33+
with urlopen(
34+
Request(
35+
"http://169.254.169.254" + path, headers=headers, method=method
36+
),
37+
timeout=1000,
38+
) as response:
39+
return response.read().decode("utf-8")
40+
41+
42+
def _get_token():
43+
return _aws_http_request(
44+
"PUT",
45+
"/latest/api/token",
46+
{"X-aws-ec2-metadata-token-ttl-seconds": "60"},
47+
)
48+
49+
50+
def _get_identity(token):
51+
return _aws_http_request(
52+
_GET_METHOD,
53+
"/latest/dynamic/instance-identity/document",
54+
{_AWS_METADATA_TOKEN_HEADER: token},
55+
)
56+
57+
58+
def _get_host(token):
59+
return _aws_http_request(
60+
_GET_METHOD,
61+
"/latest/meta-data/hostname",
62+
{_AWS_METADATA_TOKEN_HEADER: token},
63+
)
64+
65+
66+
class AwsEc2ResourceDetector(ResourceDetector):
67+
"""Detects attribute values only available when the app is running on AWS
68+
Elastic Compute Cloud (EC2) and returns them in a Resource.
69+
70+
Uses a special URI to get instance meta-data. See more: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
71+
"""
72+
73+
def detect(self) -> "Resource":
74+
try:
75+
token = _get_token()
76+
identity_dict = json.loads(_get_identity(token))
77+
hostname = _get_host(token)
78+
79+
return Resource(
80+
{
81+
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
82+
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_EC2.value,
83+
ResourceAttributes.CLOUD_ACCOUNT_ID: identity_dict[
84+
"accountId"
85+
],
86+
ResourceAttributes.CLOUD_REGION: identity_dict["region"],
87+
ResourceAttributes.CLOUD_AVAILABILITY_ZONE: identity_dict[
88+
"availabilityZone"
89+
],
90+
ResourceAttributes.HOST_ID: identity_dict["instanceId"],
91+
ResourceAttributes.HOST_TYPE: identity_dict[
92+
"instanceType"
93+
],
94+
ResourceAttributes.HOST_NAME: hostname,
95+
}
96+
)
97+
# pylint: disable=broad-except
98+
except Exception as exception:
99+
if self.raise_on_error:
100+
raise exception
101+
102+
logger.warning("%s failed: %s", self.__class__.__name__, exception)
103+
return Resource.get_empty()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
import os
17+
import socket
18+
19+
from opentelemetry.sdk.resources import Resource, ResourceDetector
20+
from opentelemetry.semconv.resource import (
21+
CloudPlatformValues,
22+
CloudProviderValues,
23+
ResourceAttributes,
24+
)
25+
26+
logger = logging.getLogger(__name__)
27+
28+
_CONTAINER_ID_LENGTH = 64
29+
30+
31+
class AwsEcsResourceDetector(ResourceDetector):
32+
"""Detects attribute values only available when the app is running on AWS
33+
Elastic Container Service (ECS) and returns them in a Resource.
34+
"""
35+
36+
def detect(self) -> "Resource":
37+
try:
38+
if not os.environ.get(
39+
"ECS_CONTAINER_METADATA_URI"
40+
) and not os.environ.get("ECS_CONTAINER_METADATA_URI_V4"):
41+
raise RuntimeError(
42+
"Missing ECS_CONTAINER_METADATA_URI therefore process is not on ECS."
43+
)
44+
45+
container_id = ""
46+
try:
47+
with open(
48+
"/proc/self/cgroup", encoding="utf8"
49+
) as container_info_file:
50+
for raw_line in container_info_file.readlines():
51+
line = raw_line.strip()
52+
if len(line) > _CONTAINER_ID_LENGTH:
53+
container_id = line[-_CONTAINER_ID_LENGTH:]
54+
except FileNotFoundError as exception:
55+
logger.warning(
56+
"Failed to get container ID on ECS: %s.", exception
57+
)
58+
59+
return Resource(
60+
{
61+
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
62+
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_ECS.value,
63+
ResourceAttributes.CONTAINER_NAME: socket.gethostname(),
64+
ResourceAttributes.CONTAINER_ID: container_id,
65+
}
66+
)
67+
# pylint: disable=broad-except
68+
except Exception as exception:
69+
if self.raise_on_error:
70+
raise exception
71+
72+
logger.warning("%s failed: %s", self.__class__.__name__, exception)
73+
return Resource.get_empty()

0 commit comments

Comments
 (0)