-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Logging section #512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Logging section #512
Changes from all commits
7ff1d00
95ecb66
7c37ec8
1cfb637
0c1551a
df7a225
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| Logging | ||
| ======= | ||
|
|
||
| The :mod:`logging` module has been a part of Python's Standard Library since | ||
| version 2.3. It is succinctly described in :pep:`282`. The documentation | ||
| is notoriously hard to read, except for the `basic logging tutorial`_. | ||
|
|
||
| Logging serves two purposes: | ||
|
|
||
| - **Diagnostic logging** records events related to the application's | ||
| operation. If a user calls in to report an error, for example, the logs | ||
| can be searched for context. | ||
| - **Audit logging** records events for business analysis. A user's | ||
| transactions can be extracted and combined with other user details for | ||
| reports or to optimize a business goal. | ||
|
|
||
|
|
||
| ... or Print? | ||
| ------------- | ||
|
|
||
| The only time that ``print`` is a better option than logging is when | ||
| the goal is to display a help statement for a command line application. | ||
| Other reasons why logging is better than ``print``: | ||
|
|
||
| - The `log record`_, which is created with every logging event, contains | ||
| readily available diagnostic information such as the file name, | ||
| full path, function, and line number of the logging event. | ||
| - Events logged in included modules are automatically accessible via the | ||
| root logger | ||
| to your application's logging stream, unless you filter them out. | ||
| - Logging can be selectively silenced by using the method | ||
| :meth:`logging.Logger.setLevel` or disabled by setting the attribute | ||
| :attr:`logging.Logger.disabled` to ``True``. | ||
|
|
||
|
|
||
| Logging in a Library | ||
| -------------------- | ||
|
|
||
| Notes for `configuring logging for a library`_ are in the | ||
| `logging tutorial`_. Because the *user*, not the library, should | ||
| dictate what happens when a logging event occurs, one admonition bears | ||
| repeating: | ||
|
|
||
| .. note:: | ||
| It is strongly advised that you do not add any handlers other than | ||
| NullHandler to your library’s loggers. | ||
|
|
||
|
|
||
| Best practice when instantiating loggers in a library is to only create them | ||
| using the ``__name__`` global variable: the :mod:`logging` module creates a | ||
| hierarchy of loggers using dot notation, so using ``__name__`` ensures | ||
| no name collisions. | ||
|
|
||
| Here is an example of best practice from the `requests source`_ -- place | ||
| this in your ``__init__.py`` | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # Set default logging handler to avoid "No handler found" warnings. | ||
| import logging | ||
| try: # Python 2.7+ | ||
| from logging import NullHandler | ||
| except ImportError: | ||
| class NullHandler(logging.Handler): | ||
| def emit(self, record): | ||
| pass | ||
|
|
||
| logging.getLogger(__name__).addHandler(NullHandler()) | ||
|
|
||
|
|
||
|
|
||
| Logging in an Application | ||
| ------------------------- | ||
|
|
||
| The `twelve factor app <http://12factor.net>`_, an authoritative reference | ||
| for good practice in application development, contains a section on | ||
| `logging best practice <http://12factor.net/logs>`_. It emphatically | ||
| advocates for treating log events as an event stream, and for | ||
| sending that event stream to standard output to be handled by the | ||
| application environment. | ||
|
|
||
|
|
||
| There are at least three ways to configure a logger: | ||
|
|
||
| - Using an INI-formatted file: | ||
| - **Pro**: possible to update configuration while running | ||
| using the function :func:`logging.config.listen` to listen | ||
| on a socket. | ||
| - **Con**: less control (*e.g.* custom subclassed filters or loggers) | ||
| than possible when configuring a logger in code. | ||
| - Using a dictionary or a JSON-formatted file: | ||
| - **Pro**: in addition to updating while running, it is possible to | ||
| load from a file using the :mod:`json` module, in the standard | ||
| library since Python 2.6. | ||
| - **Con**: less control than when configuring a logger in code. | ||
| - Using code: | ||
| - **Pro**: complete control over the configuration. | ||
| - **Con**: modifications require a change to source code. | ||
|
|
||
|
|
||
| Example Configuration via an INI File | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Let us say the file is named ``logging_config.ini``. | ||
| More details for the file format are in the `logging configuration`_ | ||
| section of the `logging tutorial`_. | ||
|
|
||
| .. code-block:: ini | ||
|
|
||
| [loggers] | ||
| keys=root | ||
|
|
||
| [handlers] | ||
| keys=stream_handler | ||
|
|
||
| [formatters] | ||
| keys=formatter | ||
|
|
||
| [logger_root] | ||
| level=DEBUG | ||
| handlers=stream_handler | ||
|
|
||
| [handler_stream_handler] | ||
| class=StreamHandler | ||
| level=DEBUG | ||
| formatter=formatter | ||
| args=(sys.stderr,) | ||
|
|
||
| [formatter_formatter] | ||
| format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s | ||
|
|
||
|
|
||
| Then use :meth:`logging.config.fileConfig` in the code: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| import logging | ||
| from logging.config import fileConfig | ||
|
|
||
| fileConfig('logging_config.txt') | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logging_config.txt should be logging_config.ini sorry
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Woops! You have to make a new PR with that. Sorry for the hassle
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made the change myself @tanyaschlusser to save you some trouble |
||
| logger = logging.getLogger() | ||
| logger.debug('often makes a very good meal of %s', 'visiting tourists') | ||
|
|
||
|
|
||
| Example Configuration via a Dictionary | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| As of Python 2.7, you can use a dictionary with configuration details. | ||
| :pep:`319` contains a list of the mandatory and optional elements in | ||
| the configuration dictionary. | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| import logging | ||
| from logging.config import dictConfig | ||
|
|
||
| logging_config = dict( | ||
| version = 1, | ||
| formatters = { | ||
| 'f': {'format': | ||
| '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'} | ||
| }, | ||
| handlers = { | ||
| 'h': {'class': 'logging.StreamHandler', | ||
| 'formatter': 'f', | ||
| 'level': logging.DEBUG} | ||
| }, | ||
| loggers = { | ||
| root : {'handlers': ['h'], | ||
| 'level': logging.DEBUG} | ||
| } | ||
| ) | ||
|
|
||
| dictConfig(logging_config) | ||
|
|
||
| logger = logging.getLogger() | ||
| logger.debug('often makes a very good meal of %s', 'visiting tourists') | ||
|
|
||
|
|
||
| Example Configuration Directly in Code | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| import logging | ||
|
|
||
| logger = logging.getLogger() | ||
| handler = logging.StreamHandler() | ||
| formatter = logging.Formatter( | ||
| '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') | ||
| handler.setFormatter(formatter) | ||
| logger.addHandler(handler) | ||
| logger.setLevel(logging.DEBUG) | ||
|
|
||
| logger.debug('often makes a very good meal of %s', 'visiting tourists') | ||
|
|
||
|
|
||
| .. _basic logging tutorial: http://docs.python.org/howto/logging.html#logging-basic-tutorial | ||
| .. _logging configuration: https://docs.python.org/howto/logging.html#configuring-logging | ||
| .. _logging tutorial: http://docs.python.org/howto/logging.html | ||
| .. _configuring logging for a library: https://docs.python.org/howto/logging.html#configuring-logging-for-a-library | ||
| .. _log record: https://docs.python.org/library/logging.html#logrecord-attributes | ||
| .. _requests source: https://github.com/kennethreitz/requests | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think
:pep:actually works (unless this wholly relies on a newer version of sphinx). I'll have to double check.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes on the newer version for sure -- should I delete the markup? (I didn't)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL. Thanks!