Skip to content

Commit cb1ddc6

Browse files
committed
Merge pull request realpython#512 from tanyaschlusser/master
Logging section
2 parents 4db76aa + df7a225 commit cb1ddc6

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

docs/contents.rst.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This part of the guide focuses on best practices for writing Python code.
3232
writing/reading
3333
writing/documentation
3434
writing/tests
35+
writing/logging
3536
writing/gotchas
3637
writing/license
3738

docs/writing/logging.rst

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
Logging
2+
=======
3+
4+
The :mod:`logging` module has been a part of Python's Standard Library since
5+
version 2.3. It is succinctly described in :pep:`282`. The documentation
6+
is notoriously hard to read, except for the `basic logging tutorial`_.
7+
8+
Logging serves two purposes:
9+
10+
- **Diagnostic logging** records events related to the application's
11+
operation. If a user calls in to report an error, for example, the logs
12+
can be searched for context.
13+
- **Audit logging** records events for business analysis. A user's
14+
transactions can be extracted and combined with other user details for
15+
reports or to optimize a business goal.
16+
17+
18+
... or Print?
19+
-------------
20+
21+
The only time that ``print`` is a better option than logging is when
22+
the goal is to display a help statement for a command line application.
23+
Other reasons why logging is better than ``print``:
24+
25+
- The `log record`_, which is created with every logging event, contains
26+
readily available diagnostic information such as the file name,
27+
full path, function, and line number of the logging event.
28+
- Events logged in included modules are automatically accessible via the
29+
root logger
30+
to your application's logging stream, unless you filter them out.
31+
- Logging can be selectively silenced by using the method
32+
:meth:`logging.Logger.setLevel` or disabled by setting the attribute
33+
:attr:`logging.Logger.disabled` to ``True``.
34+
35+
36+
Logging in a Library
37+
--------------------
38+
39+
Notes for `configuring logging for a library`_ are in the
40+
`logging tutorial`_. Because the *user*, not the library, should
41+
dictate what happens when a logging event occurs, one admonition bears
42+
repeating:
43+
44+
.. note::
45+
It is strongly advised that you do not add any handlers other than
46+
NullHandler to your library’s loggers.
47+
48+
49+
Best practice when instantiating loggers in a library is to only create them
50+
using the ``__name__`` global variable: the :mod:`logging` module creates a
51+
hierarchy of loggers using dot notation, so using ``__name__`` ensures
52+
no name collisions.
53+
54+
Here is an example of best practice from the `requests source`_ -- place
55+
this in your ``__init__.py``
56+
57+
.. code-block:: python
58+
59+
# Set default logging handler to avoid "No handler found" warnings.
60+
import logging
61+
try: # Python 2.7+
62+
from logging import NullHandler
63+
except ImportError:
64+
class NullHandler(logging.Handler):
65+
def emit(self, record):
66+
pass
67+
68+
logging.getLogger(__name__).addHandler(NullHandler())
69+
70+
71+
72+
Logging in an Application
73+
-------------------------
74+
75+
The `twelve factor app <http://12factor.net>`_, an authoritative reference
76+
for good practice in application development, contains a section on
77+
`logging best practice <http://12factor.net/logs>`_. It emphatically
78+
advocates for treating log events as an event stream, and for
79+
sending that event stream to standard output to be handled by the
80+
application environment.
81+
82+
83+
There are at least three ways to configure a logger:
84+
85+
- Using an INI-formatted file:
86+
- **Pro**: possible to update configuration while running
87+
using the function :func:`logging.config.listen` to listen
88+
on a socket.
89+
- **Con**: less control (*e.g.* custom subclassed filters or loggers)
90+
than possible when configuring a logger in code.
91+
- Using a dictionary or a JSON-formatted file:
92+
- **Pro**: in addition to updating while running, it is possible to
93+
load from a file using the :mod:`json` module, in the standard
94+
library since Python 2.6.
95+
- **Con**: less control than when configuring a logger in code.
96+
- Using code:
97+
- **Pro**: complete control over the configuration.
98+
- **Con**: modifications require a change to source code.
99+
100+
101+
Example Configuration via an INI File
102+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
103+
104+
Let us say the file is named ``logging_config.ini``.
105+
More details for the file format are in the `logging configuration`_
106+
section of the `logging tutorial`_.
107+
108+
.. code-block:: ini
109+
110+
[loggers]
111+
keys=root
112+
113+
[handlers]
114+
keys=stream_handler
115+
116+
[formatters]
117+
keys=formatter
118+
119+
[logger_root]
120+
level=DEBUG
121+
handlers=stream_handler
122+
123+
[handler_stream_handler]
124+
class=StreamHandler
125+
level=DEBUG
126+
formatter=formatter
127+
args=(sys.stderr,)
128+
129+
[formatter_formatter]
130+
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s
131+
132+
133+
Then use :meth:`logging.config.fileConfig` in the code:
134+
135+
.. code-block:: python
136+
137+
import logging
138+
from logging.config import fileConfig
139+
140+
fileConfig('logging_config.txt')
141+
logger = logging.getLogger()
142+
logger.debug('often makes a very good meal of %s', 'visiting tourists')
143+
144+
145+
Example Configuration via a Dictionary
146+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147+
148+
As of Python 2.7, you can use a dictionary with configuration details.
149+
:pep:`319` contains a list of the mandatory and optional elements in
150+
the configuration dictionary.
151+
152+
.. code-block:: python
153+
154+
import logging
155+
from logging.config import dictConfig
156+
157+
logging_config = dict(
158+
version = 1,
159+
formatters = {
160+
'f': {'format':
161+
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s'}
162+
},
163+
handlers = {
164+
'h': {'class': 'logging.StreamHandler',
165+
'formatter': 'f',
166+
'level': logging.DEBUG}
167+
},
168+
loggers = {
169+
root : {'handlers': ['h'],
170+
'level': logging.DEBUG}
171+
}
172+
)
173+
174+
dictConfig(logging_config)
175+
176+
logger = logging.getLogger()
177+
logger.debug('often makes a very good meal of %s', 'visiting tourists')
178+
179+
180+
Example Configuration Directly in Code
181+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
182+
183+
.. code-block:: python
184+
185+
import logging
186+
187+
logger = logging.getLogger()
188+
handler = logging.StreamHandler()
189+
formatter = logging.Formatter(
190+
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
191+
handler.setFormatter(formatter)
192+
logger.addHandler(handler)
193+
logger.setLevel(logging.DEBUG)
194+
195+
logger.debug('often makes a very good meal of %s', 'visiting tourists')
196+
197+
198+
.. _basic logging tutorial: http://docs.python.org/howto/logging.html#logging-basic-tutorial
199+
.. _logging configuration: https://docs.python.org/howto/logging.html#configuring-logging
200+
.. _logging tutorial: http://docs.python.org/howto/logging.html
201+
.. _configuring logging for a library: https://docs.python.org/howto/logging.html#configuring-logging-for-a-library
202+
.. _log record: https://docs.python.org/library/logging.html#logrecord-attributes
203+
.. _requests source: https://github.com/kennethreitz/requests

0 commit comments

Comments
 (0)