Skip to content

Commit 39fa078

Browse files
toumorokoshilzchenalrex
authored
starlette instrumentation (open-telemetry#777)
adding an initial starlette instrumentation. tox does exact match on fields delimited by a dash. Thus, any instrumentation that includes "instrumentation" in the name would collide with testing of the "opentelemetry-instrumentation" package. Renaming opentelemetry-instrumentation to opentelemetry-instrumentation-base to fix that. Co-authored-by: Leighton Chen <lechen@microsoft.com> Co-authored-by: alrex <aboten@lightstep.com>
1 parent b70450e commit 39fa078

File tree

21 files changed

+423
-101
lines changed

21 files changed

+423
-101
lines changed

docs-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ boto~=2.0
2626
google-cloud-trace >=0.23.0
2727
google-cloud-monitoring>=0.36.0
2828
botocore~=1.0
29+
starlette~=0.13

docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
__version__ = "0.9.dev0"
15+
__version__ = "0.10.dev0"

docs/ext/starlette/starlette.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.. include:: ../../../ext/opentelemetry-instrumentation-starlette/README.rst
2+
3+
API
4+
---
5+
6+
.. automodule:: opentelemetry.instrumentation.starlette
7+
:members:
8+
:undoc-members:
9+
:show-inheritance:

ext/opentelemetry-ext-asgi/setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ package_dir=
4040
packages=find_namespace:
4141
install_requires =
4242
opentelemetry-api == 0.10.dev0
43+
opentelemetry-instrumentation == 0.10.dev0
4344
asgiref ~= 3.0
4445

4546
[options.extras_require]

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

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
import typing
2323
import urllib
2424
from functools import wraps
25+
from typing import Tuple
2526

2627
from asgiref.compatibility import guarantee_single_callable
2728

2829
from opentelemetry import context, propagators, trace
2930
from opentelemetry.ext.asgi.version import __version__ # noqa
31+
from opentelemetry.instrumentation.utils import http_status_to_canonical_code
3032
from opentelemetry.trace.status import Status, StatusCanonicalCode
3133

3234

@@ -44,37 +46,6 @@ def get_header_from_scope(scope: dict, header_name: str) -> typing.List[str]:
4446
]
4547

4648

47-
def http_status_to_canonical_code(code: int, allow_redirect: bool = True):
48-
# pylint:disable=too-many-branches,too-many-return-statements
49-
if code < 100:
50-
return StatusCanonicalCode.UNKNOWN
51-
if code <= 299:
52-
return StatusCanonicalCode.OK
53-
if code <= 399:
54-
if allow_redirect:
55-
return StatusCanonicalCode.OK
56-
return StatusCanonicalCode.DEADLINE_EXCEEDED
57-
if code <= 499:
58-
if code == 401: # HTTPStatus.UNAUTHORIZED:
59-
return StatusCanonicalCode.UNAUTHENTICATED
60-
if code == 403: # HTTPStatus.FORBIDDEN:
61-
return StatusCanonicalCode.PERMISSION_DENIED
62-
if code == 404: # HTTPStatus.NOT_FOUND:
63-
return StatusCanonicalCode.NOT_FOUND
64-
if code == 429: # HTTPStatus.TOO_MANY_REQUESTS:
65-
return StatusCanonicalCode.RESOURCE_EXHAUSTED
66-
return StatusCanonicalCode.INVALID_ARGUMENT
67-
if code <= 599:
68-
if code == 501: # HTTPStatus.NOT_IMPLEMENTED:
69-
return StatusCanonicalCode.UNIMPLEMENTED
70-
if code == 503: # HTTPStatus.SERVICE_UNAVAILABLE:
71-
return StatusCanonicalCode.UNAVAILABLE
72-
if code == 504: # HTTPStatus.GATEWAY_TIMEOUT:
73-
return StatusCanonicalCode.DEADLINE_EXCEEDED
74-
return StatusCanonicalCode.INTERNAL
75-
return StatusCanonicalCode.UNKNOWN
76-
77-
7849
def collect_request_attributes(scope):
7950
"""Collects HTTP request attributes from the ASGI scope and returns a
8051
dictionary to be used as span creation attributes."""
@@ -134,11 +105,19 @@ def set_status_code(span, status_code):
134105
span.set_status(Status(http_status_to_canonical_code(status_code)))
135106

136107

