Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

starlette instrumentation #777

Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
48ebb60
Have a basic starlette instrumentation working.
toumorokoshi Jun 4, 2020
537029e
fix typo
toumorokoshi Jun 9, 2020
3bc1aec
Merge branch 'master' into feature/starlette-instrumentation
toumorokoshi Jun 9, 2020
e89c37d
Fleshing out instrumentor interface.
toumorokoshi Jun 9, 2020
26464db
more changes.
toumorokoshi Jun 9, 2020
1300812
tox: rename instrumentation to instrumentation-base
toumorokoshi Jun 9, 2020
16c9921
fixing lint
toumorokoshi Jun 9, 2020
56330ec
Merge remote-tracking branch 'source/master' into feature/starlette-i…
toumorokoshi Jun 9, 2020
342d3b6
Adding entry point for starlette instrumentation
toumorokoshi Jun 10, 2020
6f7e875
removing unused middleware
toumorokoshi Jun 10, 2020
1e6e5d8
Add SumObserver and UpDownSumObserver instruments (#789)
lzchen Jun 9, 2020
bee354a
Add start_pipeline to MeterProvider in SDK, atexit moved to MeterProv…
lzchen Jun 9, 2020
37af971
chore: Add majorgreys to approvers (#802)
Jun 10, 2020
fea75c7
some refactoring to use opentelemetry-instrumentation
toumorokoshi Jun 11, 2020
8c129a3
Adding documentation, more unit tests
toumorokoshi Jun 11, 2020
5488878
Merge remote-tracking branch 'source/master' into feature/starlette-i…
toumorokoshi Jun 11, 2020
0a6222a
Fixing linting
toumorokoshi Jun 11, 2020
83aff3e
removing invalid python envs, fixing tox docker-tests
toumorokoshi Jun 11, 2020
d5f498d
removing starlette invalid python versions.
toumorokoshi Jun 11, 2020
871ee6e
addressing feedback
toumorokoshi Jun 12, 2020
1b85bd4
More fixes
toumorokoshi Jun 12, 2020
a9dbb29
Apply suggestions from code review
toumorokoshi Jun 15, 2020
40b8941
fixing tox syntax
toumorokoshi Jun 15, 2020
1cdd5cf
Merge branch 'master' into feature/starlette-instrumentation
toumorokoshi Jun 15, 2020
bf99f5f
Merge branch 'master' into feature/starlette-instrumentation
toumorokoshi Jun 15, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ boto~=2.0
google-cloud-trace >=0.23.0
google-cloud-monitoring>=0.36.0
botocore~=1.0
starlette~=0.13
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = "0.9.dev0"
__version__ = "0.10.dev0"
9 changes: 9 additions & 0 deletions docs/ext/starlette/starlette.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.. include:: ../../../ext/opentelemetry-instrumentation-starlette/README.rst

API
---

.. automodule:: opentelemetry.instrumentation.starlette
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions ext/opentelemetry-ext-asgi/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ package_dir=
packages=find_namespace:
install_requires =
opentelemetry-api == 0.10.dev0
opentelemetry-instrumentation == 0.10.dev0
asgiref ~= 3.0

[options.extras_require]
Expand Down
66 changes: 25 additions & 41 deletions ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
import typing
import urllib
from functools import wraps
from typing import Tuple

from asgiref.compatibility import guarantee_single_callable

from opentelemetry import context, propagators, trace
from opentelemetry.ext.asgi.version import __version__ # noqa
from opentelemetry.instrumentation.utils import http_status_to_canonical_code
from opentelemetry.trace.status import Status, StatusCanonicalCode


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


def http_status_to_canonical_code(code: int, allow_redirect: bool = True):
# pylint:disable=too-many-branches,too-many-return-statements
if code < 100:
return StatusCanonicalCode.UNKNOWN
if code <= 299:
return StatusCanonicalCode.OK
if code <= 399:
if allow_redirect:
return StatusCanonicalCode.OK
return StatusCanonicalCode.DEADLINE_EXCEEDED
if code <= 499:
if code == 401: # HTTPStatus.UNAUTHORIZED:
return StatusCanonicalCode.UNAUTHENTICATED
if code == 403: # HTTPStatus.FORBIDDEN:
return StatusCanonicalCode.PERMISSION_DENIED
if code == 404: # HTTPStatus.NOT_FOUND:
return StatusCanonicalCode.NOT_FOUND
if code == 429: # HTTPStatus.TOO_MANY_REQUESTS:
return StatusCanonicalCode.RESOURCE_EXHAUSTED
return StatusCanonicalCode.INVALID_ARGUMENT
if code <= 599:
if code == 501: # HTTPStatus.NOT_IMPLEMENTED:
return StatusCanonicalCode.UNIMPLEMENTED
if code == 503: # HTTPStatus.SERVICE_UNAVAILABLE:
return StatusCanonicalCode.UNAVAILABLE
if code == 504: # HTTPStatus.GATEWAY_TIMEOUT:
return StatusCanonicalCode.DEADLINE_EXCEEDED
return StatusCanonicalCode.INTERNAL
return StatusCanonicalCode.UNKNOWN


def collect_request_attributes(scope):
"""Collects HTTP request attributes from the ASGI scope and returns a
dictionary to be used as span creation attributes."""
Expand Down Expand Up @@ -134,11 +105,19 @@ def set_status_code(span, status_code):
span.set_status(Status(http_status_to_canonical_code(status_code)))


def get_default_span_name(scope):
"""Default implementation for name_callback"""
def get_default_span_details(scope: dict) -> Tuple[str, dict]:
"""Default implementation for span_details_callback

Args:
scope: the asgi scope dictionary

Returns:
a tuple of the span, and any attributes to attach to the
span.
"""
method_or_path = scope.get("method") or scope.get("path")

return method_or_path
return method_or_path, {}


class OpenTelemetryMiddleware:
Expand All @@ -149,15 +128,18 @@ class OpenTelemetryMiddleware:

Args:
app: The ASGI application callable to forward requests to.
name_callback: Callback which calculates a generic span name for an
incoming HTTP request based on the ASGI scope.
Optional: Defaults to get_default_span_name.
span_details_callback: Callback which should return a string
and a tuple, representing the desired span name and a
dictionary with any additional span attributes to set.
Optional: Defaults to get_default_span_details.
"""

def __init__(self, app, name_callback=None):
def __init__(self, app, span_details_callback=None):
self.app = guarantee_single_callable(app)
self.tracer = trace.get_tracer(__name__, __version__)
self.name_callback = name_callback or get_default_span_name
self.span_details_callback = (
span_details_callback or get_default_span_details
)

async def __call__(self, scope, receive, send):
"""The ASGI application
Expand All @@ -173,13 +155,15 @@ async def __call__(self, scope, receive, send):
token = context.attach(
propagators.extract(get_header_from_scope, scope)
)
span_name = self.name_callback(scope)
span_name, additional_attributes = self.span_details_callback(scope)
attributes = collect_request_attributes(scope)
attributes.update(additional_attributes)

try:
with self.tracer.start_as_current_span(
span_name + " asgi",
kind=trace.SpanKind.SERVER,
attributes=collect_request_attributes(scope),
attributes=attributes,
):

@wraps(receive)
Expand Down
7 changes: 3 additions & 4 deletions ext/opentelemetry-ext-asgi/tests/test_asgi_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,8 @@ def test_override_span_name(self):
"""Test that span_names can be overwritten by our callback function."""
span_name = "Dymaxion"

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

def update_expected_span_name(expected):
for entry in expected:
Expand All @@ -188,7 +187,7 @@ def update_expected_span_name(expected):
return expected

app = otel_asgi.OpenTelemetryMiddleware(
simple_asgi, name_callback=get_predefined_span_name
simple_asgi, span_details_callback=get_predefined_span_details
)
self.seed_app(app)
self.send_default_request()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
import boto.elasticache
import boto.s3
import boto.sts

from moto import ( # pylint: disable=import-error
mock_ec2_deprecated,
mock_lambda_deprecated,
mock_s3_deprecated,
mock_sts_deprecated,
)

from opentelemetry.ext.boto import BotoInstrumentor
from opentelemetry.test.test_base import TestBase

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import botocore.session
from botocore.exceptions import ParamValidationError

from moto import ( # pylint: disable=import-error
mock_ec2,
mock_kinesis,
Expand All @@ -9,6 +8,7 @@
mock_s3,
mock_sqs,
)

from opentelemetry.ext.botocore import BotocoreInstrumentor
from opentelemetry.test.test_base import TestBase

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
DEFAULT_AGENT_URL = "http://localhost:8126"
_INSTRUMENTATION_SPAN_TYPES = {
"opentelemetry.ext.aiohttp-client": DatadogSpanTypes.HTTP,
"opentelemetry.ext.asgi": DatadogSpanTypes.WEB,
"opentelemetry.ext.dbapi": DatadogSpanTypes.SQL,
"opentelemetry.ext.django": DatadogSpanTypes.WEB,
"opentelemetry.ext.flask": DatadogSpanTypes.WEB,
Expand Down
5 changes: 5 additions & 0 deletions ext/opentelemetry-instrumentation-starlette/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## Unreleased

- Initial release ([#777](https://github.com/open-telemetry/opentelemetry-python/pull/777))
45 changes: 45 additions & 0 deletions ext/opentelemetry-instrumentation-starlette/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
OpenTelemetry Starlette Instrumentation
=======================================

|pypi|

.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-starlette.svg
:target: https://pypi.org/project/opentelemetry-instrumentation-starlette/


This library provides automatic and manual instrumentation of Starlette web frameworks,
instrumenting http requests served by applications utilizing the framework.

auto-instrumentation using the opentelemetry-instrumentation package is also supported.

Installation
------------

::

pip install opentelemetry-instrumentation-starlette


Usage
-----

.. code-block:: python

from opentelemetry.instrumentation.starlette import StarletteInstrumentor
from starlette import applications
from starlette.responses import PlainTextResponse
from starlette.routing import Route

def home(request):
return PlainTextResponse("hi")

app = applications.Starlette(
routes=[Route("/foobar", home)]
)
StarletteInstrumentor.instrument_app(app)


References
----------

* `OpenTelemetry Project <https://opentelemetry.io/>`_
55 changes: 55 additions & 0 deletions ext/opentelemetry-instrumentation-starlette/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
[metadata]
name = opentelemetry-instrumentation-starlette
description = OpenTelemetry Starlette Instrumentation
long_description = file: README.rst
long_description_content_type = text/x-rst
author = OpenTelemetry Authors
author_email = cncf-opentelemetry-contributors@lists.cncf.io
url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-instrumentation-starlette
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 4 - Beta
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8

[options]
python_requires = >=3.6
package_dir=
=src
packages=find_namespace:
install_requires =
opentelemetry-api == 0.10.dev0
opentelemetry-ext-asgi == 0.10.dev0

[options.entry_points]
opentelemetry_instrumentor =
starlette = opentelemetry.instrumentation.starlette:StarletteInstrumentor

[options.extras_require]
test =
opentelemetry-test == 0.10.dev0
starlette ~= 0.13.0
requests ~= 2.23.0 # needed for testclient

[options.packages.find]
where = src
31 changes: 31 additions & 0 deletions ext/opentelemetry-instrumentation-starlette/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os

import setuptools

BASE_DIR = os.path.dirname(__file__)
VERSION_FILENAME = os.path.join(
BASE_DIR,
"src",
"opentelemetry",
"instrumentation",
"starlette",
"version.py",
)
PACKAGE_INFO = {}
with open(VERSION_FILENAME) as f:
exec(f.read(), PACKAGE_INFO)

setuptools.setup(version=PACKAGE_INFO["__version__"])
Loading