mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-30 05:32:30 +08:00
Add ability to exclude some routes in fastapi and starlette (#237)
This commit is contained in:
@ -64,11 +64,7 @@ carrier_getter = CarrierGetter()
|
|||||||
def collect_request_attributes(scope):
|
def collect_request_attributes(scope):
|
||||||
"""Collects HTTP request attributes from the ASGI scope and returns a
|
"""Collects HTTP request attributes from the ASGI scope and returns a
|
||||||
dictionary to be used as span creation attributes."""
|
dictionary to be used as span creation attributes."""
|
||||||
server = scope.get("server") or ["0.0.0.0", 80]
|
server_host, port, http_url = get_host_port_url_tuple(scope)
|
||||||
port = server[1]
|
|
||||||
server_host = server[0] + (":" + str(port) if port != 80 else "")
|
|
||||||
full_path = scope.get("root_path", "") + scope.get("path", "")
|
|
||||||
http_url = scope.get("scheme", "http") + "://" + server_host + full_path
|
|
||||||
query_string = scope.get("query_string")
|
query_string = scope.get("query_string")
|
||||||
if query_string and http_url:
|
if query_string and http_url:
|
||||||
if isinstance(query_string, bytes):
|
if isinstance(query_string, bytes):
|
||||||
@ -105,6 +101,17 @@ def collect_request_attributes(scope):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_host_port_url_tuple(scope):
|
||||||
|
"""Returns (host, port, full_url) tuple.
|
||||||
|
"""
|
||||||
|
server = scope.get("server") or ["0.0.0.0", 80]
|
||||||
|
port = server[1]
|
||||||
|
server_host = server[0] + (":" + str(port) if port != 80 else "")
|
||||||
|
full_path = scope.get("root_path", "") + scope.get("path", "")
|
||||||
|
http_url = scope.get("scheme", "http") + "://" + server_host + full_path
|
||||||
|
return server_host, port, http_url
|
||||||
|
|
||||||
|
|
||||||
def set_status_code(span, status_code):
|
def set_status_code(span, status_code):
|
||||||
"""Adds HTTP response attributes to span using the status_code argument."""
|
"""Adds HTTP response attributes to span using the status_code argument."""
|
||||||
if not span.is_recording():
|
if not span.is_recording():
|
||||||
@ -152,12 +159,13 @@ class OpenTelemetryMiddleware:
|
|||||||
Optional: Defaults to get_default_span_details.
|
Optional: Defaults to get_default_span_details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app, span_details_callback=None):
|
def __init__(self, app, excluded_urls=None, span_details_callback=None):
|
||||||
self.app = guarantee_single_callable(app)
|
self.app = guarantee_single_callable(app)
|
||||||
self.tracer = trace.get_tracer(__name__, __version__)
|
self.tracer = trace.get_tracer(__name__, __version__)
|
||||||
self.span_details_callback = (
|
self.span_details_callback = (
|
||||||
span_details_callback or get_default_span_details
|
span_details_callback or get_default_span_details
|
||||||
)
|
)
|
||||||
|
self.excluded_urls = excluded_urls
|
||||||
|
|
||||||
async def __call__(self, scope, receive, send):
|
async def __call__(self, scope, receive, send):
|
||||||
"""The ASGI application
|
"""The ASGI application
|
||||||
@ -170,6 +178,10 @@ class OpenTelemetryMiddleware:
|
|||||||
if scope["type"] not in ("http", "websocket"):
|
if scope["type"] not in ("http", "websocket"):
|
||||||
return await self.app(scope, receive, send)
|
return await self.app(scope, receive, send)
|
||||||
|
|
||||||
|
_, _, url = get_host_port_url_tuple(scope)
|
||||||
|
if self.excluded_urls and self.excluded_urls.url_disabled(url):
|
||||||
|
return await self.app(scope, receive, send)
|
||||||
|
|
||||||
token = context.attach(propagators.extract(carrier_getter, scope))
|
token = context.attach(propagators.extract(carrier_getter, scope))
|
||||||
span_name, additional_attributes = self.span_details_callback(scope)
|
span_name, additional_attributes = self.span_details_callback(scope)
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Added support for excluding some routes with env var `OTEL_PYTHON_FASTAPI_EXCLUDED_URLS`
|
||||||
|
([#237](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/237))
|
||||||
|
|
||||||
## Version 0.11b0
|
## Version 0.11b0
|
||||||
|
|
||||||
Released 2020-07-28
|
Released 2020-07-28
|
||||||
|
@ -19,6 +19,21 @@ Installation
|
|||||||
|
|
||||||
pip install opentelemetry-instrumentation-fastapi
|
pip install opentelemetry-instrumentation-fastapi
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Exclude lists
|
||||||
|
*************
|
||||||
|
To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_FASTAPI_EXCLUDED_URLS`` with comma delimited regexes representing which URLs to exclude.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="client/.*/info,healthcheck"
|
||||||
|
|
||||||
|
will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
@ -16,10 +16,13 @@ from typing import Optional
|
|||||||
import fastapi
|
import fastapi
|
||||||
from starlette.routing import Match
|
from starlette.routing import Match
|
||||||
|
|
||||||
|
from opentelemetry.configuration import Configuration
|
||||||
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
||||||
from opentelemetry.instrumentation.fastapi.version import __version__ # noqa
|
from opentelemetry.instrumentation.fastapi.version import __version__ # noqa
|
||||||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
||||||
|
|
||||||
|
_excluded_urls = Configuration()._excluded_urls("fastapi")
|
||||||
|
|
||||||
|
|
||||||
class FastAPIInstrumentor(BaseInstrumentor):
|
class FastAPIInstrumentor(BaseInstrumentor):
|
||||||
"""An instrumentor for FastAPI
|
"""An instrumentor for FastAPI
|
||||||
@ -36,6 +39,7 @@ class FastAPIInstrumentor(BaseInstrumentor):
|
|||||||
if not getattr(app, "is_instrumented_by_opentelemetry", False):
|
if not getattr(app, "is_instrumented_by_opentelemetry", False):
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
OpenTelemetryMiddleware,
|
OpenTelemetryMiddleware,
|
||||||
|
excluded_urls=_excluded_urls,
|
||||||
span_details_callback=_get_route_details,
|
span_details_callback=_get_route_details,
|
||||||
)
|
)
|
||||||
app.is_instrumented_by_opentelemetry = True
|
app.is_instrumented_by_opentelemetry = True
|
||||||
@ -52,7 +56,9 @@ class _InstrumentedFastAPI(fastapi.FastAPI):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.add_middleware(
|
self.add_middleware(
|
||||||
OpenTelemetryMiddleware, span_details_callback=_get_route_details
|
OpenTelemetryMiddleware,
|
||||||
|
excluded_urls=_excluded_urls,
|
||||||
|
span_details_callback=_get_route_details,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,11 +13,13 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import fastapi
|
import fastapi
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
import opentelemetry.instrumentation.fastapi as otel_fastapi
|
import opentelemetry.instrumentation.fastapi as otel_fastapi
|
||||||
|
from opentelemetry.configuration import Configuration
|
||||||
from opentelemetry.test.test_base import TestBase
|
from opentelemetry.test.test_base import TestBase
|
||||||
|
|
||||||
|
|
||||||
@ -29,10 +31,26 @@ class TestFastAPIManualInstrumentation(TestBase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
Configuration()._reset()
|
||||||
|
self.env_patch = patch.dict(
|
||||||
|
"os.environ",
|
||||||
|
{"OTEL_PYTHON_FASTAPI_EXCLUDED_URLS": "/exclude/123,healthzz"},
|
||||||
|
)
|
||||||
|
self.env_patch.start()
|
||||||
|
self.exclude_patch = patch(
|
||||||
|
"opentelemetry.instrumentation.fastapi._excluded_urls",
|
||||||
|
Configuration()._excluded_urls("fastapi"),
|
||||||
|
)
|
||||||
|
self.exclude_patch.start()
|
||||||
self._instrumentor = otel_fastapi.FastAPIInstrumentor()
|
self._instrumentor = otel_fastapi.FastAPIInstrumentor()
|
||||||
self._app = self._create_app()
|
self._app = self._create_app()
|
||||||
self._client = TestClient(self._app)
|
self._client = TestClient(self._app)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.env_patch.stop()
|
||||||
|
self.exclude_patch.stop()
|
||||||
|
|
||||||
def test_basic_fastapi_call(self):
|
def test_basic_fastapi_call(self):
|
||||||
self._client.get("/foobar")
|
self._client.get("/foobar")
|
||||||
spans = self.memory_exporter.get_finished_spans()
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
@ -54,6 +72,15 @@ class TestFastAPIManualInstrumentation(TestBase):
|
|||||||
# the asgi instrumentation is successfully feeding though.
|
# the asgi instrumentation is successfully feeding though.
|
||||||
self.assertEqual(spans[-1].attributes["http.flavor"], "1.1")
|
self.assertEqual(spans[-1].attributes["http.flavor"], "1.1")
|
||||||
|
|
||||||
|
def test_fastapi_excluded_urls(self):
|
||||||
|
"""Ensure that given fastapi routes are excluded."""
|
||||||
|
self._client.get("/exclude/123")
|
||||||
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans), 0)
|
||||||
|
self._client.get("/healthzz")
|
||||||
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans), 0)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_fastapi_app():
|
def _create_fastapi_app():
|
||||||
app = fastapi.FastAPI()
|
app = fastapi.FastAPI()
|
||||||
@ -66,6 +93,14 @@ class TestFastAPIManualInstrumentation(TestBase):
|
|||||||
async def _(username: str):
|
async def _(username: str):
|
||||||
return {"message": username}
|
return {"message": username}
|
||||||
|
|
||||||
|
@app.get("/exclude/{param}")
|
||||||
|
async def _(param: str):
|
||||||
|
return {"message": param}
|
||||||
|
|
||||||
|
@app.get("/healthzz")
|
||||||
|
async def health():
|
||||||
|
return {"message": "ok"}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
- Added support for excluding some routes with env var `OTEL_PYTHON_STARLETTE_EXCLUDED_URLS`
|
||||||
|
([#237](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/237))
|
||||||
|
|
||||||
## Version 0.10b0
|
## Version 0.10b0
|
||||||
|
|
||||||
|
@ -19,6 +19,21 @@ Installation
|
|||||||
|
|
||||||
pip install opentelemetry-instrumentation-starlette
|
pip install opentelemetry-instrumentation-starlette
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Exclude lists
|
||||||
|
*************
|
||||||
|
To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_STARLETTE_EXCLUDED_URLS`` with comma delimited regexes representing which URLs to exclude.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
export OTEL_PYTHON_STARLETTE_EXCLUDED_URLS="client/.*/info,healthcheck"
|
||||||
|
|
||||||
|
will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
@ -16,10 +16,13 @@ from typing import Optional
|
|||||||
from starlette import applications
|
from starlette import applications
|
||||||
from starlette.routing import Match
|
from starlette.routing import Match
|
||||||
|
|
||||||
|
from opentelemetry.configuration import Configuration
|
||||||
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
||||||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
||||||
from opentelemetry.instrumentation.starlette.version import __version__ # noqa
|
from opentelemetry.instrumentation.starlette.version import __version__ # noqa
|
||||||
|
|
||||||
|
_excluded_urls = Configuration()._excluded_urls("starlette")
|
||||||
|
|
||||||
|
|
||||||
class StarletteInstrumentor(BaseInstrumentor):
|
class StarletteInstrumentor(BaseInstrumentor):
|
||||||
"""An instrumentor for starlette
|
"""An instrumentor for starlette
|
||||||
@ -36,6 +39,7 @@ class StarletteInstrumentor(BaseInstrumentor):
|
|||||||
if not getattr(app, "is_instrumented_by_opentelemetry", False):
|
if not getattr(app, "is_instrumented_by_opentelemetry", False):
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
OpenTelemetryMiddleware,
|
OpenTelemetryMiddleware,
|
||||||
|
excluded_urls=_excluded_urls,
|
||||||
span_details_callback=_get_route_details,
|
span_details_callback=_get_route_details,
|
||||||
)
|
)
|
||||||
app.is_instrumented_by_opentelemetry = True
|
app.is_instrumented_by_opentelemetry = True
|
||||||
@ -52,7 +56,9 @@ class _InstrumentedStarlette(applications.Starlette):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.add_middleware(
|
self.add_middleware(
|
||||||
OpenTelemetryMiddleware, span_details_callback=_get_route_details
|
OpenTelemetryMiddleware,
|
||||||
|
excluded_urls=_excluded_urls,
|
||||||
|
span_details_callback=_get_route_details,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from starlette import applications
|
from starlette import applications
|
||||||
from starlette.responses import PlainTextResponse
|
from starlette.responses import PlainTextResponse
|
||||||
@ -20,6 +21,7 @@ from starlette.routing import Route
|
|||||||
from starlette.testclient import TestClient
|
from starlette.testclient import TestClient
|
||||||
|
|
||||||
import opentelemetry.instrumentation.starlette as otel_starlette
|
import opentelemetry.instrumentation.starlette as otel_starlette
|
||||||
|
from opentelemetry.configuration import Configuration
|
||||||
from opentelemetry.test.test_base import TestBase
|
from opentelemetry.test.test_base import TestBase
|
||||||
|
|
||||||
|
|
||||||
@ -31,10 +33,26 @@ class TestStarletteManualInstrumentation(TestBase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
Configuration()._reset()
|
||||||
|
self.env_patch = patch.dict(
|
||||||
|
"os.environ",
|
||||||
|
{"OTEL_PYTHON_STARLETTE_EXCLUDED_URLS": "/exclude/123,healthzz"},
|
||||||
|
)
|
||||||
|
self.env_patch.start()
|
||||||
|
self.exclude_patch = patch(
|
||||||
|
"opentelemetry.instrumentation.starlette._excluded_urls",
|
||||||
|
Configuration()._excluded_urls("starlette"),
|
||||||
|
)
|
||||||
|
self.exclude_patch.start()
|
||||||
self._instrumentor = otel_starlette.StarletteInstrumentor()
|
self._instrumentor = otel_starlette.StarletteInstrumentor()
|
||||||
self._app = self._create_app()
|
self._app = self._create_app()
|
||||||
self._client = TestClient(self._app)
|
self._client = TestClient(self._app)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.env_patch.stop()
|
||||||
|
self.exclude_patch.stop()
|
||||||
|
|
||||||
def test_basic_starlette_call(self):
|
def test_basic_starlette_call(self):
|
||||||
self._client.get("/foobar")
|
self._client.get("/foobar")
|
||||||
spans = self.memory_exporter.get_finished_spans()
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
@ -56,13 +74,26 @@ class TestStarletteManualInstrumentation(TestBase):
|
|||||||
# the asgi instrumentation is successfully feeding though.
|
# the asgi instrumentation is successfully feeding though.
|
||||||
self.assertEqual(spans[-1].attributes["http.flavor"], "1.1")
|
self.assertEqual(spans[-1].attributes["http.flavor"], "1.1")
|
||||||
|
|
||||||
|
def test_starlette_excluded_urls(self):
|
||||||
|
"""Ensure that givem starlette routes are excluded."""
|
||||||
|
self._client.get("/healthzz")
|
||||||
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans), 0)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_starlette_app():
|
def _create_starlette_app():
|
||||||
def home(_):
|
def home(_):
|
||||||
return PlainTextResponse("hi")
|
return PlainTextResponse("hi")
|
||||||
|
|
||||||
|
def health(_):
|
||||||
|
return PlainTextResponse("ok")
|
||||||
|
|
||||||
app = applications.Starlette(
|
app = applications.Starlette(
|
||||||
routes=[Route("/foobar", home), Route("/user/{username}", home)]
|
routes=[
|
||||||
|
Route("/foobar", home),
|
||||||
|
Route("/user/{username}", home),
|
||||||
|
Route("/healthzz", health),
|
||||||
|
]
|
||||||
)
|
)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user