Skip to content

Commit 8a2c205

Browse files
authored
Merge pull request #70 from edx/robrap/ARCHBOM-1584-add-monitoring-internal
ARCHBOM-1584: refactor monitoring app
2 parents 13faebe + 241bb42 commit 8a2c205

28 files changed

+769
-706
lines changed

CHANGELOG.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,42 @@ Change Log
1515
Unreleased
1616
~~~~~~~~~~
1717

18+
[3.11.0] - 2020-10-31
19+
~~~~~~~~~~~~~~~~~~~~~
20+
21+
Added
22+
_____
23+
24+
* Added ADR 0004-public-api-and-app-organization.rst to explain a new app organization, which makes use of the public API more consistent.
25+
26+
Updated
27+
_______
28+
29+
* Applied the new app organization described in th ADR to the monitoring Django app.
30+
* Moved CachedCustomMonitoringMiddleware, CodeOwnerMonitoringMiddleware, and MonitoringMemoryMiddleware to the public API.
31+
32+
Deprecated
33+
__________
34+
35+
* Deprecated the old locations of CachedCustomMonitoringMiddleware, CodeOwnerMonitoringMiddleware, and MonitoringMemoryMiddleware.
36+
* Deprecated various methods from modules that were always meant to be used from the public API.
37+
38+
* accumulate
39+
* increment
40+
* set_custom_attribute
41+
* set_custom_attributes_for_course_key
42+
43+
* Added additional custom attributes for deprecated classes and methods to make them safer to retire.
44+
45+
.. note::
46+
47+
Some method implementations that were available in the public API were moved without adding a deprecated equivalent. These were not found when searching, so hopefully they are only used via the public API, which did not change. This includes functions in ``transactions.py`` and ``code_owner/utils.py``.
48+
49+
Removed
50+
_______
51+
52+
* Removed the middleware ordering checks. This is not a typical Django feature and it is painful when refactoring.
53+
1854
[3.10.0] - 2020-10-28
1955
~~~~~~~~~~~~~~~~~~~~~
2056

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Public API and App Organization
2+
===============================
3+
4+
Status
5+
------
6+
7+
Accepted
8+
9+
Context
10+
-------
11+
12+
The original apps in the library (e.g. ``cache`` and ``monitoring``) exposed a public API via ``__init__.py``.
13+
14+
There are several problems with the original organization of the app code:
15+
16+
* It was easy to forget to add new code to the public API, or ignore this requirement. For example, the Middleware didn't follow the same process.
17+
* It was easy for a user of the library to mistakenly use code from a different module in the app, rather than through the public API.
18+
19+
Decision
20+
--------
21+
22+
All implementation code should be moved to an ``internal`` module.
23+
24+
using monitoring as an example, the public API would be exposed as follows in ``edx_django_utils/monitoring/__init__.py``::
25+
26+
from .internal.somemodule import ...
27+
28+
The benefits of this setup include:
29+
30+
* A clear designation of what is part of the public API.
31+
* The ability to refactor the implementation without changing the API.
32+
* A clear reminder to developers adding new code that it needs to be exposed if it is public.
33+
* A clear reminder to developers using the library not to use code from the internal implementation.
34+
35+
Consequences
36+
------------
37+
38+
Whenever a new class or function is added to the edx_django_utils public API, it should be implemented in the Django app's ``internal`` module and explicitly imported in its ``__init__.py`` module.
39+
40+
Additionally, some existing apps will need to be refactored.

edx_django_utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
EdX utilities for Django Application development..
33
"""
44

5-
__version__ = "3.10.0"
5+
__version__ = "3.11.0"
66

77
default_app_config = (
88
"edx_django_utils.apps.EdxDjangoUtilsConfig"

edx_django_utils/cache/middleware.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
"""
44
from django.utils.deprecation import MiddlewareMixin
55

6-
from edx_django_utils.private_utils import _check_middleware_dependencies
7-
86
from . import RequestCache, TieredCache
97

108