137-
def get_default_span_name(scope):
138-
"""Default implementation for name_callback"""
108+
def get_default_span_details(scope: dict) -> Tuple[str, dict]:
109+
"""Default implementation for span_details_callback
110+
111+
Args:
112+
scope: the asgi scope dictionary
113+
114+
Returns:
115+
a tuple of the span, and any attributes to attach to the
116+
span.
117+
"""
139118
method_or_path = scope.get("method") or scope.get("path")
140119

141-
return method_or_path
120+
return method_or_path, {}
142121

143122

144123
class OpenTelemetryMiddleware:
@@ -149,15 +128,18 @@ class OpenTelemetryMiddleware:
149128
150129
Args:
151130
app: The ASGI application callable to forward requests to.
152-
name_callback: Callback which calculates a generic span name for an
153-
incoming HTTP request based on the ASGI scope.
154-
Optional: Defaults to get_default_span_name.
131+
span_details_callback: Callback which should return a string
132+
and a tuple, representing the desired span name and a
133+
dictionary with any additional span attributes to set.
134+
Optional: Defaults to get_default_span_details.
155135
"""
156136

157-
def __init__(self, app, name_callback=None):
137+
def __init__(self, app, span_details_callback=None):
158138
self.app = guarantee_single_callable(app)
159139
self.tracer = trace.get_tracer(__name__, __version__)
160-
self.name_callback = name_callback or get_default_span_name
140+
self.span_details_callback = (
141+
span_details_callback or get_default_span_details
142+
)
161143

162144
async def __call__(self, scope, receive, send):
163145
"""The ASGI application
@@ -173,13 +155,15 @@ async def __call__(self, scope, receive, send):
173155
token = context.attach(
174156
propagators.extract(get_header_from_scope, scope)
175157
)
176-
span_name = self.name_callback(scope)
158+
span_name, additional_attributes = self.span_details_callback(scope)
159+
attributes = collect_request_attributes(scope)
160+
attributes.update(additional_attributes)
177161

178162
try:
179163
with self.tracer.start_as_current_span(
180164
span_name + " asgi",
181165
kind=trace.SpanKind.SERVER,
182-
attributes=collect_request_attributes(scope),
166+
attributes=attributes,
183167
):
184168

185169
@wraps(receive)

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,8 @@ def test_override_span_name(self):
176176
"""Test that span_names can be overwritten by our callback function."""
177177
span_name = "Dymaxion"
178178

179-
# pylint:disable=unused-argument
180-
def get_predefined_span_name(scope):
181-
return span_name
179+
def get_predefined_span_details(_):
180+
return span_name, {}
182181

183182
def update_expected_span_name(expected):
184183
for entry in expected:
@@ -188,7 +187,7 @@ def update_expected_span_name(expected):
188187
return expected
189188

190189
app = otel_asgi.OpenTelemetryMiddleware(
191-
simple_asgi, name_callback=get_predefined_span_name
190+
simple_asgi, span_details_callback=get_predefined_span_details
192191
)
193192
self.seed_app(app)
194193
self.send_default_request()

ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
import boto.elasticache
2020
import boto.s3
2121
import boto.sts
22-
2322
from moto import ( # pylint: disable=import-error
2423
mock_ec2_deprecated,
2524
mock_lambda_deprecated,
2625
mock_s3_deprecated,
2726
mock_sts_deprecated,
2827
)
28+
2929
from opentelemetry.ext.boto import BotoInstrumentor
3030
from opentelemetry.test.test_base import TestBase
3131

ext/opentelemetry-ext-botocore/tests/test_botocore_instrumentation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import botocore.session
22
from botocore.exceptions import ParamValidationError
3-
43
from moto import ( # pylint: disable=import-error
54
mock_ec2,
65
mock_kinesis,
@@ -9,6 +8,7 @@
98
mock_s3,
109
mock_sqs,
1110
)
11+
1212
from opentelemetry.ext.botocore import BotocoreInstrumentor
1313
from opentelemetry.test.test_base import TestBase
1414

ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
DEFAULT_AGENT_URL = "http://localhost:8126"
3434
_INSTRUMENTATION_SPAN_TYPES = {
3535
"opentelemetry.ext.aiohttp-client": DatadogSpanTypes.HTTP,
36+
"opentelemetry.ext.asgi": DatadogSpanTypes.WEB,
3637
"opentelemetry.ext.dbapi": DatadogSpanTypes.SQL,
3738
"opentelemetry.ext.django": DatadogSpanTypes.WEB,
3839
"opentelemetry.ext.flask": DatadogSpanTypes.WEB,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
- Initial release ([#777](https://github.com/open-telemetry/opentelemetry-python/pull/777))

0 commit comments

Comments
 (0)