How to Configure Logging in TabPy?

TabPy logs are useful for investigating issues, learning about user activities, how TabPy is used and so on. In my previous post TabPy: modifying default configuration I showed how to change TabPy settings. Logging for TabPy is configured in a similar way and I am going to show and explain some details for how to configure logging.

For the most recent and complete documentation about Python logger configuration, read this documentation page – https://docs.python.org/3/howto/logging.html.

In TabPy config file logger (or rather loggers) are configured with a few sections. A logger itself associated with a logger handler which in its turn depends on a logger formatter:

  • Loggers are used in Python code to initiate logging a message. Based on the severity logger passes the message to corresponding handlers.
  • Handlers are dispatching log messages to the handler’s specified destination. There are handlers for the console, files, etc.
  • Formatters specify how a logged message should look like: order and format of the message properties.

For logger configuration file format read the documentation at https://docs.python.org/3/library/logging.config.html#configuration-file-format.

Following is the example of how to configure logging.

First, section to look at is [loggers] where names of loggers are listed (TabPy will log with all of them). The following example specifies 2 loggers:

[loggers]
keys=root,fileLogger

NOTE: there have to be a logger (it can be the only logger) named “root” in the list of loggers.

Next is the [handlers] section which lists handlers:

[handlers]
keys=consoleHandler,fileHandler

And finally formatters section [formatter] lists all the formatters (not all of them have to be used):

[formatters]
keys=consoleFormatter,fileFormatter

Now for each logger in [logger] section there is [logger_<name>] section with settings for a logger:

[logger_root]
level=WARNING
handlers=consoleHandler

[logger_fileLogger]
level=DEBUG
handlers=fileHandler
propgate=1
qualname=consoleLogger

As you can see from the example above for each logger severity level is specified which means only messages with specified severity of higher. The level can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL or NOTSET (all messages will be logged).

Handlers for a logger are listed with handlers parameter. It is possible to have more than one handler for a logger.

For non-root loggers properties propgate and qualname need to be set. The first one specifies if messages need to be propagated to a handler higher up and is set to 1 or 0. And qualname sets the name for the logger so it can be referenced from Python code.

For each handler there is [handler_<name>] section:

[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=consoleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=fileFormatter
args=('tabpy_log.log', 'a', 1000000, 5)

For each formatter there is level parameter which is configured in the same way as for a logger.

With class parameter a Python class which implements a handler is specified (more details below). It is possible to use the same class for different handlers. As an example, you can have one large log file where entries are appended and the same messages logged to date specific log files.

Formatter for logged messages is set with formatter parameter. Again – the same formatter can be used with multiple handlers.

And args parameter provides logger specific parameters.

Handler classes available with Python are listed at https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers, but it is possible to use any custom handlers (e.g. for colorful output in the console) with other packages installed in the Python environment. Some useful handlers are:

  • StreamHandler sends logging to a stream (e.g. console).
  • FileHandler appends messages to a file.
  • NullHandler does not log anything.
  • RotatingFileHandler logs to a file until log file size limit is reached and then creates a new file and logs to it. The handler is used in the example above and it will create a new log file when the current one reaches 1000000 bytes in size. The current file name is always tabpy_log.log, previous log file will be named tabpy_log.log.1 and so on till tabpy_log.log.5.
  • TimedRotatingFileHandler is similar to RotatingFileHandler but creates a new file after the specified time interval.
  • HTTPHandler sends messages to a web server with GET or POST command.

For each formatter printf-style string formatting string (documentation is here – https://docs.python.org/3/library/stdtypes.html#old-string-formatting) specifies how the format message is built. In the format string additional log objects can be used (list of the log objects is here – https://docs.python.org/3/library/logging.html#logging.LogRecord):

[formatter_consoleFormatter]
format=%(asctime)s: %(message)s
datefmt=%H:%M:%S

[formatter_fileFormatter]
format=%(asctime)s [%(levelname)s] (%(filename)s:%(module)s:%(lineno)d): %(message)s
datefmt=%Y-%m-%d,%H:%M:%S

In this example console formatter will output all the messages with preceding timestamp and for file logs there will be date-time stamp, severity of the message, where in the code it was logged from and the message itself.

Now the whole config file (I have it on my machine saved as c:\demo\tabpy\tabpy.conf:

[loggers]
keys=root,fileLogger

[formatters]
keys=consoleFormatter,fileFormatter

[handlers]
keys=consoleHandler,fileHandler

[logger_root]
level=WARNING
handlers=consoleHandler

[logger_fileLogger]
level=DEBUG
handlers=fileHandler
propgate=1
qualname=consoleLogger

[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=consoleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=fileFormatter
args=('tabpy_log.log', 'a', 1000000, 5)

[formatter_consoleFormatter]
format=%(asctime)s: %(message)s
datefmt=%H:%M:%S

[formatter_fileFormatter]
format=%(asctime)s [%(levelname)s] (%(filename)s:%(module)s:%(lineno)d): %(message)s
datefmt=%Y-%m-%d,%H:%M:%S

Starting TabPy with the config:

tabpy --config c:\demo\tabpy\tabpy.conf

After running a few requests against my local TabPy instance this is what I see in the console:

15:19:37: Responding with status=404, message="Unknown endpoint", info="Endpoint olek_add is not found"
15:19:37: 404 GET /endpoints/olek_add (::1) 5.03ms

And for the same TabPy there’s much much more information in tabpy_log.log file:

2019-12-04,15:14:54 [DEBUG] (app.py:app:215): Parameter port set to "9004" from default value
2019-12-04,15:14:54 [DEBUG] (app.py:app:215): Parameter server_version set to "0.8.9" from default value
2019-12-04,15:14:54 [DEBUG] (app.py:app:215): Parameter evaluate_timeout set to "30" from default value
...
2019-12-04,15:15:17 [INFO] (app.py:app:110): Initializing TabPy...
...
2019-12-04,15:15:17 [INFO] (app.py:app:93): Web service listening on port 9004

5 1 vote
Article Rating
Share the post if you liked it
Oleksandr Golovatyi

Author: Oleksandr Golovatyi

Member of Tableau Advanced Analytics team and a contributor to TabPy and this blog.

1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
trackback

[…] post explains some details about logger configuration for TabPy – How to Configure Logging in TabPy? Another post shows how to use colors for console logging – Add colors to TabPy console […]