Contents

Consideration about python logging and file configuration(2)

Former article (Consideration about python logging and file configuration(1)) has concerns to be updated.

  • There is more space to improve the usage for logging library with module naming space with __name__.
  • setting configuration by config.fileConfig is older and limited in features (e.g. filter setting is not supported) (ref. document). Instead, it’s better to use config.dictConfig and load configuration file in .json or .yml format, a format that can be loaded as python' dictionary format.

Environment

  • MacOS Catalina 10.15.7
  • Python 3.9.7

5. Single logger for different modules, with utilizing module naming space

Directory structure

1
2
3
4
5
6
7
python_logging
├── sample5
│   ├── __init__.py
│   ├── logging5.conf
│   ├── sample5_called.py
│   └── sample5_main.py
└── setup.py

Above sample directory structure can be said that every python module (sample5_main.py and sample5_called.py) belongs to sample5 package.

In that case, __name__ in each module is:

  • sample5_main.py : __main__ (this is because sample5_main.py is called as main routine. If it’s not called as main routine, __name__ is sample5.sample5_main)
  • sample5_called.py : sample5.sample5_called

So, if a logger is defined by a name of sample5, a root package name, the modules below can succeed that setting.

Using __name__ would be good to use logger function (handler, filter etc.) with distinguishing modules by naming space for expansion in the future.

Tip
For adding a root package name (sample5) as a module, it’s necessary to add path to the parent directory that python recognizes. For doing that, see some ways to add python path

logging5.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[loggers]
keys=root, sample5

[handlers]
keys=fileHandler, consoleHandler

[logger_root]
handlers=fileHandler, consoleHandler
level=DEBUG

[logger_sample5]
handlers=fileHandler, consoleHandler
level=DEBUG
qualname=sample5
propagate=0

[handler_fileHandler]
class=handlers.TimedRotatingFileHandler
level=DEBUG
formatter=logFormatter
args=('sample5/sample5.log', 'D')

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=logFormatter
args=(sys.stdout, )

[formatters]
keys=logFormatter

[formatter_logFormatter]
class=logging.Formatter
format=%(asctime)s %(thread)d %(levelname)s %(name)s %(funcName)s %(lineno)d %(message)s

sample5_main.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from logging import config, getLogger

config.fileConfig('logging.conf')
logger = getLogger(__name__)

def main():
    logger.debug("this is logged by main()")

if __name__ == '__main__':
    main()

sample5_called.py

1
2
3
4
5
6
from logging import getLogger

logger = getLogger(__name__)

def sample5_func():
    logger.debug("this is logged by sample5_called")

run module from outside of sample5 directory.

1
2
3
$ python sample5/sample5_main.py 
2021-10-22 13:59:02,028 4456861120 DEBUG __main__ main 8 this is logged by main()
2021-10-22 13:59:02,028 4456861120 DEBUG sample5.sample5_called sample5_func 6 this is logged by sample5_called

Output in sample5/sample5.log is like:

1
2
2021-10-22 13:59:02,028 4456861120 DEBUG __main__ main 8 this is logged by main()
2021-10-22 13:59:02,028 4456861120 DEBUG sample5.sample5_called sample5_func 6 this is logged by sample5_called

6. logger with config.dictConfig

Directory structure

1
2
3
4
5
6
7
python_logging
├── sample6
│   ├── __init__.py
│   ├── logging_conf.json
│   ├── sample6_called.py
│   └── sample6_main.py
└── setup.py

logging_conf.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
    "version": 1,
    "disable_existing_loggers": false,
    "root": {
      "level": "DEBUG",
      "handlers": [
        "consoleHandler",
        "logFileHandler"
      ]
    },
    "sample6": {
        "level": "DEBUG",
        "handlers": [
          "consoleHandler",
          "logFileHandler"
        ],
        "propagate": false
    },
    "handlers": {
      "consoleHandler": {
        "class": "logging.StreamHandler",
        "level": "DEBUG",
        "formatter": "consoleFormatter",
        "stream": "ext://sys.stdout"
      },
      "logFileHandler": {
        "class": "logging.handlers.TimedRotatingFileHandler",
        "level": "DEBUG",
        "formatter": "logFileFormatter",
        "filename": "./sample6/sample6.log",
        "when" : "MIDNIGHT",
        "backupCount": 7,
        "encoding": "utf-8"
      }
    },
    "formatters": {
      "consoleFormatter": {
        "format": "%(asctime)s %(thread)d %(levelname)s %(name)s %(funcName)s %(lineno)d %(message)s"
      },
      "logFileFormatter": {
        "format": "%(asctime)s %(thread)d %(levelname)s %(name)s %(funcName)s %(lineno)d %(message)s"
      }
    }
}
Tip
  • "disable_existing_loggers": false should be set for seeing existing logger including third party libraries' one with useful information.
  • "handlers.logFileHandler.when": "MIDNIGHT" seems to be better than "D" for surely enabling to switch log file over the end of the day.

sample6_main.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import json
from logging import config, getLogger
from sample6.sample6_called import sample6_func

config_dict = None
with open('sample6/logging_conf.json', 'r', encoding='utf-8') as f:
    config_dict = json.load(f)

config.dictConfig(config_dict)
logger = getLogger(__name__)

def main():
    logger.debug("this is logged by main()")
    sample6_func()

if __name__ == '__main__':
    main()

sample6_called.py

1
2
3
4
5
6
from logging import getLogger

logger = getLogger(__name__)

def sample6_func():
    logger.debug("this is logged by sample6_called")

The output in standard output and log file is the same as sample5.


Sources

Sample codes are uploaded in github


References