@@ -276,6 +276,211 @@ choose a different directory name for the log - just ensure that the directory e
276
276
and that you have the permissions to create and update files in it.
277
277
278
278
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
+
279
484
Configuration server example
280
485
----------------------------
281
486
@@ -3503,7 +3708,7 @@ instance). Then, you'd get this kind of result:
3503
3708
WARNING:demo:Bar
3504
3709
>>>
3505
3710
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
3507
3712
:func: `~logging.basicConfig `, but you can use a different formatter when you
3508
3713
configure logging.
3509
3714
@@ -3517,7 +3722,6 @@ need to do or deal with, it is worth mentioning some usage patterns which are
3517
3722
*unhelpful *, and which should therefore be avoided in most cases. The following
3518
3723
sections are in no particular order.
3519
3724
3520
-
3521
3725
Opening the same log file multiple times
3522
3726
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3523
3727
@@ -3566,7 +3770,6 @@ that in other languages such as Java and C#, loggers are often static class
3566
3770
attributes. However, this pattern doesn't make sense in Python, where the
3567
3771
module (and not the class) is the unit of software decomposition.
3568
3772
3569
-
3570
3773
Adding handlers other than :class: `NullHandler ` to a logger in a library
3571
3774
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3572
3775
@@ -3575,7 +3778,6 @@ responsibility of the application developer, not the library developer. If you
3575
3778
are maintaining a library, ensure that you don't add handlers to any of your
3576
3779
loggers other than a :class: `~logging.NullHandler ` instance.
3577
3780
3578
-
3579
3781
Creating a lot of loggers
3580
3782
^^^^^^^^^^^^^^^^^^^^^^^^^
3581
3783
0 commit comments