@@ -36,16 +34,6 @@ class TieredCacheMiddleware(MiddlewareMixin):
3634
"""
3735
Middleware to store whether or not to force django cache misses.
3836
"""
39-
def __init__(self, get_response=None):
40-
super(TieredCacheMiddleware, self).__init__(get_response=get_response)
41-
# checks proper dependency order as well.
42-
_check_middleware_dependencies(self, required_middleware=[
43-
'edx_django_utils.cache.middleware.RequestCacheMiddleware',
44-
# Some Authentication Middleware also needs to be in between,
45-
# but don't want to hard-code that dependency.
46-
'edx_django_utils.cache.middleware.TieredCacheMiddleware',
47-
])
48-
4937
def process_request(self, request):
5038
"""
5139
Stores whether or not FORCE_CACHE_MISS_PARAM was supplied in the

edx_django_utils/cache/tests/test_middleware.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"""
33
Tests for the RequestCacheMiddleware.
44
"""
5-
from django.test import RequestFactory, TestCase, override_settings
5+
from django.test import RequestFactory, TestCase
66
from mock import MagicMock
77

88
from edx_django_utils.cache import middleware
@@ -15,7 +15,6 @@
1515

1616
class TestRequestCacheMiddleware(TestCase): # pylint: disable=missing-class-docstring
1717

18-
@override_settings(MIDDLEWARE=['edx_django_utils.cache.middleware.RequestCacheMiddleware'])
1918
def setUp(self):
2019
super(TestRequestCacheMiddleware, self).setUp()
2120
self.middleware = middleware.RequestCacheMiddleware()
@@ -56,10 +55,6 @@ def _dirty_request_cache(self):
5655

5756
class TestTieredCacheMiddleware(TestCase): # pylint: disable=missing-class-docstring
5857

59-
@override_settings(MIDDLEWARE=[
60-
'edx_django_utils.cache.middleware.RequestCacheMiddleware',
61-
'edx_django_utils.cache.middleware.TieredCacheMiddleware'
62-
])
6358
def setUp(self):
6459
super(TestTieredCacheMiddleware, self).setUp()
6560
self.middleware = middleware.TieredCacheMiddleware()
@@ -90,11 +85,6 @@ def test_process_request_force_cache_miss_non_staff(self):
9085

9186
self.assertFalse(self.request_cache.get_cached_response(SHOULD_FORCE_CACHE_MISS_KEY).value)
9287

93-
@override_settings(MIDDLEWARE=['some.Middleware'])
94-
def test_tiered_cache_missing_middleware(self):
95-
with self.assertRaises(AssertionError):
96-
middleware.TieredCacheMiddleware()
97-
9888
def _mock_user(self, is_staff=True):
9989
mock_user = MagicMock()
10090
mock_user.is_active = True

edx_django_utils/monitoring/README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ Here is how you add the middleware:
2222
# Generate code ownership attributes. Keep this immediately after RequestCacheMiddleware.
2323
...
2424
# Monitoring middleware must come after RequestCacheMiddleware
25-
'edx_django_utils.monitoring.code_owner.middleware.CodeOwnerMonitoringMiddleware',
26-
'edx_django_utils.monitoring.middleware.CachedCustomMonitoringMiddleware',
27-
'edx_django_utils.monitoring.middleware.MonitoringMemoryMiddleware',
25+
'edx_django_utils.monitoring.CodeOwnerMonitoringMiddleware',
26+
'edx_django_utils.monitoring.CachedCustomMonitoringMiddleware',
27+
'edx_django_utils.monitoring.MonitoringMemoryMiddleware',
2828
)
2929
3030
Monitoring Memory Usage

edx_django_utils/monitoring/__init__.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
44
See README.rst for details.
55
"""
6-
from .code_owner.utils import get_code_owner_from_module
7-
from .transactions import function_trace, get_current_transaction, ignore_transaction, set_monitoring_transaction_name
8-
# "set_custom_metric*" methods are deprecated
9-
from .utils import (
10-
accumulate,
11-
increment,
12-
set_custom_attribute,
13-
set_custom_attributes_for_course_key,
14-
set_custom_metric,
15-
set_custom_metrics_for_course_key
6+
from .internal.code_owner.middleware import CodeOwnerMonitoringMiddleware
7+
from .internal.code_owner.utils import get_code_owner_from_module
8+
from .internal.middleware import CachedCustomMonitoringMiddleware, MonitoringMemoryMiddleware
9+
from .internal.transactions import (
10+
function_trace,
11+
get_current_transaction,
12+
ignore_transaction,
13+
set_monitoring_transaction_name
1614
)
15+
from .internal.utils import accumulate, increment, set_custom_attribute, set_custom_attributes_for_course_key
16+
# "set_custom_metric*" methods are deprecated
17+
from .utils import set_custom_metric, set_custom_metrics_for_course_key
Lines changed: 21 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,36 @@
11
"""
2-
Middleware for code_owner custom attribute
3-
"""
4-
import logging
5-
import warnings
2+
Deprecated Middleware for backward-compatibility.
63
7-
from django.urls import resolve
4+
IMPORTANT: No new classes should be added to this file.
5+
TODO: Remove this file once these classes are no longer used.
86
9-
from edx_django_utils.monitoring import get_current_transaction, set_custom_attribute
10-
11-
from .utils import get_code_owner_from_module, is_code_owner_mappings_configured
7+
"""
8+
import warnings
129

13-
log = logging.getLogger(__name__)
10+
from edx_django_utils.monitoring.internal.code_owner.middleware import \
11+
CodeOwnerMonitoringMiddleware as InternalCodeOwnerMonitoringMiddleware
12+
from edx_django_utils.monitoring.internal.utils import set_custom_attribute
1413

1514

16-
class CodeOwnerMonitoringMiddleware:
15+
class CodeOwnerMonitoringMiddleware(InternalCodeOwnerMonitoringMiddleware):
1716
"""
18-
Django middleware object to set custom attributes for the owner of each view.
19-
20-
For instructions on usage, see:
21-
https://github.com/edx/edx-django-utils/blob/master/edx_django_utils/monitoring/docs/how_tos/add_code_owner_custom_attribute_to_an_ida.rst
22-
23-
Custom attributes set:
24-
- code_owner: The owning team mapped to the current view.
25-
- code_owner_mapping_error: If there are any errors when trying to perform the mapping.
26-
- code_owner_path_error: The error mapping by path, if code_owner isn't found in other ways.
27-
- code_owner_path_module: The __module__ of the view_func which was used to try to map to code_owner.
28-
This can be used to find missing mappings.
29-
- code_owner_transaction_error: The error mapping by transaction, if code_owner isn't found in other ways.
30-
- code_owner_transaction_name: The current transaction name used to try to map to code_owner.
31-
This can be used to find missing mappings.
32-
17+
Deprecated class for handling middleware. Class has been moved to public API.
3318
"""
34-
def __init__(self, get_response):
35-
self.get_response = get_response
36-
37-
def __call__(self, request):
38-
response = self.get_response(request)
39-
self._set_code_owner_attribute(request)
40-
return response
41-
42-
def process_exception(self, request, exception):
43-
self._set_code_owner_attribute(request)
44-
45-
def _set_code_owner_attribute(self, request):
46-
"""
47-
Sets the code_owner custom attribute, as well as several supporting custom attributes.
48-
49-
See CodeOwnerMonitoringMiddleware docstring for a complete list of attributes.
50-
51-
"""
52-
code_owner, path_error = self._set_code_owner_attribute_from_path(request)
53-
if code_owner:
54-
set_custom_attribute('code_owner', code_owner)
55-
return
56-
if not path_error:
57-
# module found, but mapping wasn't configured
58-
code_owner = self._set_code_owner_attribute_catch_all()
59-
if code_owner:
60-
set_custom_attribute('code_owner', code_owner)
61-
return
62-
63-
code_owner, transaction_error = self._set_code_owner_attribute_from_current_transaction(request)
64-
if code_owner:
65-
set_custom_attribute('code_owner', code_owner)
66-
return
67-
if not transaction_error:
68-
# transaction name found, but mapping wasn't configured
69-
code_owner = self._set_code_owner_attribute_catch_all()
70-
if code_owner:
71-
set_custom_attribute('code_owner', code_owner)
72-
return
73-
74-
code_owner = self._set_code_owner_attribute_catch_all()
75-
if code_owner:
76-
set_custom_attribute('code_owner', code_owner)
77-
return
78-
79-
# only report errors if code_owner couldn't be found, including catch-all
80-
if path_error:
81-
set_custom_attribute('code_owner_path_error', path_error)
82-
if transaction_error:
83-
set_custom_attribute('code_owner_transaction_error', transaction_error)
84-
85-
def _set_code_owner_attribute_from_path(self, request):
86-
"""
87-
Uses the request path to find the view_func and then sets code owner attributes based on the view.
88-
89-
Side-effects:
90-
Sets code_owner_path_module custom attribute, used to determine code_owner
91-
92-
Returns:
93-
(str, str): (code_owner, error_message), where at least one of these should be None
94-
95-
"""
96-
if not is_code_owner_mappings_configured():
97-
return None, None
98-
99-
try:
100-
view_func, _, _ = resolve(request.path)
101-
path_module = view_func.__module__
102-
set_custom_attribute('code_owner_path_module', path_module)
103-
code_owner = get_code_owner_from_module(path_module)
104-
return code_owner, None
105-
except Exception as e: # pylint: disable=broad-except
106-
return None, str(e)
107-
108-
def _set_code_owner_attribute_from_current_transaction(self, request):
109-
"""
110-
Uses the current transaction name to set the code owner attribute.
111-
112-
Side-effects:
113-
Sets code_owner_transaction_name custom attribute, used to determine code_owner
114-
115-
Returns:
116-
(str, str): (code_owner, error_message), where at least one of these should be None
117-
118-
"""
119-
if not is_code_owner_mappings_configured():
120-
# ensure we don't set code ownership custom attributes if not configured to do so
121-
return None, None # pragma: no cover
122-
123-
try:
124-
# Example: openedx.core.djangoapps.contentserver.middleware:StaticContentServer
125-
transaction_name = get_current_transaction().name
126-
if not transaction_name:
127-
return None, 'No current transaction name found.'
128-
module_name = transaction_name.split(':')[0]
129-
set_custom_attribute('code_owner_transaction_name', transaction_name)
130-
set_custom_attribute('code_owner_path_module', module_name)
131-
code_owner = get_code_owner_from_module(module_name)
132-
return code_owner, None
133-
except Exception as e: # pylint: disable=broad-except
134-
return None, str(e)
135-
136-
def _set_code_owner_attribute_catch_all(self):
137-
"""
138-
If the catch-all module "*" is configured, return the code_owner.
139-
140-
Returns:
141-
(str): code_owner or None if no catch-all configured.
142-
143-
"""
144-
try:
145-
code_owner = get_code_owner_from_module('*')
146-
return code_owner
147-
except Exception: # pylint: disable=broad-except; #pragma: no cover
148-
return None
19+
def __init__(self, *args, **kwargs):
20+
super().__init__(*args, **kwargs)
21+
msg = "Use 'edx_django_utils.monitoring.CodeOwnerMonitoringMiddleware' in place of " \
22+
"'edx_django_utils.monitoring.code_owner.middleware.CodeOwnerMonitoringMiddleware'."
23+
warnings.warn(msg, DeprecationWarning)
24+
set_custom_attribute('deprecated_code_owner_middleware', 'CodeOwnerMonitoringMiddleware')
14925

15026

151-
class CodeOwnerMetricMiddleware(CodeOwnerMonitoringMiddleware):
27+
class CodeOwnerMetricMiddleware(InternalCodeOwnerMonitoringMiddleware):
15228
"""
15329
Deprecated class for handling middleware. Class has been renamed to CodeOwnerMonitoringMiddleware.
15430
"""
15531
def __init__(self, *args, **kwargs): # pragma: no cover
156-
super(CodeOwnerMetricMiddleware, self).__init__(*args, **kwargs)
157-
msg = "Use 'CodeOwnerMonitoringMiddleware' in place of 'CodeOwnerMetricMiddleware'."
32+
super().__init__(*args, **kwargs)
33+
msg = "Use 'edx_django_utils.monitoring.CodeOwnerMonitoringMiddleware' in place of " \
34+
"'edx_django_utils.monitoring.code_owner.middleware.CodeOwnerMetricMiddleware'."
15835
warnings.warn(msg, DeprecationWarning)
36+
set_custom_attribute('deprecated_code_owner_middleware', 'CodeOwnerMetricMiddleware')

edx_django_utils/monitoring/docs/how_tos/add_code_owner_custom_attribute_to_an_ida.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ If you want to know about custom attributes in general, see: using_custom_attrib
1919
Setting up the Middleware
2020
-------------------------
2121

22-
You simply need to add ``edx_django_utils.monitoring.code_owner.middleware.CodeOwnerMonitoringMiddleware`` as described in the README to make this functionality available. Then it is ready to be configured.
22+
You simply need to add ``edx_django_utils.monitoring.CodeOwnerMonitoringMiddleware`` as described in the README to make this functionality available. Then it is ready to be configured.
2323

2424
Configuring your app settings
2525
-----------------------------

0 commit comments

Comments
 (0)