Repository: open-telemetry/opentelemetry-python-contrib

Issue:
urllib instrumentation fails for local file access
When reading local files the status code is not specified and is None. This isn't handled by the instrumentation and causes an exception.

https://github.com/open-telemetry/opentelemetry-python-contrib/blob/444e0a13127304d3a04ccd44445b2e6caed3f770/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py#L212-L217
urllib instrumentation fails for local file access
When reading local files the status code is not specified and is None. This isn't handled by the instrumentation and causes an exception.

https://github.com/open-telemetry/opentelemetry-python-contrib/blob/444e0a13127304d3a04ccd44445b2e6caed3f770/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py#L212-L217


Discussion:
Thanks for reporting this. Could you please share the exception traceback and a reproducible code example? 
Traceback:
```
Traceback (most recent call last):
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 36, in wrapper
    return method(*args, **kwargs)
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/validator20.py", line 195, in validate_json
    schema, schema_path = read_resource_file(schema_path)
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 63, in read_resource_file
    return read_file(schema_path), schema_path
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 57, in read_file
    return read_url(get_uri_from_file_path(file_path))
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 67, in read_url
    with contextlib.closing(urlopen(url, timeout=timeout)) as fh:
  File "/usr/lib/python3.7/urllib/request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
  File "/opt/foo/lib/python3.7/site-packages/opentelemetry/instrumentation/urllib/__init__.py", line 163, in instrumented_open
    opener, request_, call_wrapped, get_or_create_headers
  File "/opt/foo/lib/python3.7/site-packages/opentelemetry/instrumentation/urllib/__init__.py", line 217, in _instrumented_open_call
    span.set_status(Status(http_status_to_status_code(code_)))
  File "/opt/foo/lib/python3.7/site-packages/opentelemetry/instrumentation/utils.py", line 49, in http_status_to_status_code
    if status < 100:
TypeError: '<' not supported between instances of 'NoneType' and 'int'
```
It's not minimal, but reproduction is as easy as specifying a local file as the schema for https://github.com/Yelp/swagger_spec_validator.
Is "returning a non-int status code' a common thing amongst instrumentations? If not, maybe we should treat this as a special use case and address it only for urlib.
Thanks for reporting this. Could you please share the exception traceback and a reproducible code example? 
Traceback:
```
Traceback (most recent call last):
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 36, in wrapper
    return method(*args, **kwargs)
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/validator20.py", line 195, in validate_json
    schema, schema_path = read_resource_file(schema_path)
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 63, in read_resource_file
    return read_file(schema_path), schema_path
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 57, in read_file
    return read_url(get_uri_from_file_path(file_path))
  File "/opt/foo/lib/python3.7/site-packages/swagger_spec_validator/common.py", line 67, in read_url
    with contextlib.closing(urlopen(url, timeout=timeout)) as fh:
  File "/usr/lib/python3.7/urllib/request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
  File "/opt/foo/lib/python3.7/site-packages/opentelemetry/instrumentation/urllib/__init__.py", line 163, in instrumented_open
    opener, request_, call_wrapped, get_or_create_headers
  File "/opt/foo/lib/python3.7/site-packages/opentelemetry/instrumentation/urllib/__init__.py", line 217, in _instrumented_open_call
    span.set_status(Status(http_status_to_status_code(code_)))
  File "/opt/foo/lib/python3.7/site-packages/opentelemetry/instrumentation/utils.py", line 49, in http_status_to_status_code
    if status < 100:
TypeError: '<' not supported between instances of 'NoneType' and 'int'
```
It's not minimal, but reproduction is as easy as specifying a local file as the schema for https://github.com/Yelp/swagger_spec_validator.
Is "returning a non-int status code' a common thing amongst instrumentations? If not, maybe we should treat this as a special use case and address it only for urlib.

Resolving patch:
diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py
--- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py
+++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py
@@ -49,6 +49,9 @@ def http_status_to_status_code(
         status (int): HTTP status code
     """
     # See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status
+    if not isinstance(status, int):
+        return StatusCode.UNSET
+
     if status < 100:
         return StatusCode.ERROR
     if status <= 299:


Test patch:
diff --git a/opentelemetry-instrumentation/tests/test_utils.py b/opentelemetry-instrumentation/tests/test_utils.py
--- a/opentelemetry-instrumentation/tests/test_utils.py
+++ b/opentelemetry-instrumentation/tests/test_utils.py
@@ -56,6 +56,12 @@ def test_http_status_to_status_code(self):
                 actual = http_status_to_status_code(int(status_code))
                 self.assertEqual(actual, expected, status_code)
 
+    def test_http_status_to_status_code_none(self):
+        for status_code, expected in ((None, StatusCode.UNSET),):
+            with self.subTest(status_code=status_code):
+                actual = http_status_to_status_code(status_code)
+                self.assertEqual(actual, expected, status_code)
+
     def test_http_status_to_status_code_redirect(self):
         for status_code, expected in (
             (HTTPStatus.MULTIPLE_CHOICES, StatusCode.ERROR),