@@ -276,6 +276,211 @@ choose a different directory name for the log - just ensure that the directory e
276276and that you have the permissions to create and update files in it.
277277
278278
279+ .. _custom-level-handling :
280+
281+ Custom handling of levels
282+ -------------------------
283+
284+ Sometimes, you might want to do something slightly different from the standard
285+ handling of levels in handlers, where all levels above a threshold get
286+ processed by a handler. To do this, you need to use filters. Let's look at a
287+ scenario where you want to arrange things as follows:
288+
289+ * Send messages of severity ``INFO `` and ``WARNING `` to ``sys.stdout ``
290+ * Send messages of severity ``ERROR `` and above to ``sys.stderr ``
291+ * Send messages of severity ``DEBUG `` and above to file ``app.log ``
292+
293+ Suppose you configure logging with the following JSON:
294+
295+ .. code-block :: json
296+
297+ {
298+ "version" : 1 ,
299+ "disable_existing_loggers" : false ,
300+ "formatters" : {
301+ "simple" : {
302+ "format" : " %(levelname)-8s - %(message)s"
303+ }
304+ },
305+ "handlers" : {
306+ "stdout" : {
307+ "class" : " logging.StreamHandler" ,
308+ "level" : " INFO" ,
309+ "formatter" : " simple" ,
310+ "stream" : " ext://sys.stdout" ,
311+ },
312+ "stderr" : {
313+ "class" : " logging.StreamHandler" ,
314+ "level" : " ERROR" ,
315+ "formatter" : " simple" ,
316+ "stream" : " ext://sys.stderr"
317+ },
318+ "file" : {
319+ "class" : " logging.FileHandler" ,
320+ "formatter" : " simple" ,
321+ "filename" : " app.log" ,
322+ "mode" : " w"
323+ }
324+ },
325+ "root" : {
326+ "level" : " DEBUG" ,
327+ "handlers" : [
328+ " stderr" ,
329+ " stdout" ,
330+ " file"
331+ ]
332+ }
333+ }
334+
335+ This configuration does *almost * what we want, except that ``sys.stdout `` would
336+ show messages of severity ``ERROR `` and above as well as ``INFO `` and
337+ ``WARNING `` messages. To prevent this, we can set up a filter which excludes
338+ those messages and add it to the relevant handler. This can be configured by
339+ adding a ``filters `` section parallel to ``formatters `` and ``handlers ``:
340+
341+ .. code-block :: json
342+
343+ "filters" : {
344+ "warnings_and_below" : {
345+ "()" : " __main__.filter_maker" ,
346+ "level" : " WARNING"
347+ }
348+ }
349+
350+ and changing the section on the ``stdout `` handler to add it:
351+
352+ .. code-block :: json
353+
354+ "stdout" : {
355+ "class" : " logging.StreamHandler" ,
356+ "level" : " INFO" ,
357+ "formatter" : " simple" ,
358+ "stream" : " ext://sys.stdout" ,
359+ "filters" : [" warnings_and_below" ]
360+ }
361+
362+ A filter is just a function, so we can define the ``filter_maker `` (a factory
363+ function) as follows:
364+
365+ .. code-block :: python
366+
367+ def filter_maker (level ):
368+ level = getattr (logging, level)
369+
370+ def filter (record ):
371+ return record.levelno <= level
372+
373+ return filter
374+
375+ This converts the string argument passed in to a numeric level, and returns a
376+ function which only returns ``True `` if the level of the passed in record is
377+ at or below the specified level. Note that in this example I have defined the
378+ ``filter_maker `` in a test script ``main.py `` that I run from the command line,
379+ so its module will be ``__main__ `` - hence the ``__main__.filter_maker `` in the
380+ filter configuration. You will need to change that if you define it in a
381+ different module.
382+
383+ With the filter added, we can run ``main.py ``, which in full is:
384+
385+ .. code-block :: python
386+
387+ import json
388+ import logging
389+ import logging.config
390+
391+ CONFIG = '''
392+ {
393+ "version": 1,
394+ "disable_existing_loggers": false,
395+ "formatters": {
396+ "simple": {
397+ "format": "%(levelname)-8s - %(message)s "
398+ }
399+ },
400+ "filters": {
401+ "warnings_and_below": {
402+ "()" : "__main__.filter_maker",
403+ "level": "WARNING"
404+ }
405+ },
406+ "handlers": {
407+ "stdout": {
408+ "class": "logging.StreamHandler",
409+ "level": "INFO",
410+ "formatter": "simple",
411+ "stream": "ext://sys.stdout",
412+ "filters": ["warnings_and_below"]
413+ },
414+ "stderr": {
415+ "class": "logging.StreamHandler",
416+ "level": "ERROR",
417+ "formatter": "simple",
418+ "stream": "ext://sys.stderr"
419+ },
420+ "file": {
421+ "class": "logging.FileHandler",
422+ "formatter": "simple",
423+ "filename": "app.log",
424+ "mode": "w"
425+ }
426+ },
427+ "root": {
428+ "level": "DEBUG",
429+ "handlers": [
430+ "stderr",
431+ "stdout",
432+ "file"
433+ ]
434+ }
435+ }
436+ '''
437+
438+ def filter_maker (level ):
439+ level = getattr (logging, level)
440+
441+ def filter (record ):
442+ return record.levelno <= level
443+
444+ return filter
445+
446+ logging.config.dictConfig(json.loads(CONFIG ))
447+ logging.debug(' A DEBUG message' )
448+ logging.info(' An INFO message' )
449+ logging.warning(' A WARNING message' )
450+ logging.error(' An ERROR message' )
451+ logging.critical(' A CRITICAL message' )
452+
453+ And after running it like this:
454+
455+ .. code-block :: shell
456+
457+ python main.py 2> stderr.log > stdout.log
458+
459+ We can see the results are as expected:
460+
461+ .. code-block :: shell
462+
463+ $ more * .log
464+ ::::::::::::::
465+ app.log
466+ ::::::::::::::
467+ DEBUG - A DEBUG message
468+ INFO - An INFO message
469+ WARNING - A WARNING message
470+ ERROR - An ERROR message
471+ CRITICAL - A CRITICAL message
472+ ::::::::::::::
473+ stderr.log
474+ ::::::::::::::
475+ ERROR - An ERROR message
476+ CRITICAL - A CRITICAL message
477+ ::::::::::::::
478+ stdout.log
479+ ::::::::::::::
480+ INFO - An INFO message
481+ WARNING - A WARNING message
482+
483+
279484 Configuration server example
280485----------------------------
281486
@@ -3503,7 +3708,7 @@ instance). Then, you'd get this kind of result:
35033708 WARNING:demo:Bar
35043709 >>>
35053710
3506- Of course, these above examples show output according to the format used by
3711+ Of course, the examples above show output according to the format used by
35073712:func: `~logging.basicConfig `, but you can use a different formatter when you
35083713configure logging.
35093714
@@ -3517,7 +3722,6 @@ need to do or deal with, it is worth mentioning some usage patterns which are
35173722*unhelpful *, and which should therefore be avoided in most cases. The following
35183723sections are in no particular order.
35193724
3520-
35213725Opening the same log file multiple times
35223726^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35233727
@@ -3566,7 +3770,6 @@ that in other languages such as Java and C#, loggers are often static class
35663770attributes. However, this pattern doesn't make sense in Python, where the
35673771module (and not the class) is the unit of software decomposition.
35683772
3569-
35703773Adding handlers other than :class: `NullHandler ` to a logger in a library
35713774^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35723775
@@ -3575,7 +3778,6 @@ responsibility of the application developer, not the library developer. If you
35753778are maintaining a library, ensure that you don't add handlers to any of your
35763779loggers other than a :class: `~logging.NullHandler ` instance.
35773780
3578-
35793781Creating a lot of loggers
35803782^^^^^^^^^^^^^^^^^^^^^^^^^
35813783
0 commit comments