diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e7db937..1cf8e8d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,12 @@ Change Log Unreleased ~~~~~~~~~~ +[5.0.0] - 2024-10-22 +~~~~~~~~~~~~~~~~~~~~ +Removed +------- +* Deleted Datadog diagnostics plugin app and middleware, which are no longer in use in edxapp. + [4.5.0] - 2024-09-19 ~~~~~~~~~~~~~~~~~~~~ Added diff --git a/edx_arch_experiments/__init__.py b/edx_arch_experiments/__init__.py index 72386ec..068150b 100644 --- a/edx_arch_experiments/__init__.py +++ b/edx_arch_experiments/__init__.py @@ -2,4 +2,4 @@ A plugin to include applications under development by the architecture team at 2U. """ -__version__ = '4.5.0' +__version__ = '5.0.0' diff --git a/edx_arch_experiments/datadog_diagnostics/README.rst b/edx_arch_experiments/datadog_diagnostics/README.rst deleted file mode 100644 index 364ad6d..0000000 --- a/edx_arch_experiments/datadog_diagnostics/README.rst +++ /dev/null @@ -1,6 +0,0 @@ -Datadog Diagnostics -################### - -When installed in the LMS as a plugin app, the ``datadog_diagnostics`` app adds additional logging for debugging our Datadog integration. - -This is intended as a temporary situation while we debug the `trace concatenation issue `_. diff --git a/edx_arch_experiments/datadog_diagnostics/__init__.py b/edx_arch_experiments/datadog_diagnostics/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/edx_arch_experiments/datadog_diagnostics/apps.py b/edx_arch_experiments/datadog_diagnostics/apps.py deleted file mode 100644 index d57c0c6..0000000 --- a/edx_arch_experiments/datadog_diagnostics/apps.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -App for emitting additional diagnostic information for the Datadog integration. -""" - -import logging -import re - -from django.apps import AppConfig -from django.conf import settings - -log = logging.getLogger(__name__) - - -# pylint: disable=missing-function-docstring -class MissingSpanProcessor: - """Datadog span processor that logs unfinished spans at shutdown.""" - - def __init__(self): - self.spans_started = 0 - self.spans_finished = 0 - self.open_spans = {} - - # .. setting_name: DATADOG_DIAGNOSTICS_MAX_SPANS - # .. setting_default: 100 - # .. setting_description: Limit of how many spans to hold onto and log - # when diagnosing Datadog tracing issues. This limits memory consumption - # avoids logging more data than is actually needed for diagnosis. - self.DATADOG_DIAGNOSTICS_MAX_SPANS = getattr(settings, 'DATADOG_DIAGNOSTICS_MAX_SPANS', 100) - - def on_span_start(self, span): - self.spans_started += 1 - if len(self.open_spans) < self.DATADOG_DIAGNOSTICS_MAX_SPANS: - self.open_spans[span.span_id] = span - - def on_span_finish(self, span): - self.spans_finished += 1 - self.open_spans.pop(span.span_id, None) # "delete if present" - - def shutdown(self, _timeout): - log.info(f"Spans created = {self.spans_started}; spans finished = {self.spans_finished}") - for span in self.open_spans.values(): - log.error(f"Span created but not finished: {span._pprint()}") # pylint: disable=protected-access - - -# Dictionary of Celery signal names to a task information extractor. -# The latter is a lambda accepting the signal receiver's kwargs dict -# and returning a minimal dict of info for tracking task lifecycle. -# Celery signal params vary quite a bit in how they convey the -# information we need, so this is probably better than trying to use -# one set of heuristics to get the task ID and name from all signals. -# -# Docs: https://docs.celeryq.dev/en/stable/userguide/signals.html -CELERY_SIGNAL_CONFIG = { - 'before_task_publish': lambda kwargs: {'name': kwargs['sender']}, - 'after_task_publish': lambda kwargs: {'name': kwargs['sender']}, - 'task_prerun': lambda kwargs: {'name': kwargs['task'].name, 'id': kwargs['task_id']}, - 'task_postrun': lambda kwargs: {'name': kwargs['task'].name, 'id': kwargs['task_id']}, - 'task_retry': lambda kwargs: {'name': kwargs['sender'].name, 'id': kwargs['request'].id}, - 'task_success': lambda kwargs: {'name': kwargs['sender'].name}, - 'task_failure': lambda kwargs: {'name': kwargs['sender'].name, 'id': kwargs['task_id']}, - 'task_internal_error': lambda kwargs: {'name': kwargs['sender'].name, 'id': kwargs['task_id']}, - 'task_received': lambda kwargs: {'name': kwargs['request'].name, 'id': kwargs['request'].id}, - 'task_revoked': lambda kwargs: {'name': kwargs['sender'].name, 'id': kwargs['request'].id}, - 'task_unknown': lambda kwargs: {'name': kwargs['name'], 'id': kwargs['id']}, - 'task_rejected': lambda _kwargs: {}, -} - - -def _connect_celery_handler(signal, signal_name, extractor): - """ - Register one Celery signal receiver. - - This serves as a closure to capture the config (and some state) for one signal. - If the extractor ever throws, log the error just once and don't try calling it - again for the remaining life of the process (as it will likely continue failing - the same way.) - - Args: - signal: Django Signal instance to register a handler for - signal_name: Name of signal in Celery signals module (used for logging) - extractor: Function to take signal receiver's entire kwargs and return - a dict optionally containing 'id' and 'name' keys. - """ - errored = False - - def log_celery_signal(**kwargs): - nonlocal errored - info = None - try: - if not errored: - info = extractor(kwargs) - except BaseException: - errored = True - log.exception( - f"Error while extracting Celery signal info for '{signal_name}'; " - "will not attempt for future calls to this signal." - ) - - if info is None: - extra = "(skipped data extraction)" - else: - extra = f"with name={info.get('name')} id={info.get('id')}" - log.info(f"Celery signal called: '{signal_name}' {extra}") - - signal.connect(log_celery_signal, weak=False) - - -def connect_celery_logging(): - """ - Set up logging of configured Celery signals. - - Throws if celery is not installed. - """ - import celery.signals # pylint: disable=import-outside-toplevel - - # .. setting_name: DATADOG_DIAGNOSTICS_CELERY_LOG_SIGNALS - # .. setting_default: '' - # .. setting_description: Log calls to these Celery signals (signal name as well - # as task name and id, if available). Specify as a comma and/or whitespace delimited - # list of names from the celery.signals module. Full list of available signals: - # "after_task_publish, before_task_publish, task_failure, task_internal_error, - # task_postrun, task_prerun, task_received, task_rejected, task_retry, - # task_revoked, task_success, task_unknown" - DATADOG_DIAGNOSTICS_CELERY_LOG_SIGNALS = getattr( - settings, - 'DATADOG_DIAGNOSTICS_CELERY_LOG_SIGNALS', - '' - ) - - connected_names = [] - for signal_name in re.split(r'[,\s]+', DATADOG_DIAGNOSTICS_CELERY_LOG_SIGNALS): - if not signal_name: # artifacts from splitting - continue - - signal = getattr(celery.signals, signal_name, None) - if not signal: - log.warning(f"Could not connect receiver to unknown Celery signal '{signal_name}'") - continue - - extractor = CELERY_SIGNAL_CONFIG.get(signal_name) - if not extractor: - log.warning(f"Don't know how to extract info for Celery signal '{signal_name}'; ignoring.") - continue - - _connect_celery_handler(signal, signal_name, extractor) - connected_names.append(signal_name) - - log.info(f"Logging lifecycle info for these celery signals: {connected_names!r}") - - -class DatadogDiagnostics(AppConfig): - """ - Django application to log diagnostic information for Datadog. - """ - name = 'edx_arch_experiments.datadog_diagnostics' - - # Mark this as a plugin app - plugin_app = {} - - def ready(self): - # .. toggle_name: DATADOG_DIAGNOSTICS_ENABLE - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Enables logging of Datadog diagnostics information. - # .. toggle_use_cases: circuit_breaker - # .. toggle_creation_date: 2024-07-11 - # .. toggle_tickets: https://github.com/edx/edx-arch-experiments/issues/692 - DATADOG_DIAGNOSTICS_ENABLE = getattr(settings, 'DATADOG_DIAGNOSTICS_ENABLE', True) - - if not DATADOG_DIAGNOSTICS_ENABLE: - return - - try: - from ddtrace import tracer # pylint: disable=import-outside-toplevel - tracer._span_processors.append(MissingSpanProcessor()) # pylint: disable=protected-access - log.info("Attached MissingSpanProcessor for Datadog diagnostics") - except ImportError: - log.warning( - "Unable to attach MissingSpanProcessor for Datadog diagnostics" - " -- ddtrace module not found." - ) - - # We think that something related to Celery instrumentation is involved - # in causing trace concatenation in Datadog. DD Support has requested that - # we log lifecycle information of Celery tasks to see if all of the needed - # signals are being emitted for their span construction. - try: - connect_celery_logging() - except BaseException: - log.exception("Unable to subscribe to Celery task signals") diff --git a/edx_arch_experiments/datadog_diagnostics/middleware.py b/edx_arch_experiments/datadog_diagnostics/middleware.py deleted file mode 100644 index b249868..0000000 --- a/edx_arch_experiments/datadog_diagnostics/middleware.py +++ /dev/null @@ -1,201 +0,0 @@ -""" -Diagnostic middleware for Datadog. - -To use, install edx-arch-experiments and add -``edx_arch_experiments.datadog_diagnostics.middleware.DatadogDiagnosticMiddleware`` -to ``MIDDLEWARE``, then set the below settings as needed. -""" - -import logging -import time - -from django.conf import settings -from django.core.exceptions import MiddlewareNotUsed -from edx_toggles.toggles import WaffleFlag - -log = logging.getLogger(__name__) - -# .. toggle_name: datadog.diagnostics.detect_anomalous_trace -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Enables logging of anomalous Datadog traces for web requests. -# .. toggle_warning: This is a noisy feature and so it should only be enabled -# for a percentage of requests. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2024-08-01 -# .. toggle_target_removal_date: 2024-11-01 -# .. toggle_tickets: https://github.com/edx/edx-arch-experiments/issues/692 -DETECT_ANOMALOUS_TRACE = WaffleFlag('datadog.diagnostics.detect_anomalous_trace', module_name=__name__) - -# .. toggle_name: datadog.diagnostics.close_anomalous_spans -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Close anomalous spans that are ancestors of the django.request span. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2024-09-19 -# .. toggle_target_removal_date: 2024-12-01 -# .. toggle_tickets: https://github.com/edx/edx-arch-experiments/issues/692 -CLOSE_ANOMALOUS_SPANS = WaffleFlag('datadog.diagnostics.close_anomalous_spans', module_name=__name__) - -# .. toggle_name: datadog.diagnostics.log_root_span -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Enables logging of Datadog root span IDs for web requests. -# .. toggle_warning: This is a noisy feature and so it should only be enabled -# for a percentage of requests. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2024-07-24 -# .. toggle_target_removal_date: 2024-10-01 -# .. toggle_tickets: https://github.com/edx/edx-arch-experiments/issues/692 -LOG_ROOT_SPAN = WaffleFlag('datadog.diagnostics.log_root_span', module_name=__name__) - - -# pylint: disable=missing-function-docstring -class DatadogDiagnosticMiddleware: - """ - Middleware to add diagnostic logging for Datadog. - - Best added early in the middleware stack. - - Only activates if ``ddtrace`` package is installed and - ``datadog.diagnostics.log_root_span`` Waffle flag is enabled. - """ - def __init__(self, get_response): - self.get_response = get_response - self.error = False - - try: - from ddtrace import tracer # pylint: disable=import-outside-toplevel - self.dd_tracer = tracer - except ImportError: - # If import fails, don't even load this middleware. - raise MiddlewareNotUsed # pylint: disable=raise-missing-from - - self.worker_start_epoch = time.time() - # .. setting_name: DATADOG_DIAGNOSTICS_LOG_SPAN_DEPTH - # .. setting_default: 10 - # .. setting_description: Controls how many ancestors spans are logged - # when anomalous traces are detected. This limits log size when very - # deep span trees are present (such as in anomalous traces, or even - # just when each middleware is given a span). - self.log_span_ancestors_depth = getattr(settings, "DATADOG_DIAGNOSTICS_LOG_SPAN_DEPTH", 10) - - def __call__(self, request): - return self.get_response(request) - - def process_view(self, request, _view_func, _view_args, _view_kwargs): - try: - self.log_diagnostics(request) - if CLOSE_ANOMALOUS_SPANS.is_enabled(): - self.close_anomalous_spans(request) - except BaseException as e: - # If there's an error, it will probably hit every request, - # so let's just log it once. - if not self.error: - self.error = True - log.error( - "Encountered error in DatadogDiagnosticMiddleware " - f"(suppressing further errors): {e!r}" - ) - - # pylint: disable=protected-access - def close_anomalous_spans(self, request): - """ - Detect anomalous spans and close them. - - This closes any open spans that are ancestors of the current - request. The trace will still have two requests concatenated - together, but the problematic spans should not affect - future requests. - - Only activates if the root span is itself closed, which is a - cheap thing to check. - """ - # If the root span is still open, probably not an anomalous trace. - if self.dd_tracer.current_root_span().duration is None: - return # nothing to do! - - # Walk upwards until we find the django.request span. - walk_span = self.dd_tracer.current_span() - while walk_span.name != 'django.request': - walk_span = walk_span._parent - if walk_span is None: - # If we can't find the django.request root, there's - # something bad about our assumptions and we should - # not attempt a fix. - log.error( - "Did not find django.request span when walking anomalous trace " - "to root. Not attempting a fix." - ) - return - - # Go "above" the request span - walk_span = walk_span._parent - - # Now close everything above the current request span that's - # still open, logging as we go. - while walk_span is not None: - # We call finish() individually rather than - # finish_with_ancestors() because this gives us a chance - # to log each one. - if walk_span.duration is None: - walk_span.finish() - log.info( - f"Closed span in anomalous trace: name={walk_span.name} " - f"id={walk_span.span_id:x} trace={walk_span.trace_id:x}" - ) - # Keep walking up even if we discover closed spans; we've - # previously seen multiple contiguous segments of open - # spans separated by closed ones. - walk_span = walk_span._parent - - def log_diagnostics(self, request): - """ - Contains all the actual logging logic. - """ - current_span = self.dd_tracer.current_span() - local_root_span = self.dd_tracer.current_root_span() - - if DETECT_ANOMALOUS_TRACE.is_enabled(): - # For testing, uncomment this line to fake an anomalous span: - # local_root_span.finish() - root_duration_s = local_root_span.duration - if root_duration_s is not None: - self.log_anomalous_trace(current_span, local_root_span) - - if LOG_ROOT_SPAN.is_enabled(): - route_pattern = getattr(request.resolver_match, 'route', None) - # pylint: disable=protected-access - log.info( - f"Datadog span diagnostics: Route = {route_pattern}; " - f"local root span = {local_root_span._pprint()}; " - f"current span = {current_span._pprint()}" - ) - - def log_anomalous_trace(self, current_span, local_root_span): - worker_run_time_s = time.time() - self.worker_start_epoch - - # Build up a list of spans from current back towards the root, up to a limit. - ancestors = [] - walk_span = current_span - while len(ancestors) < self.log_span_ancestors_depth and walk_span is not None: - ancestors.insert(0, walk_span._pprint()) # pylint: disable=protected-access - walk_span = walk_span._parent # pylint: disable=protected-access - - trunc = "(ancestors truncated)\n" if walk_span else "" - - if local_root_span.duration: - duration_msg = f"duration={local_root_span.duration:0.3f}" - else: - # Should only occur during local testing of this - # middleware, when forcing this code path to run. - duration_msg = "duration not set" - - msg = ( - "Anomalous Datadog local root span: " - f"trace_id={local_root_span.trace_id:x}; " - f"{duration_msg}; " - f"worker_age={worker_run_time_s:0.3f}; span ancestry:" - ) - - log.warning(msg + "\n" + trunc + "\n".join(ancestors)) # pylint: disable=logging-not-lazy diff --git a/edx_arch_experiments/datadog_diagnostics/tests/__init__.py b/edx_arch_experiments/datadog_diagnostics/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/edx_arch_experiments/datadog_diagnostics/tests/test_app.py b/edx_arch_experiments/datadog_diagnostics/tests/test_app.py deleted file mode 100644 index 77682cf..0000000 --- a/edx_arch_experiments/datadog_diagnostics/tests/test_app.py +++ /dev/null @@ -1,208 +0,0 @@ -""" -Tests for plugin app. -""" - -from unittest.mock import call, patch - -import celery.signals -from ddtrace import tracer -from django.dispatch import Signal -from django.test import TestCase, override_settings - -from .. import apps - - -class FakeSpan: - """A fake Span instance that just carries a span_id.""" - def __init__(self, span_id): - self.span_id = span_id - - def _pprint(self): - return f"span_id={self.span_id}" - - -class TestMissingSpanProcessor(TestCase): - """Tests for MissingSpanProcessor.""" - - def setUp(self): - # Remove custom span processor from previous runs. - # pylint: disable=protected-access - tracer._span_processors = [sp for sp in tracer._span_processors if type(sp).__name__ != 'MissingSpanProcessor'] - - def test_feature_switch(self): - """ - Regression test -- the use of override_settings ensures that we read - the setting as needed, and not once at module load time (when it's - not guaranteed to be available.) - """ - def initialize(): - apps.DatadogDiagnostics('edx_arch_experiments.datadog_diagnostics', apps).ready() - - def get_processor_list(): - # pylint: disable=protected-access - return [type(sp).__name__ for sp in tracer._span_processors] - - with override_settings(DATADOG_DIAGNOSTICS_ENABLE=False): - initialize() - assert sorted(get_processor_list()) == [ - 'EndpointCallCounterProcessor', 'TopLevelSpanProcessor', - ] - - # The True case needs to come second because the initializer - # appends to the list and there isn't an immediately obvious - # way of resetting it. - with override_settings(DATADOG_DIAGNOSTICS_ENABLE=True): - initialize() - assert sorted(get_processor_list()) == [ - 'EndpointCallCounterProcessor', 'MissingSpanProcessor', 'TopLevelSpanProcessor', - ] - - @override_settings(DATADOG_DIAGNOSTICS_MAX_SPANS=3) - def test_metrics(self): - proc = apps.MissingSpanProcessor() - ids = [2, 4, 6, 8, 10] - - for span_id in ids: - proc.on_span_start(FakeSpan(span_id)) - - assert {(sk, sv.span_id) for sk, sv in proc.open_spans.items()} == {(2, 2), (4, 4), (6, 6)} - assert proc.spans_started == 5 - assert proc.spans_finished == 0 - - for span_id in ids: - proc.on_span_finish(FakeSpan(span_id)) - - assert proc.open_spans.keys() == set() - assert proc.spans_started == 5 - assert proc.spans_finished == 5 - - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.info') - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.error') - def test_logging(self, mock_log_error, mock_log_info): - proc = apps.MissingSpanProcessor() - proc.on_span_start(FakeSpan(17)) - proc.shutdown(0) - - mock_log_info.assert_called_once_with("Spans created = 1; spans finished = 0") - mock_log_error.assert_called_once_with("Span created but not finished: span_id=17") - - -class TestCeleryLogging(TestCase): - """ - Tests for celery signal logging. - - While it would be nice to test actual Celery tasks and signals, - it's difficult to get that all working with unit tests. We'd have - to use Celery's pytest extra, which provides fixtures, but those - don't play well with unittest's TestCase classes and even after - converting to top-level functions things just seemed to hang after - setup. - - So instead, we'll mock things out at the signal level and just - ensure that each level of functionality works in isolation. - """ - - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.info') - def test_default_config_has_no_signals(self, mock_log_info): - apps.DatadogDiagnostics('edx_arch_experiments.datadog_diagnostics', apps).ready() - mock_log_info.assert_called_with("Logging lifecycle info for these celery signals: []") - - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.info') - def test_registration_maximal(self, mock_log_info): - """Test that all celery signal names are actually signals.""" - all_signal_names = ', '.join(sorted(apps.CELERY_SIGNAL_CONFIG.keys())) - with override_settings(DATADOG_DIAGNOSTICS_CELERY_LOG_SIGNALS=all_signal_names): - apps.DatadogDiagnostics('edx_arch_experiments.datadog_diagnostics', apps).ready() - - mock_log_info.assert_called_with( - "Logging lifecycle info for these celery signals: ['after_task_publish', " - "'before_task_publish', 'task_failure', 'task_internal_error', " - "'task_postrun', 'task_prerun', 'task_received', 'task_rejected', " - "'task_retry', 'task_revoked', 'task_success', 'task_unknown']" - ) - - @override_settings( - DATADOG_DIAGNOSTICS_CELERY_LOG_SIGNALS=',,,task_success, task_unknown, task_rejected, fake_signal' - ) - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.info') - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.warning') - @patch('edx_arch_experiments.datadog_diagnostics.apps._connect_celery_handler') - def test_register(self, mock_connect, mock_log_warning, mock_log_info): - """Test that signal connector is *called* as expected.""" - - with patch.dict('edx_arch_experiments.datadog_diagnostics.apps.CELERY_SIGNAL_CONFIG'): - # Add a bad entry to the config to test the signal lookup path - apps.CELERY_SIGNAL_CONFIG['fake_signal'] = lambda kwargs: {} - # Remove a real signal from the config to test the extractor lookup path - del apps.CELERY_SIGNAL_CONFIG['task_unknown'] - apps.DatadogDiagnostics('edx_arch_experiments.datadog_diagnostics', apps).ready() - - assert mock_connect.call_args_list == [ - call( - celery.signals.task_success, - 'task_success', - apps.CELERY_SIGNAL_CONFIG['task_success'], - ), - call( - celery.signals.task_rejected, - 'task_rejected', - apps.CELERY_SIGNAL_CONFIG['task_rejected'], - ), - ] - assert mock_log_warning.call_args_list == [ - call("Don't know how to extract info for Celery signal 'task_unknown'; ignoring."), - call("Could not connect receiver to unknown Celery signal 'fake_signal'"), - ] - mock_log_info.assert_called_with( - "Logging lifecycle info for these celery signals: ['task_success', 'task_rejected']" - ) - - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.info') - @patch('edx_arch_experiments.datadog_diagnostics.apps.log.exception') - def test_handler(self, mock_log_exception, mock_log_info): - """Test that signal connector *behaves* as expected.""" - # Signal A will just do a straightforward data extraction from the kwargs. - # pylint: disable=protected-access - apps._connect_celery_handler( - signal_example_a, 'signal_example_a', - lambda kwargs: {'name': kwargs['info']['name']}, - ) - - # Signal B will have an extractor that goes bad on the 2nd and 3rd calls - b_called_times = 0 - - def b_extractor(kwargs): - nonlocal b_called_times - b_called_times += 1 - if b_called_times >= 2: - raise Exception("oops") - - return {'id': kwargs['the_id']} - - # pylint: disable=protected-access - apps._connect_celery_handler(signal_example_b, 'signal_example_b', b_extractor) - - # Send to B a few times to show that error logging only happens once - signal_example_b.send(sender='some_sender', the_id=42) - signal_example_b.send(sender='some_sender', the_id=42) - signal_example_b.send(sender='some_sender', the_id=42) - # And then send to A to show it still works - signal_example_a.send( - sender='some_sender', other='whatever', info={'name': "Alice"}, name='not this one', - ) - - mock_log_exception.assert_called_once_with( - "Error while extracting Celery signal info for 'signal_example_b'; " - "will not attempt for future calls to this signal." - ) - assert b_called_times == 2 - assert mock_log_info.call_args_list == [ - call("Celery signal called: 'signal_example_b' with name=None id=42"), - call("Celery signal called: 'signal_example_b' (skipped data extraction)"), - call("Celery signal called: 'signal_example_b' (skipped data extraction)"), - call("Celery signal called: 'signal_example_a' with name=Alice id=None"), - ] - - -signal_example_a = Signal() -signal_example_b = Signal() diff --git a/edx_arch_experiments/datadog_diagnostics/tests/test_middleware.py b/edx_arch_experiments/datadog_diagnostics/tests/test_middleware.py deleted file mode 100644 index 4ae05c8..0000000 --- a/edx_arch_experiments/datadog_diagnostics/tests/test_middleware.py +++ /dev/null @@ -1,243 +0,0 @@ -""" -Tests for diagnostic middleware. -""" - -import re -from contextlib import ExitStack -from unittest.mock import Mock, patch - -import ddt -import ddtrace -from django.test import TestCase, override_settings - -from ..middleware import CLOSE_ANOMALOUS_SPANS, DETECT_ANOMALOUS_TRACE, LOG_ROOT_SPAN, DatadogDiagnosticMiddleware - - -def fake_view(_request): - """Fake get_response for middleware.""" - - -@ddt.ddt -class TestDatadogDiagnosticMiddleware(TestCase): - """Tests for DatadogDiagnosticMiddleware.""" - - def make_middleware(self): - """Make an instance of the middleware with current settings.""" - return DatadogDiagnosticMiddleware(fake_view) - - def run_middleware(self, middleware=None, check_error_state=True): - """Run the middleware using a fake request.""" - if middleware is None: - middleware = self.make_middleware() - - resolver = Mock() - resolver.route = "/some/path" - request = Mock() - request.resolver_match = resolver - - middleware.process_view(request, None, None, None) - - if check_error_state: - assert middleware.error is False - - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.error') - def test_log_diagnostics_error_only_once(self, mock_log_error): - """ - If the log_diagnostics function is broken, only log the error once. - The method should still be called every time in case it is still doing - useful work before the error, though. - """ - middleware = self.make_middleware() - - bad_method = Mock(side_effect=lambda request: 1/0) - middleware.log_diagnostics = bad_method - - self.run_middleware(middleware, check_error_state=False) - self.run_middleware(middleware, check_error_state=False) - assert middleware.error is True - - # Called twice - assert len(bad_method.call_args_list) == 2 - - # But only log once - mock_log_error.assert_called_once_with( - "Encountered error in DatadogDiagnosticMiddleware (suppressing further errors): " - "ZeroDivisionError('division by zero')" - ) - - @ddt.data( - # Feature disabled - (False, False), - (False, True), - # Enabled, but nothing anomalous - (True, False), - # Enabled and anomaly detected - (True, True), - ) - @ddt.unpack - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.warning') - def test_anomalous_trace(self, enabled, cause_anomaly, mock_log_warning): - with ( - patch.object(DETECT_ANOMALOUS_TRACE, 'is_enabled', return_value=enabled), - patch.object(CLOSE_ANOMALOUS_SPANS, 'is_enabled', return_value=False), - patch.object(LOG_ROOT_SPAN, 'is_enabled', return_value=False), - # Need at least two levels of spans in order to fake - # an anomaly. (Otherwise current_root_span returns None.) - ddtrace.tracer.trace("local_root"), - ddtrace.tracer.trace("intermediary_span"), - ddtrace.tracer.trace("inner_span"), - ): - if cause_anomaly: - ddtrace.tracer.current_root_span().finish() - self.run_middleware() - - if enabled and cause_anomaly: - mock_log_warning.assert_called_once() - log_msg = mock_log_warning.call_args_list[0][0][0] # first arg of first call - - assert re.fullmatch( - r"Anomalous Datadog local root span: " - r"trace_id=[0-9A-Fa-f]+; duration=[0-9]\.[0-9]{3}; worker_age=[0-9]\.[0-9]{3}; span ancestry:\n" - r"name='local_root'.*duration=[0-9]+.*\n" - r"name='intermediary_span'.*duration=None.*\n" - r"name='inner_span'.*duration=None.*", - log_msg - ) - else: - mock_log_warning.assert_not_called() - - @override_settings(DATADOG_DIAGNOSTICS_LOG_SPAN_DEPTH=2) - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.warning') - def test_anomalous_trace_truncation(self, mock_log_warning): - """ - Test that truncation works, returning N most proximal spans. - """ - with ( - patch.object(DETECT_ANOMALOUS_TRACE, 'is_enabled', return_value=True), - patch.object(CLOSE_ANOMALOUS_SPANS, 'is_enabled', return_value=False), - patch.object(LOG_ROOT_SPAN, 'is_enabled', return_value=False), - # Need at least two levels of spans in order to fake - # an anomaly. (Otherwise current_root_span returns None.) - ddtrace.tracer.trace("local_root"), - ddtrace.tracer.trace("intermediary_span"), - ddtrace.tracer.trace("inner_span"), - ): - ddtrace.tracer.current_root_span().finish() # cause anomaly - self.run_middleware() - - mock_log_warning.assert_called_once() - log_msg = mock_log_warning.call_args_list[0][0][0] # first arg of first call - - assert re.fullmatch( - r"Anomalous Datadog local root span: " - r"trace_id=[0-9A-Fa-f]+; duration=[0-9]\.[0-9]{3}; worker_age=[0-9]\.[0-9]{3}; span ancestry:\n" - r"\(ancestors truncated\)\n" # difference here - r"name='intermediary_span'.*duration=None.*\n" - r"name='inner_span'.*duration=None.*", - log_msg - ) - - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.info') - def test_log_root_span(self, mock_log_info): - with ( - patch.object(DETECT_ANOMALOUS_TRACE, 'is_enabled', return_value=False), - patch.object(CLOSE_ANOMALOUS_SPANS, 'is_enabled', return_value=False), - patch.object(LOG_ROOT_SPAN, 'is_enabled', return_value=True), - # Need at least two levels of spans for interesting logging - ddtrace.tracer.trace("local_root"), - ddtrace.tracer.trace("inner_span"), - ): - self.run_middleware() - - mock_log_info.assert_called_once() - log_msg = mock_log_info.call_args_list[0][0][0] # first arg of first call - assert re.fullmatch( - r"Datadog span diagnostics: Route = /some/path; " - r"local root span = name='local_root' .*; " - r"current span = name='inner_span' .*", - log_msg - ) - - def run_close_with(self, *, enabled, anomalous, ancestors=None): - """ - Run a "close anomalous spans" scenario with supplied settings. - - ancestors is a list of span operation names, defaulting to - something reasonable if not supplied. - """ - with ( - patch.object(DETECT_ANOMALOUS_TRACE, 'is_enabled', return_value=False), - patch.object(CLOSE_ANOMALOUS_SPANS, 'is_enabled', return_value=enabled), - patch.object(LOG_ROOT_SPAN, 'is_enabled', return_value=False), - ExitStack() as stack, - ): - if ancestors is None: - ancestors = [ - 'django.request', 'django.view', - 'celery.apply', - # ^ will need to close some of these - 'django.request', 'django.view', - ] - for ancestor_name in ancestors: - stack.enter_context(ddtrace.tracer.trace(ancestor_name)) - # make anomaly readily detectable - if anomalous: - ddtrace.tracer.current_root_span().finish() - - self.run_middleware() - - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.info') - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.error') - def test_close_disabled(self, mock_log_error, mock_log_info): - """ - Confirm that nothing interesting happens when close-spans flag is disabled. - """ - self.run_close_with(enabled=False, anomalous=True) - - mock_log_error.assert_not_called() - mock_log_info.assert_not_called() - - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.info') - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.error') - def test_close_applied(self, mock_log_error, mock_log_info): - """ - Confirm that anomalous spans are closed, at least for future requests. - """ - self.run_close_with(enabled=True, anomalous=True) - - mock_log_error.assert_not_called() - - # Expect to close celery.apply and the one above it (but we've - # already closed the root, above). - assert len(mock_log_info.call_args_list) == 2 - assert [call[0][0].split(' id=')[0] for call in mock_log_info.call_args_list] == [ - "Closed span in anomalous trace: name=celery.apply", - "Closed span in anomalous trace: name=django.view", - ] - - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.info') - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.error') - def test_close_not_needed(self, mock_log_error, mock_log_info): - """ - Confirm that no logging when anomalous trace not present. - """ - self.run_close_with(enabled=True, anomalous=False) - - mock_log_error.assert_not_called() - mock_log_info.assert_not_called() - - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.info') - @patch('edx_arch_experiments.datadog_diagnostics.middleware.log.error') - def test_close_missing_request(self, mock_log_error, mock_log_info): - """ - Check that we look for the expected ancestor and only close above it. - """ - self.run_close_with(enabled=True, anomalous=True, ancestors=[ - # Artificial scenario standing in for something unexpected. - 'django.view', 'celery.apply', 'django.view', - ]) - - mock_log_error.assert_called_once_with( - "Did not find django.request span when walking anomalous trace to root. Not attempting a fix." - ) - mock_log_info.assert_not_called() diff --git a/requirements/dev.txt b/requirements/dev.txt index 933ae96..65ad589 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -4,10 +4,6 @@ # # make upgrade # -amqp==5.2.0 - # via - # -r requirements/quality.txt - # kombu asgiref==3.8.1 # via # -r requirements/quality.txt @@ -26,24 +22,14 @@ backports-tarfile==1.2.0 # via # -r requirements/quality.txt # jaraco-context -billiard==4.2.0 - # via - # -r requirements/quality.txt - # celery build==1.2.2 # via # -r requirements/pip-tools.txt # pip-tools -bytecode==0.15.1 - # via - # -r requirements/quality.txt - # ddtrace cachetools==5.5.0 # via # -r requirements/ci.txt # tox -celery==5.4.0 - # via -r requirements/quality.txt certifi==2024.8.30 # via # -r requirements/quality.txt @@ -66,31 +52,15 @@ click==8.1.7 # via # -r requirements/pip-tools.txt # -r requirements/quality.txt - # celery - # click-didyoumean # click-log - # click-plugins - # click-repl # code-annotations # edx-django-utils # edx-lint # pip-tools -click-didyoumean==0.3.1 - # via - # -r requirements/quality.txt - # celery click-log==0.4.0 # via # -r requirements/quality.txt # edx-lint -click-plugins==1.1.1 - # via - # -r requirements/quality.txt - # celery -click-repl==0.3.0 - # via - # -r requirements/quality.txt - # celery code-annotations==1.8.0 # via # -r requirements/quality.txt @@ -111,12 +81,6 @@ cryptography==43.0.1 # secretstorage ddt==1.7.2 # via -r requirements/quality.txt -ddtrace==2.12.2 - # via -r requirements/quality.txt -deprecated==1.2.14 - # via - # -r requirements/quality.txt - # opentelemetry-api diff-cover==9.2.0 # via -r requirements/dev.in dill==0.3.8 @@ -186,10 +150,6 @@ edx-opaque-keys==2.11.0 # edx-drf-extensions edx-toggles==5.2.0 # via -r requirements/quality.txt -envier==0.5.2 - # via - # -r requirements/quality.txt - # ddtrace filelock==3.16.1 # via # -r requirements/ci.txt @@ -203,7 +163,6 @@ importlib-metadata==8.4.0 # via # -r requirements/quality.txt # keyring - # opentelemetry-api # twine iniconfig==2.0.0 # via @@ -245,10 +204,6 @@ keyring==25.4.0 # via # -r requirements/quality.txt # twine -kombu==5.4.2 - # via - # -r requirements/quality.txt - # celery lxml[html-clean,html_clean]==5.3.0 # via # edx-i18n-tools @@ -284,10 +239,6 @@ nh3==0.2.18 # via # -r requirements/quality.txt # readme-renderer -opentelemetry-api==1.27.0 - # via - # -r requirements/quality.txt - # ddtrace packaging==24.1 # via # -r requirements/ci.txt @@ -325,14 +276,6 @@ pluggy==1.5.0 # tox polib==1.2.0 # via edx-i18n-tools -prompt-toolkit==3.0.47 - # via - # -r requirements/quality.txt - # click-repl -protobuf==5.28.2 - # via - # -r requirements/quality.txt - # ddtrace psutil==6.0.0 # via # -r requirements/quality.txt @@ -405,10 +348,6 @@ pytest-django==4.9.0 # via -r requirements/quality.txt pytest-randomly==3.15.0 # via -r requirements/quality.txt -python-dateutil==2.9.0.post0 - # via - # -r requirements/quality.txt - # celery python-slugify==8.0.4 # via # -r requirements/quality.txt @@ -463,7 +402,6 @@ six==1.16.0 # -r requirements/quality.txt # edx-codejail # edx-lint - # python-dateutil snowballstemmer==2.2.0 # via # -r requirements/quality.txt @@ -493,45 +431,20 @@ twine==5.1.1 typing-extensions==4.12.2 # via # -r requirements/quality.txt - # ddtrace # edx-opaque-keys -tzdata==2024.1 - # via - # -r requirements/quality.txt - # celery - # kombu urllib3==2.2.3 # via # -r requirements/quality.txt # requests # twine -vine==5.1.0 - # via - # -r requirements/quality.txt - # amqp - # celery - # kombu virtualenv==20.26.5 # via # -r requirements/ci.txt # tox -wcwidth==0.2.13 - # via - # -r requirements/quality.txt - # prompt-toolkit wheel==0.44.0 # via # -r requirements/pip-tools.txt # pip-tools -wrapt==1.16.0 - # via - # -r requirements/quality.txt - # ddtrace - # deprecated -xmltodict==0.13.0 - # via - # -r requirements/quality.txt - # ddtrace zipp==3.20.2 # via # -r requirements/quality.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index 953b522..d0bb9e7 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -6,10 +6,6 @@ # alabaster==0.7.16 # via sphinx -amqp==5.2.0 - # via - # -r requirements/test.txt - # kombu asgiref==3.8.1 # via # -r requirements/test.txt @@ -21,16 +17,6 @@ attrs==24.2.0 # referencing babel==2.16.0 # via sphinx -billiard==4.2.0 - # via - # -r requirements/test.txt - # celery -bytecode==0.15.1 - # via - # -r requirements/test.txt - # ddtrace -celery==5.4.0 - # via -r requirements/test.txt certifi==2024.8.30 # via # -r requirements/test.txt @@ -47,24 +33,8 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/test.txt - # celery - # click-didyoumean - # click-plugins - # click-repl # code-annotations # edx-django-utils -click-didyoumean==0.3.1 - # via - # -r requirements/test.txt - # celery -click-plugins==1.1.1 - # via - # -r requirements/test.txt - # celery -click-repl==0.3.0 - # via - # -r requirements/test.txt - # celery code-annotations==1.8.0 # via # -r requirements/test.txt @@ -79,12 +49,6 @@ cryptography==43.0.1 # pyjwt ddt==1.7.2 # via -r requirements/test.txt -ddtrace==2.12.2 - # via -r requirements/test.txt -deprecated==1.2.14 - # via - # -r requirements/test.txt - # opentelemetry-api django==4.2.16 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt @@ -145,20 +109,12 @@ edx-sphinx-theme==3.1.0 # via -r requirements/doc.in edx-toggles==5.2.0 # via -r requirements/test.txt -envier==0.5.2 - # via - # -r requirements/test.txt - # ddtrace idna==3.10 # via # -r requirements/test.txt # requests imagesize==1.4.1 # via sphinx -importlib-metadata==8.4.0 - # via - # -r requirements/test.txt - # opentelemetry-api iniconfig==2.0.0 # via # -r requirements/test.txt @@ -174,10 +130,6 @@ jsonschema-specifications==2023.12.1 # via # -r requirements/test.txt # jsonschema -kombu==5.4.2 - # via - # -r requirements/test.txt - # celery markupsafe==2.1.5 # via # -r requirements/test.txt @@ -188,10 +140,6 @@ newrelic==9.13.0 # edx-django-utils nh3==0.2.18 # via readme-renderer -opentelemetry-api==1.27.0 - # via - # -r requirements/test.txt - # ddtrace packaging==24.1 # via # -r requirements/test.txt @@ -205,14 +153,6 @@ pluggy==1.5.0 # via # -r requirements/test.txt # pytest -prompt-toolkit==3.0.47 - # via - # -r requirements/test.txt - # click-repl -protobuf==5.28.2 - # via - # -r requirements/test.txt - # ddtrace psutil==6.0.0 # via # -r requirements/test.txt @@ -251,10 +191,6 @@ pytest-django==4.9.0 # via -r requirements/test.txt pytest-randomly==3.15.0 # via -r requirements/test.txt -python-dateutil==2.9.0.post0 - # via - # -r requirements/test.txt - # celery python-slugify==8.0.4 # via # -r requirements/test.txt @@ -291,7 +227,6 @@ six==1.16.0 # -r requirements/test.txt # edx-codejail # edx-sphinx-theme - # python-dateutil snowballstemmer==2.2.0 # via sphinx sphinx==3.5.3 @@ -328,40 +263,11 @@ text-unidecode==1.3 typing-extensions==4.12.2 # via # -r requirements/test.txt - # ddtrace # edx-opaque-keys -tzdata==2024.1 - # via - # -r requirements/test.txt - # celery - # kombu urllib3==2.2.3 # via # -r requirements/test.txt # requests -vine==5.1.0 - # via - # -r requirements/test.txt - # amqp - # celery - # kombu -wcwidth==0.2.13 - # via - # -r requirements/test.txt - # prompt-toolkit -wrapt==1.16.0 - # via - # -r requirements/test.txt - # ddtrace - # deprecated -xmltodict==0.13.0 - # via - # -r requirements/test.txt - # ddtrace -zipp==3.20.2 - # via - # -r requirements/test.txt - # importlib-metadata # The following packages are considered to be unsafe in a requirements file: setuptools==75.1.0 diff --git a/requirements/quality.txt b/requirements/quality.txt index cc284ea..bc99982 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -4,10 +4,6 @@ # # make upgrade # -amqp==5.2.0 - # via - # -r requirements/test.txt - # kombu asgiref==3.8.1 # via # -r requirements/test.txt @@ -23,16 +19,6 @@ attrs==24.2.0 # referencing backports-tarfile==1.2.0 # via jaraco-context -billiard==4.2.0 - # via - # -r requirements/test.txt - # celery -bytecode==0.15.1 - # via - # -r requirements/test.txt - # ddtrace -celery==5.4.0 - # via -r requirements/test.txt certifi==2024.8.30 # via # -r requirements/test.txt @@ -49,28 +35,12 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/test.txt - # celery - # click-didyoumean # click-log - # click-plugins - # click-repl # code-annotations # edx-django-utils # edx-lint -click-didyoumean==0.3.1 - # via - # -r requirements/test.txt - # celery click-log==0.4.0 # via edx-lint -click-plugins==1.1.1 - # via - # -r requirements/test.txt - # celery -click-repl==0.3.0 - # via - # -r requirements/test.txt - # celery code-annotations==1.8.0 # via # -r requirements/test.txt @@ -87,12 +57,6 @@ cryptography==43.0.1 # secretstorage ddt==1.7.2 # via -r requirements/test.txt -ddtrace==2.12.2 - # via -r requirements/test.txt -deprecated==1.2.14 - # via - # -r requirements/test.txt - # opentelemetry-api dill==0.3.8 # via pylint django==4.2.16 @@ -149,19 +113,13 @@ edx-opaque-keys==2.11.0 # edx-drf-extensions edx-toggles==5.2.0 # via -r requirements/test.txt -envier==0.5.2 - # via - # -r requirements/test.txt - # ddtrace idna==3.10 # via # -r requirements/test.txt # requests importlib-metadata==8.4.0 # via - # -r requirements/test.txt # keyring - # opentelemetry-api # twine iniconfig==2.0.0 # via @@ -193,10 +151,6 @@ jsonschema-specifications==2023.12.1 # jsonschema keyring==25.4.0 # via twine -kombu==5.4.2 - # via - # -r requirements/test.txt - # celery markdown-it-py==3.0.0 # via rich markupsafe==2.1.5 @@ -217,10 +171,6 @@ newrelic==9.13.0 # edx-django-utils nh3==0.2.18 # via readme-renderer -opentelemetry-api==1.27.0 - # via - # -r requirements/test.txt - # ddtrace packaging==24.1 # via # -r requirements/test.txt @@ -237,14 +187,6 @@ pluggy==1.5.0 # via # -r requirements/test.txt # pytest -prompt-toolkit==3.0.47 - # via - # -r requirements/test.txt - # click-repl -protobuf==5.28.2 - # via - # -r requirements/test.txt - # ddtrace psutil==6.0.0 # via # -r requirements/test.txt @@ -300,10 +242,6 @@ pytest-django==4.9.0 # via -r requirements/test.txt pytest-randomly==3.15.0 # via -r requirements/test.txt -python-dateutil==2.9.0.post0 - # via - # -r requirements/test.txt - # celery python-slugify==8.0.4 # via # -r requirements/test.txt @@ -347,7 +285,6 @@ six==1.16.0 # -r requirements/test.txt # edx-codejail # edx-lint - # python-dateutil snowballstemmer==2.2.0 # via pydocstyle sqlparse==0.5.1 @@ -371,41 +308,14 @@ twine==5.1.1 typing-extensions==4.12.2 # via # -r requirements/test.txt - # ddtrace # edx-opaque-keys -tzdata==2024.1 - # via - # -r requirements/test.txt - # celery - # kombu urllib3==2.2.3 # via # -r requirements/test.txt # requests # twine -vine==5.1.0 - # via - # -r requirements/test.txt - # amqp - # celery - # kombu -wcwidth==0.2.13 - # via - # -r requirements/test.txt - # prompt-toolkit -wrapt==1.16.0 - # via - # -r requirements/test.txt - # ddtrace - # deprecated -xmltodict==0.13.0 - # via - # -r requirements/test.txt - # ddtrace zipp==3.20.2 - # via - # -r requirements/test.txt - # importlib-metadata + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: setuptools==75.1.0 diff --git a/requirements/test.in b/requirements/test.in index 1ddb552..37a4119 100644 --- a/requirements/test.in +++ b/requirements/test.in @@ -8,5 +8,3 @@ pytest-django # pytest extension for better Django support pytest-randomly # pytest extension for discovering order-sensitive tests code-annotations # provides commands used by the pii_check make target. ddt # data-driven tests -ddtrace # Required for testing datadog_diagnostics app and middleware -celery # Required for testing datadog_diagnostics app diff --git a/requirements/test.txt b/requirements/test.txt index b053e46..94c920d 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,8 +4,6 @@ # # make upgrade # -amqp==5.2.0 - # via kombu asgiref==3.8.1 # via # -r requirements/base.txt @@ -15,12 +13,6 @@ attrs==24.2.0 # -r requirements/base.txt # jsonschema # referencing -billiard==4.2.0 - # via celery -bytecode==0.15.1 - # via ddtrace -celery==5.4.0 - # via -r requirements/test.in certifi==2024.8.30 # via # -r requirements/base.txt @@ -37,18 +29,8 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/base.txt - # celery - # click-didyoumean - # click-plugins - # click-repl # code-annotations # edx-django-utils -click-didyoumean==0.3.1 - # via celery -click-plugins==1.1.1 - # via celery -click-repl==0.3.0 - # via celery code-annotations==1.8.0 # via # -r requirements/base.txt @@ -62,10 +44,6 @@ cryptography==43.0.1 # pyjwt ddt==1.7.2 # via -r requirements/test.in -ddtrace==2.12.2 - # via -r requirements/test.in -deprecated==1.2.14 - # via opentelemetry-api # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.txt @@ -115,14 +93,10 @@ edx-opaque-keys==2.11.0 # edx-drf-extensions edx-toggles==5.2.0 # via -r requirements/base.txt -envier==0.5.2 - # via ddtrace idna==3.10 # via # -r requirements/base.txt # requests -importlib-metadata==8.4.0 - # via opentelemetry-api iniconfig==2.0.0 # via pytest jinja2==3.1.4 @@ -135,8 +109,6 @@ jsonschema-specifications==2023.12.1 # via # -r requirements/base.txt # jsonschema -kombu==5.4.2 - # via celery markupsafe==2.1.5 # via # -r requirements/base.txt @@ -145,8 +117,6 @@ newrelic==9.13.0 # via # -r requirements/base.txt # edx-django-utils -opentelemetry-api==1.27.0 - # via ddtrace packaging==24.1 # via pytest pbr==6.1.0 @@ -155,10 +125,6 @@ pbr==6.1.0 # stevedore pluggy==1.5.0 # via pytest -prompt-toolkit==3.0.47 - # via click-repl -protobuf==5.28.2 - # via ddtrace psutil==6.0.0 # via # -r requirements/base.txt @@ -191,8 +157,6 @@ pytest-django==4.9.0 # via -r requirements/test.in pytest-randomly==3.15.0 # via -r requirements/test.in -python-dateutil==2.9.0.post0 - # via celery python-slugify==8.0.4 # via # -r requirements/base.txt @@ -223,7 +187,6 @@ six==1.16.0 # via # -r requirements/base.txt # edx-codejail - # python-dateutil sqlparse==0.5.1 # via # -r requirements/base.txt @@ -241,31 +204,11 @@ text-unidecode==1.3 typing-extensions==4.12.2 # via # -r requirements/base.txt - # ddtrace # edx-opaque-keys -tzdata==2024.1 - # via - # celery - # kombu urllib3==2.2.3 # via # -r requirements/base.txt # requests -vine==5.1.0 - # via - # amqp - # celery - # kombu -wcwidth==0.2.13 - # via prompt-toolkit -wrapt==1.16.0 - # via - # ddtrace - # deprecated -xmltodict==0.13.0 - # via ddtrace -zipp==3.20.2 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: setuptools==75.1.0 diff --git a/setup.py b/setup.py index bc11b14..3b29fa9 100644 --- a/setup.py +++ b/setup.py @@ -164,7 +164,6 @@ def is_requirement(line): "arch_experiments = edx_arch_experiments.apps:EdxArchExperimentsConfig", "config_watcher = edx_arch_experiments.config_watcher.apps:ConfigWatcher", "codejail_service = edx_arch_experiments.codejail_service.apps:CodejailService", - "datadog_diagnostics = edx_arch_experiments.datadog_diagnostics.apps:DatadogDiagnostics", ], "cms.djangoapp": [ "config_watcher = edx_arch_experiments.config_watcher.apps:ConfigWatcher",