#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 django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/047.f639cd9e1ded034c68c22f9a66f17514%40djangoproject.com.

Reply via email to