Skip to content

Commit

Permalink
Use single NullHandler for whole library
Browse files Browse the repository at this point in the history
I was trying to get a feel for what the rest of the python ecosystem
does with its logging, so I looked into the top few libraries on pypi:

urllib3 maintains a logger for not quite every module, but for each
one that does heavy lifting. The logger name is `__name__`, and no
handlers are registered for any module-level loggers, including
NullHandler. Their documentation spells out how to configure logging
for the library.

They explicitly configure a library root-level logger called `urllib3`
to which they attach a `NullHandler`. This addresses the "no handlers
could be found" problem.

Their tests explicitly configure handlers, just like ours do.

scrapy is more hands-on. It provides a configuration module for its
logging and a whole document on how to handle logging with scrapy. It
looks like log.py's whole reason for existence is making sure that a
handler is attached to to the scrapy handler at startup.

I think the extra complexity here is because scrapy also offers a CLI,
so there has to be some way to configure logging without resorting to
writing python, so I felt we didn't need to resort to this added
complexity.

---

Based on all of the libraries I've looked at, I think our current
approach is reasonable. The one change I would make is to explicitly
configure a `grpc` logger and to only attach a `NullHandler` to it
instead of putting the burden on the author of each new module to
configure it there.

With this change, I have

- Configured a logger in each module that cares about logging
- Removed all NullHandlers attached to module-level loggers
- Explicitly configured a `grpc` logger with a `NullHandler` attached

Resolves: #16572
Related: #17064
  • Loading branch information
gnossen committed Nov 9, 2018
1 parent 01c6565 commit ecd9063
Show file tree
Hide file tree
Showing 10 changed files with 10 additions and 9 deletions.
4 changes: 3 additions & 1 deletion src/python/grpcio/grpc/__init__.py
Expand Up @@ -15,12 +15,14 @@

import abc
import enum
import logging
import sys

import six

from grpc._cython import cygrpc as _cygrpc

logging.getLogger(__name__).addHandler(logging.NullHandler())

############################## Future Interface ###############################


Expand Down
1 change: 0 additions & 1 deletion src/python/grpcio/grpc/_channel.py
Expand Up @@ -25,7 +25,6 @@
from grpc.framework.foundation import callable_util

_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())

_USER_AGENT = 'grpc-python/{}'.format(_grpcio_metadata.__version__)

Expand Down
1 change: 0 additions & 1 deletion src/python/grpcio/grpc/_common.py
Expand Up @@ -21,7 +21,6 @@
from grpc._cython import cygrpc

_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())

CYGRPC_CONNECTIVITY_STATE_TO_CHANNEL_CONNECTIVITY = {
cygrpc.ConnectivityState.idle:
Expand Down
1 change: 0 additions & 1 deletion src/python/grpcio/grpc/_cython/_cygrpc/grpc_string.pyx.pxi
Expand Up @@ -15,7 +15,6 @@
import logging

_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())

# This function will ascii encode unicode string inputs if neccesary.
# In Python3, unicode strings are the default str type.
Expand Down
1 change: 0 additions & 1 deletion src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi
Expand Up @@ -19,7 +19,6 @@ import time
import grpc

_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())

cdef class Server:

Expand Down
1 change: 0 additions & 1 deletion src/python/grpcio/grpc/_plugin_wrapping.py
Expand Up @@ -21,7 +21,6 @@
from grpc._cython import cygrpc

_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())


class _AuthMetadataContext(
Expand Down
Expand Up @@ -22,7 +22,6 @@
import six

_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())


class Outcome(six.with_metaclass(abc.ABCMeta)):
Expand Down
Expand Up @@ -18,7 +18,6 @@
from concurrent import futures

_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())


def _wrap(behavior):
Expand Down
1 change: 0 additions & 1 deletion src/python/grpcio/grpc/framework/foundation/stream_util.py
Expand Up @@ -20,7 +20,6 @@

_NO_VALUE = object()
_LOGGER = logging.getLogger(__name__)
_LOGGER.addHandler(logging.NullHandler())


class TransformingConsumer(stream.Consumer):
Expand Down
7 changes: 7 additions & 0 deletions src/python/grpcio_tests/tests/unit/_logging_test.py
Expand Up @@ -68,6 +68,13 @@ def test_can_configure_logger(self):
self.assertEqual(1, len(logging.getLogger().handlers))
self.assertIs(logging.getLogger().handlers[0].stream, intended_stream)

@isolated_logging
def test_grpc_logger(self):
self.assertIn("grpc", logging.Logger.manager.loggerDict)
root_logger = logging.getLogger("grpc")
self.assertEqual(1, len(root_logger.handlers))
self.assertIsInstance(root_logger.handlers[0], logging.NullHandler)


if __name__ == '__main__':
unittest.main(verbosity=2)

0 comments on commit ecd9063

Please sign in to comment.