Skip to content

Commit 6e89203

Browse files
thomasleveilpmcollins
authored andcommitted
Fix UnboundLocalError local variable 'start' referenced before assignment (open-telemetry#1889)
Co-authored-by: Pablo Collins <[email protected]>
1 parent 7603a1f commit 6e89203

File tree

11 files changed

+656
-1
lines changed

11 files changed

+656
-1
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Fixed
11+
12+
- `opentelemetry-instrumentation-asgi` Fix UnboundLocalError local variable 'start' referenced before assignment
13+
([#1889](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1889))
14+
15+
### Added
16+
17+
- Added Azure VM Resource Detector
18+
([#1904](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1904))
19+
1020
## Version 1.19.0/0.40b0 (2023-07-13)
1121
- `opentelemetry-instrumentation-asgi` Add `http.server.request.size` metric
1222
([#1867](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1867))

instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ async def __call__(self, scope, receive, send):
538538
receive: An awaitable callable yielding dictionaries
539539
send: An awaitable callable taking a single dictionary as argument.
540540
"""
541+
start = default_timer()
541542
if scope["type"] not in ("http", "websocket"):
542543
return await self.app(scope, receive, send)
543544

@@ -591,7 +592,6 @@ async def __call__(self, scope, receive, send):
591592
send,
592593
duration_attrs,
593594
)
594-
start = default_timer()
595595

596596
await self.app(scope, otel_receive, otel_send)
597597
finally:

instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py

+34
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
# pylint: disable=too-many-lines
1616

17+
import asyncio
1718
import sys
1819
import unittest
1920
from timeit import default_timer
@@ -796,5 +797,38 @@ async def wrapped_app(scope, receive, send):
796797
)
797798

798799

800+
class TestAsgiApplicationRaisingError(AsgiTestBase):
801+
def tearDown(self):
802+
pass
803+
804+
@mock.patch(
805+
"opentelemetry.instrumentation.asgi.collect_custom_request_headers_attributes",
806+
side_effect=ValueError("whatever"),
807+
)
808+
def test_asgi_issue_1883(
809+
self, mock_collect_custom_request_headers_attributes
810+
):
811+
"""
812+
Test that exception UnboundLocalError local variable 'start' referenced before assignment is not raised
813+
See https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1883
814+
"""
815+
app = otel_asgi.OpenTelemetryMiddleware(simple_asgi)
816+
self.seed_app(app)
817+
self.send_default_request()
818+
try:
819+
asyncio.get_event_loop().run_until_complete(
820+
self.communicator.stop()
821+
)
822+
except ValueError as exc_info:
823+
self.assertEqual(exc_info.args[0], "whatever")
824+
except Exception as exc_info: # pylint: disable=W0703
825+
self.fail(
826+
"expecting ValueError('whatever'), received instead: "
827+
+ str(exc_info)
828+
)
829+
else:
830+
self.fail("expecting ValueError('whatever')")
831+
832+
799833
if __name__ == "__main__":
800834
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Copyright (c) Microsoft Corporation.
2+
3+
MIT License
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
graft src
2+
graft tests
3+
global-exclude *.pyc
4+
global-exclude *.pyo
5+
global-exclude __pycache__/*
6+
include CHANGELOG.md
7+
include MANIFEST.in
8+
include README.rst
9+
include LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
OpenTelemetry Resource detectors for Azure Virtual Machines
2+
==========================================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-resource-detector-azure-vm.svg
7+
:target: https://pypi.org/project/opentelemetry-resource-detector-azure-vm/
8+
9+
10+
This library provides custom resource detector for Azure VMs. OpenTelemetry Python has an experimental feature whereby Resource Detectors can be injected to Resource Attributes. This package includes a resource detector for Azure VM. This detector fills out the following Resource Attributes:
11+
* `azure.vm.scaleset.name`
12+
* `azure.vm.sku`
13+
* `cloud.platform`
14+
* `cloud.provider`
15+
* `cloud.region`
16+
* `cloud.resource_id`
17+
* `host.id`
18+
* `host.name`
19+
* `host.type`
20+
* `os.type`
21+
* `os.version`
22+
* `service.instance.id`
23+
24+
For more information, see the Semantic Conventions for Cloud Resource Attributes.
25+
26+
Installation
27+
------------
28+
29+
::
30+
31+
pip install opentelemetry-resource-detector-azure-vm
32+
33+
---------------------------
34+
35+
Usage example for `opentelemetry-resource-detector-azure-vm`
36+
37+
.. code-block:: python
38+
39+
from opentelemetry import trace
40+
from opentelemetry.sdk.trace import TracerProvider
41+
from opentelemetry.resource.detector.azure.vm import (
42+
AzureVMResourceDetector,
43+
)
44+
from opentelemetry.sdk.resources import get_aggregated_resources
45+
46+
47+
trace.set_tracer_provider(
48+
TracerProvider(
49+
resource=get_aggregated_resources(
50+
[
51+
AzureVMResourceDetector(),
52+
]
53+
),
54+
)
55+
)
56+
57+
You can also enable the Azure VM Resource Detector by adding `azure_vm` to the `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS` environment variable:
58+
59+
`export OTEL_EXPERIMENTAL_RESOURCE_DETECTORS=azure_vm`
60+
61+
References
62+
----------
63+
64+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
65+
* `Resource Detector Docs <https://opentelemetry.io/docs/specs/otel/resource/sdk/#detecting-resource-information-from-the-environment>`
66+
* `Cloud Semantic Conventions <https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/cloud/>`_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "opentelemetry-resource-detector-container"
7+
dynamic = ["version"]
8+
description = "Container Resource Detector for OpenTelemetry"
9+
readme = "README.rst"
10+
license = "Apache-2.0"
11+
requires-python = ">=3.7"
12+
authors = [
13+
{ name = "OpenTelemetry Authors", email = "[email protected]" },
14+
]
15+
classifiers = [
16+
"Development Status :: 5 - Production/Stable",
17+
"Intended Audience :: Developers",
18+
"License :: OSI Approved :: Apache Software License",
19+
"Programming Language :: Python",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.7",
22+
"Programming Language :: Python :: 3.8",
23+
"Programming Language :: Python :: 3.9",
24+
"Programming Language :: Python :: 3.10",
25+
"Programming Language :: Python :: 3.11",
26+
]
27+
dependencies = [
28+
"opentelemetry-sdk ~= 1.19",
29+
]
30+
31+
[project.optional-dependencies]
32+
test = []
33+
34+
[project.entry-points.opentelemetry_resource_detector]
35+
azure_vm = "opentelemetry.resource.detector.azure.vm:AzureVMResourceDetector"
36+
37+
[project.urls]
38+
Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/resource/opentelemetry-resource-detector-azure-vm"
39+
40+
[tool.hatch.version]
41+
path = "src/opentelemetry/resource/detector/azure/vm/version.py"
42+
43+
[tool.hatch.build.targets.sdist]
44+
include = [
45+
"/src",
46+
"/tests",
47+
]
48+
49+
[tool.hatch.build.targets.wheel]
50+
packages = ["src/opentelemetry"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from json import loads
4+
from logging import getLogger
5+
from os import environ
6+
from urllib.request import Request, urlopen
7+
from urllib.error import URLError
8+
9+
from opentelemetry.sdk.resources import ResourceDetector, Resource
10+
from opentelemetry.semconv.resource import ResourceAttributes, CloudPlatformValues, CloudProviderValues
11+
12+
13+
# TODO: Remove when cloud resource id is no longer missing in Resource Attributes
14+
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE = "cloud.resource_id"
15+
_AZURE_VM_METADATA_ENDPOINT = "http://169.254.169.254/metadata/instance/compute?api-version=2021-12-13&format=json"
16+
_AZURE_VM_SCALE_SET_NAME_ATTRIBUTE = "azure.vm.scaleset.name"
17+
_AZURE_VM_SKU_ATTRIBUTE = "azure.vm.sku"
18+
_logger = getLogger(__name__)
19+
20+
EXPECTED_AZURE_AMS_ATTRIBUTES = [
21+
_AZURE_VM_SCALE_SET_NAME_ATTRIBUTE,
22+
_AZURE_VM_SKU_ATTRIBUTE,
23+
ResourceAttributes.CLOUD_PLATFORM,
24+
ResourceAttributes.CLOUD_PROVIDER,
25+
ResourceAttributes.CLOUD_REGION,
26+
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE,
27+
ResourceAttributes.HOST_ID,
28+
ResourceAttributes.HOST_NAME,
29+
ResourceAttributes.HOST_TYPE,
30+
ResourceAttributes.OS_TYPE,
31+
ResourceAttributes.OS_VERSION,
32+
ResourceAttributes.SERVICE_INSTANCE_ID,
33+
]
34+
35+
class AzureVMResourceDetector(ResourceDetector):
36+
# pylint: disable=no-self-use
37+
def detect(self) -> "Resource":
38+
attributes = {}
39+
metadata_json = _AzureVMMetadataServiceRequestor().get_azure_vm_metadata()
40+
if not metadata_json:
41+
return Resource(attributes)
42+
for attribute_key in EXPECTED_AZURE_AMS_ATTRIBUTES:
43+
attributes[attribute_key] = _AzureVMMetadataServiceRequestor().get_attribute_from_metadata(metadata_json, attribute_key)
44+
return Resource(attributes)
45+
46+
class _AzureVMMetadataServiceRequestor:
47+
def get_azure_vm_metadata(self):
48+
request = Request(_AZURE_VM_METADATA_ENDPOINT)
49+
request.add_header("Metadata", "True")
50+
try:
51+
response = urlopen(request).read()
52+
return loads(response)["compute"]
53+
except URLError:
54+
# Not on Azure VM
55+
return None
56+
except Exception as e:
57+
_logger.exception("Failed to receive Azure VM metadata: %s", e)
58+
return None
59+
60+
def get_attribute_from_metadata(self, metadata_json, attribute_key):
61+
ams_value = ""
62+
if attribute_key == _AZURE_VM_SCALE_SET_NAME_ATTRIBUTE:
63+
ams_value = metadata_json["vmScaleSetName"]
64+
elif attribute_key == _AZURE_VM_SKU_ATTRIBUTE:
65+
ams_value = metadata_json["sku"]
66+
elif attribute_key == ResourceAttributes.CLOUD_PLATFORM:
67+
ams_value = CloudPlatformValues.AZURE_VM.value
68+
elif attribute_key == ResourceAttributes.CLOUD_PROVIDER:
69+
ams_value = CloudProviderValues.AZURE.value
70+
elif attribute_key == ResourceAttributes.CLOUD_REGION:
71+
ams_value = metadata_json["location"]
72+
elif attribute_key == _CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE:
73+
ams_value = metadata_json["resourceId"]
74+
elif attribute_key == ResourceAttributes.HOST_ID or \
75+
attribute_key == ResourceAttributes.SERVICE_INSTANCE_ID:
76+
ams_value = metadata_json["vmId"]
77+
elif attribute_key == ResourceAttributes.HOST_NAME:
78+
ams_value = metadata_json["name"]
79+
elif attribute_key == ResourceAttributes.HOST_TYPE:
80+
ams_value = metadata_json["vmSize"]
81+
elif attribute_key == ResourceAttributes.OS_TYPE:
82+
ams_value = metadata_json["osType"]
83+
elif attribute_key == ResourceAttributes.OS_VERSION:
84+
ams_value = metadata_json["version"]
85+
return ams_value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
__version__ = "0.41b0.dev"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.

0 commit comments

Comments
 (0)