Skip to content

Commit

Permalink
Implement WSGI middleware integration. (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-feld authored Aug 19, 2019
1 parent ae13b26 commit 17afba5
Show file tree
Hide file tree
Showing 8 changed files with 452 additions and 7 deletions.
47 changes: 47 additions & 0 deletions ext/opentelemetry-ext-wsgi/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
OpenTelemetry WSGI Middleware
=============================

This library provides a WSGI middleware that can be used on any WSGI framework
(such as Django / Flask) to track requests timing through OpenTelemetry.


Usage (Flask)
-------------

.. code-block:: python
from flask import Flask
from opentelemetry.ext.wsgi import OpenTelemetryMiddleware
app = Flask(__name__)
app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)
@app.route("/")
def hello():
return "Hello!"
if __name__ == "__main__":
app.run(debug=True)
Usage (Django)
--------------

Modify the application's ``wsgi.py`` file as shown below.

.. code-block:: python
import os
from opentelemetry.ext.wsgi import OpenTelemetryMiddleware
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
application = get_wsgi_application()
application = OpenTelemetryMiddleware(application)
References
----------

* `OpenTelemetry Project <https://opentelemetry.io/>`_
* `WSGI <https://www.python.org/dev/peps/pep-3333>`_
45 changes: 45 additions & 0 deletions ext/opentelemetry-ext-wsgi/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2019, 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-ext-wsgi
description = WSGI Middleware for OpenTelemetry
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-ext-wsgi
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 3 - Alpha
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7

[options]
python_requires = >=3.4
package_dir=
=src
packages=find:
install_requires =
opentelemetry-api

[options.packages.find]
where = src
30 changes: 30 additions & 0 deletions ext/opentelemetry-ext-wsgi/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2019, 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",
"ext",
"wsgi",
"version.py",
)
PACKAGE_INFO = {}
with open(VERSION_FILENAME) as f:
exec(f.read(), PACKAGE_INFO)

setuptools.setup(version=PACKAGE_INFO["__version__"])
105 changes: 105 additions & 0 deletions ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright 2019, 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.

"""
The opentelemetry-ext-wsgi package provides a WSGI middleware that can be used
on any WSGI framework (such as Django / Flask) to track requests timing through
OpenTelemetry.
"""

import functools
import wsgiref.util as wsgiref_util

from opentelemetry import trace
from opentelemetry.ext.wsgi.version import __version__ # noqa


class OpenTelemetryMiddleware:
"""The WSGI application middleware.
This class is used to create and annotate spans for requests to a WSGI
application.
Args:
wsgi: The WSGI application callable.
"""

def __init__(
self,
wsgi,
propagators=None,
):
self.wsgi = wsgi

# TODO: implement context propagation
self.propagators = propagators

@staticmethod
def _add_request_attributes(span, environ):
span.set_attribute("component", "http")
span.set_attribute("http.method", environ["REQUEST_METHOD"])

host = environ.get("HTTP_HOST") or environ["SERVER_NAME"]
span.set_attribute("http.host", host)

url = (
environ.get("REQUEST_URI") or
environ.get("RAW_URI") or
wsgiref_util.request_uri(environ, include_query=False)
)
span.set_attribute("http.url", url)

@staticmethod
def _add_response_attributes(span, status):
status_code, status_text = status.split(" ", 1)
span.set_attribute("http.status_text", status_text)

try:
status_code = int(status_code)
except ValueError:
pass
else:
span.set_attribute("http.status_code", status_code)

@classmethod
def _create_start_response(cls, span, start_response):
@functools.wraps(start_response)
def _start_response(status, response_headers, *args, **kwargs):
cls._add_response_attributes(span, status)
return start_response(status, response_headers, *args, **kwargs)

return _start_response

def __call__(self, environ, start_response):
"""The WSGI application
Args:
environ: A WSGI environment.
start_response: The WSGI start_response callable.
"""

tracer = trace.tracer()
path_info = environ["PATH_INFO"] or "/"

with tracer.start_span(path_info) as span:
self._add_request_attributes(span, environ)
start_response = self._create_start_response(span, start_response)

iterable = self.wsgi(environ, start_response)
try:
for yielded in iterable:
yield yielded
finally:
if hasattr(iterable, 'close'):
iterable.close()
15 changes: 15 additions & 0 deletions ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2019, 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.

__version__ = "0.1.dev0"
Empty file.
Loading

0 comments on commit 17afba5

Please sign in to comment.