#33495: "Synchronous middleware ... adapted" is not logged for synchronous
middleware
----------------------------------------+-----------------------------
               Reporter:  Aaron Chong   |          Owner:  Aaron Chong
                   Type:  Bug           |         Status:  assigned
              Component:  Core (Other)  |        Version:  4.0
               Severity:  Normal        |       Keywords:  async
           Triage Stage:  Unreviewed    |      Has patch:  0
    Needs documentation:  0             |    Needs tests:  0
Patch needs improvement:  0             |  Easy pickings:  0
                  UI/UX:  0             |
----------------------------------------+-----------------------------
 This is a bug in log messages that contradicts the guide in a warning in
 the docs; the feature works.

 The guide at https://docs.djangoproject.com/en/4.0/topics/async/#async-
 views, **emphasis** mine:

   Warning
   [[BR]]
   You will only get the benefits of a fully-asynchronous request stack if
 you have no synchronous middleware loaded into your site. **If there is a
 piece of synchronous middleware, then Django must use a thread per request
 to safely emulate a synchronous environment for it.**
   [[BR]]
   Middleware can be built to support both sync and async contexts. Some of
 Django’s middleware is built like this, but not all. **To see what
 middleware Django has to adapt, you can turn on debug logging for the
 django.request logger and look for log messages about “Synchronous
 middleware … adapted”.**

 The test for "Synchronous middleware ... adapted" is instead testing an
 async middleware over a sync method:
 
https://github.com/django/django/blob/7ca7f4495ba746279b734695a8dd137bf7ee0bab/tests/middleware_exceptions/tests.py#L222-L234

 {{{#!python
 @override_settings(MIDDLEWARE=[
     'middleware_exceptions.middleware.async_payment_middleware',
 ])
 def test_async_middleware(self):
     with self.assertLogs('django.request', 'DEBUG') as cm:
         response = self.client.get('/middleware_exceptions/view/')
     self.assertEqual(response.status_code, 402)
     self.assertEqual(
         cm.records[0].getMessage(),
         "Synchronous middleware "
         "middleware_exceptions.middleware.async_payment_middleware "
         "adapted.",
     )
 }}}

 About the existing implementation:

 1. `BaseHandler.load_middleware` passes the middleware name as `name` to
 `BaseHandler.adapt_method_mode`:
 
https://github.com/django/django/blob/98ad327864aed8df245fd19ea9d2743279e11643/django/core/handlers/base.py#L53-L57

 {{{#!python
 # Adapt handler, if needed.
 adapted_handler = self.adapt_method_mode(
     middleware_is_async, handler, handler_is_async,
     debug=settings.DEBUG, name='middleware %s' % middleware_path,
 )
 }}}

 2. `BaseHandler.adapt_method_mode` adapts the `method` and treats `name`
 as the method name rather than the middleware name; when the middleware
 name is used, it implies a method has been adapted for the middleware, not
 that the middleware was adapted:

 {{{#!python
 if debug and not name:
     name = name or 'method %s()' % method.__qualname__
 if is_async:
     if not method_is_async:
         if debug:
             logger.debug('Synchronous %s adapted.', name)
         return sync_to_async(method, thread_sensitive=True)
 elif method_is_async:
     if debug:
         logger.debug('Asynchronous %s adapted.', name)
     return async_to_sync(method)
 }}}

 Proposed fix:

 Handle middleware `name` and method name separately within
 `BaseHandler.adapt_method_mode`:

 {{{#!diff
   def adapt_method_mode(
       self, is_async, method, method_is_async=None, debug=False,
 name=None,
   ):
       """
       Adapt a method to be in the correct "mode":
       - If is_async is False:
         - Synchronous methods are left alone
         - Asynchronous methods are wrapped with async_to_sync
       - If is_async is True:
         - Synchronous methods are wrapped with sync_to_async()
         - Asynchronous methods are left alone
       """
 +     method_name = None
       if method_is_async is None:
           method_is_async = asyncio.iscoroutinefunction(method)
       if debug and not name:
 -         name = name or 'method %s()' % method.__qualname__
 +         method_name = 'method %s()' % method.__qualname__
       if is_async:
           if not method_is_async:
               if debug:
 -                 logger.debug('Synchronous %s adapted.', name)
 +                 if name:
 +                     logger.debug('Asynchronous %s adapted.', name)
 +                 else:
 +                     logger.debug('Synchronous %s adapted.', method_name)
               return sync_to_async(method, thread_sensitive=True)
       elif method_is_async:
           if debug:
 -             logger.debug('Asynchronous %s adapted.', name)
 +             if name:
 +                 logger.debug('Synchronous %s adapted.', name)
 +             else:
 +                 logger.debug('Asynchronous %s adapted.', method_name)
           return async_to_sync(method)
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33495>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/047.f639cd9e1ded034c68c22f9a66f17514%40djangoproject.com.

Reply via email to