generated from amazon-archives/__template_Apache-2.0
-
Notifications
You must be signed in to change notification settings - Fork 26
support code attributes for starlette, fastapi, flask and aws lambda #488
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
c64b51e
support code attributes for lambda
wangzlei d416381
support code attributes for flask
wangzlei cae3f4b
support code attributes for fastapi and starlette
wangzlei a80e98d
improve coverage
wangzlei 4ea6fd2
fix lint, add dev dependencies
wangzlei 0a50295
add opentelemetry test dependency
wangzlei 8b8a79f
update comments
wangzlei 9ac3321
fix comments
wangzlei File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_fastapi_patches.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License. | ||
|
||
from logging import getLogger | ||
|
||
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import get_code_correlation_enabled_status | ||
|
||
_logger = getLogger(__name__) | ||
|
||
|
||
def _apply_fastapi_instrumentation_patches() -> None: | ||
"""FastAPI instrumentation patches | ||
|
||
Applies patches to provide code attributes support for FastAPI instrumentation. | ||
This patches the FastAPI instrumentation to automatically add code attributes | ||
to spans by decorating view functions with record_code_attributes. | ||
""" | ||
if get_code_correlation_enabled_status() is True: | ||
_apply_fastapi_code_attributes_patch() | ||
|
||
|
||
def _apply_fastapi_code_attributes_patch() -> None: | ||
"""FastAPI instrumentation patch for code attributes | ||
|
||
This patch modifies the FastAPI instrumentation to automatically apply | ||
the current_span_code_attributes decorator to all endpoint functions when | ||
the FastAPI app is instrumented. | ||
|
||
The patch: | ||
1. Imports current_span_code_attributes decorator from AWS distro utils | ||
2. Hooks FastAPI's APIRouter.add_api_route method during instrumentation | ||
3. Automatically decorates endpoint functions as they are registered | ||
4. Adds code.function.name, code.file.path, and code.line.number to spans | ||
5. Provides cleanup during uninstrumentation | ||
""" | ||
try: | ||
# Import FastAPI instrumentation classes and AWS decorator | ||
from fastapi import routing # pylint: disable=import-outside-toplevel | ||
|
||
from amazon.opentelemetry.distro.code_correlation import ( # pylint: disable=import-outside-toplevel | ||
record_code_attributes, | ||
) | ||
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor # pylint: disable=import-outside-toplevel | ||
|
||
# Store the original _instrument and _uninstrument methods | ||
original_instrument = FastAPIInstrumentor._instrument | ||
original_uninstrument = FastAPIInstrumentor._uninstrument | ||
|
||
def _wrapped_add_api_route(original_add_api_route_method): | ||
"""Wrapper for APIRouter.add_api_route method.""" | ||
|
||
def wrapper(self, *args, **kwargs): | ||
# Apply current_span_code_attributes decorator to endpoint function | ||
try: | ||
# Get endpoint function from args or kwargs | ||
endpoint = None | ||
if len(args) >= 2: | ||
endpoint = args[1] | ||
else: | ||
endpoint = kwargs.get("endpoint") | ||
|
||
if endpoint and callable(endpoint): | ||
# Check if function is already decorated (avoid double decoration) | ||
if not hasattr(endpoint, "_current_span_code_attributes_decorated"): | ||
# Apply decorator | ||
decorated_endpoint = record_code_attributes(endpoint) | ||
# Mark as decorated to avoid double decoration | ||
decorated_endpoint._current_span_code_attributes_decorated = True | ||
decorated_endpoint._original_endpoint = endpoint | ||
|
||
# Replace endpoint in args or kwargs | ||
if len(args) >= 2: | ||
args = list(args) | ||
args[1] = decorated_endpoint | ||
args = tuple(args) | ||
elif "endpoint" in kwargs: | ||
kwargs["endpoint"] = decorated_endpoint | ||
|
||
except Exception as exc: # pylint: disable=broad-exception-caught | ||
_logger.warning("Failed to apply code attributes decorator to endpoint: %s", exc) | ||
|
||
return original_add_api_route_method(self, *args, **kwargs) | ||
|
||
return wrapper | ||
|
||
def patched_instrument(self, **kwargs): | ||
"""Patched _instrument method with APIRouter.add_api_route wrapping""" | ||
# Store original add_api_route method if not already stored | ||
if not hasattr(self, "_original_apirouter"): | ||
self._original_apirouter = routing.APIRouter.add_api_route | ||
|
||
# Wrap APIRouter.add_api_route with code attributes decoration | ||
routing.APIRouter.add_api_route = _wrapped_add_api_route(self._original_apirouter) | ||
|
||
# Call the original _instrument method | ||
original_instrument(self, **kwargs) | ||
|
||
def patched_uninstrument(self, **kwargs): | ||
"""Patched _uninstrument method with APIRouter.add_api_route restoration""" | ||
# Call the original _uninstrument method first | ||
original_uninstrument(self, **kwargs) | ||
|
||
# Restore original APIRouter.add_api_route method if it exists | ||
if hasattr(self, "_original_apirouter"): | ||
try: | ||
routing.APIRouter.add_api_route = self._original_apirouter | ||
delattr(self, "_original_apirouter") | ||
except Exception as exc: # pylint: disable=broad-exception-caught | ||
_logger.warning("Failed to restore original APIRouter.add_api_route method: %s", exc) | ||
|
||
# Apply the patches to FastAPIInstrumentor | ||
FastAPIInstrumentor._instrument = patched_instrument | ||
FastAPIInstrumentor._uninstrument = patched_uninstrument | ||
|
||
_logger.debug("FastAPI instrumentation code attributes patch applied successfully") | ||
|
||
except Exception as exc: # pylint: disable=broad-exception-caught | ||
_logger.warning("Failed to apply FastAPI code attributes patch: %s", exc) |
145 changes: 145 additions & 0 deletions
145
aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_flask_patches.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License. | ||
|
||
from logging import getLogger | ||
|
||
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import get_code_correlation_enabled_status | ||
|
||
_logger = getLogger(__name__) | ||
|
||
|
||
def _apply_flask_instrumentation_patches() -> None: | ||
"""Flask instrumentation patches | ||
|
||
Applies patches to provide code attributes support for Flask instrumentation. | ||
This patches the Flask instrumentation to automatically add code attributes | ||
to spans by decorating view functions with record_code_attributes. | ||
""" | ||
if get_code_correlation_enabled_status() is True: | ||
_apply_flask_code_attributes_patch() | ||
|
||
|
||
def _apply_flask_code_attributes_patch() -> None: # pylint: disable=too-many-statements | ||
"""Flask instrumentation patch for code attributes | ||
|
||
This patch modifies the Flask instrumentation to automatically apply | ||
the current_span_code_attributes decorator to all view functions when | ||
the Flask app is instrumented. | ||
|
||
The patch: | ||
1. Imports current_span_code_attributes decorator from AWS distro utils | ||
2. Hooks Flask's add_url_rule method during _instrument by patching Flask class | ||
3. Hooks Flask's dispatch_request method to handle deferred view function binding | ||
4. Automatically decorates view functions as they are registered or at request time | ||
5. Adds code.function.name, code.file.path, and code.line.number to spans | ||
6. Provides cleanup during _uninstrument | ||
""" | ||
try: | ||
# Import Flask instrumentation classes and AWS decorator | ||
import flask # pylint: disable=import-outside-toplevel | ||
|
||
from amazon.opentelemetry.distro.code_correlation import ( # pylint: disable=import-outside-toplevel | ||
record_code_attributes, | ||
) | ||
from opentelemetry.instrumentation.flask import FlaskInstrumentor # pylint: disable=import-outside-toplevel | ||
|
||
# Store the original _instrument and _uninstrument methods | ||
original_instrument = FlaskInstrumentor._instrument | ||
original_uninstrument = FlaskInstrumentor._uninstrument | ||
|
||
# Store reference to original Flask methods | ||
original_flask_add_url_rule = flask.Flask.add_url_rule | ||
original_flask_dispatch_request = flask.Flask.dispatch_request | ||
|
||
def _decorate_view_func(view_func, endpoint=None): | ||
"""Helper function to decorate a view function with code attributes.""" | ||
try: | ||
if view_func and callable(view_func): | ||
# Check if function is already decorated (avoid double decoration) | ||
if not hasattr(view_func, "_current_span_code_attributes_decorated"): | ||
# Apply decorator | ||
decorated_view_func = record_code_attributes(view_func) | ||
# Mark as decorated to avoid double decoration | ||
decorated_view_func._current_span_code_attributes_decorated = True | ||
decorated_view_func._original_view_func = view_func | ||
return decorated_view_func | ||
return view_func | ||
except Exception as exc: # pylint: disable=broad-exception-caught | ||
_logger.warning("Failed to apply code attributes decorator to view function %s: %s", endpoint, exc) | ||
return view_func | ||
|
||
def _wrapped_add_url_rule(self, rule, endpoint=None, view_func=None, **options): | ||
"""Wrapped Flask.add_url_rule method with code attributes decoration.""" | ||
# Apply decorator to view function if available | ||
if view_func: | ||
view_func = _decorate_view_func(view_func, endpoint) | ||
|
||
return original_flask_add_url_rule(self, rule, endpoint, view_func, **options) | ||
|
||
def _wrapped_dispatch_request(self): | ||
"""Wrapped Flask.dispatch_request method to handle deferred view function binding.""" | ||
try: | ||
# Get the current request context | ||
from flask import request # pylint: disable=import-outside-toplevel | ||
|
||
# Check if there's an endpoint for this request | ||
endpoint = request.endpoint | ||
if endpoint and endpoint in self.view_functions: | ||
view_func = self.view_functions[endpoint] | ||
|
||
# Check if the view function needs decoration | ||
if view_func and callable(view_func): | ||
if not hasattr(view_func, "_current_span_code_attributes_decorated"): | ||
# Decorate the view function and replace it in view_functions | ||
decorated_view_func = _decorate_view_func(view_func, endpoint) | ||
if decorated_view_func != view_func: | ||
self.view_functions[endpoint] = decorated_view_func | ||
_logger.debug( | ||
"Applied code attributes decorator to deferred view function for endpoint: %s", | ||
endpoint, | ||
) | ||
|
||
except Exception as exc: # pylint: disable=broad-exception-caught | ||
_logger.warning("Failed to process deferred view function decoration: %s", exc) | ||
|
||
# Call the original dispatch_request method | ||
return original_flask_dispatch_request(self) | ||
|
||
def patched_instrument(self, **kwargs): | ||
"""Patched _instrument method with Flask method wrapping""" | ||
# Store original methods if not already stored | ||
if not hasattr(self, "_original_flask_add_url_rule"): | ||
self._original_flask_add_url_rule = flask.Flask.add_url_rule | ||
self._original_flask_dispatch_request = flask.Flask.dispatch_request | ||
|
||
# Wrap Flask methods with code attributes decoration | ||
flask.Flask.add_url_rule = _wrapped_add_url_rule | ||
flask.Flask.dispatch_request = _wrapped_dispatch_request | ||
|
||
# Call the original _instrument method | ||
original_instrument(self, **kwargs) | ||
|
||
def patched_uninstrument(self, **kwargs): | ||
"""Patched _uninstrument method with Flask method restoration""" | ||
# Call the original _uninstrument method first | ||
original_uninstrument(self, **kwargs) | ||
|
||
# Restore original Flask methods if they exist | ||
if hasattr(self, "_original_flask_add_url_rule"): | ||
try: | ||
flask.Flask.add_url_rule = self._original_flask_add_url_rule | ||
flask.Flask.dispatch_request = self._original_flask_dispatch_request | ||
delattr(self, "_original_flask_add_url_rule") | ||
delattr(self, "_original_flask_dispatch_request") | ||
except Exception as exc: # pylint: disable=broad-exception-caught | ||
_logger.warning("Failed to restore original Flask methods: %s", exc) | ||
|
||
# Apply the patches to FlaskInstrumentor | ||
FlaskInstrumentor._instrument = patched_instrument | ||
FlaskInstrumentor._uninstrument = patched_uninstrument | ||
|
||
_logger.debug("Flask instrumentation code attributes patch applied successfully") | ||
|
||
except Exception as exc: # pylint: disable=broad-exception-caught | ||
_logger.warning("Failed to apply Flask code attributes patch: %s", exc) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.