Polybot Project: a Simple Pretty Logger


In a series of blog posts “Python : Telegram Bot Webhook Handler : A Multi-Bot/Multi-Language Design” (1-8) (see references) we discussed how we could design a multi-bot/multi-language webhook handler to host some Telegram bots each of which could speak any of (so far) three languages based on the user’s choice. At the last few posts, we chose the name polybot for this project.

Until this point, I ran the flask web framework locally on my home computer, and with some trick, I made this local flask server look like a full operational server under the reach of the Telegram server. I set up the real-world code of the webhook handler hosted by the local flask, and also reported the available webhook URLs to the Telegram system for each bot. I tested the code as well as the conversations with the local bots successfully during the progress of the blog posts of the series.

Today, I ported the codes to the remote server of PythonAnywhere, and I tested the codes successfully. There are three bots now available for the users’ tests. You could compare their behaviors and operations … in particular, how they could speak different languages, and how they handle different time zones based on the users’ preferences and settings:

Make sure to open the bots’ profile links above in a telegram application. Then start a conversation with each bot using a start command:

/start

In particular, try using the following commands:

/start
/intro
/help
/settings
/time
/hello

The command /help will tell you what each of these commands does. In particular, try examining the /time command each time you change your time zone information with /settings command. You could also try the command /hello each time you change the bot’s language.

If you have already tested Merex before, hopefully you won’t feel any change in his behavior since the new design is compatible with the older code with respect to the bots’ behavior to the commands and conversations … well almost.

There are a few technical details that have not been covered in the series of blog posts yet … such as the interactive mode of bot-user conversations; for example, how a user may change the bot’s language or their current time zone at the startup or using the bot’s settings. These technical details will be discussed in future blog posts.

Logging vs Pretty Printing

When using a local flask server, there is no limitations in using the standard print function of python. Neither is there any limitation in using the pretty print function; that is the pprint function of pprint module (see references).

These two functions help a lot in figuring out what your code is doing, and what sort of objects with what sort of structures your code is dealing with. I personally learnt a lot about the nature of the Telegram updates and messages merely with pretty-printing them and examining their structure before I write a suitable code in the webhook handler to manipulate the updates and messages and to provide a behavior with discipline for my bots.

In the remote servers such as PythonAnywhere, these two functions are not available as in a standard python console application or as in a local flask web framework. The best alternative is the server’s logging facility for which the standard python logging module looks fair enough (see references).

The available functionality of the logging module works fine as a replacement of the print function. In fact, you could still use this module for many other areas … including normal console-based applications, or more importantly, console-less applications. Our current remote server of PythonAnywhere is one of those console-less cases here.

A dedicated Pretty Log module : plog.py

The replacement of the pprint function using the logging module is simple: a single-file module plog.py could do it well:

# -*- coding: utf-8 -*-
#
#  plog.py
#

from pprint import pformat



class Plog :
    def __init__(self, logger) :
        self.logger = logger

    def info(self, *args, **kw_args) :
        Plog.pretty_log(self.logger, 'info', *args, **kw_args)

    def warning(self, *args, **kw_args) :
        Plog.pretty_log(self.logger, 'warning', *args, **kw_args)

    def error(self, *args, **kw_args) :
        Plog.pretty_log(self.logger, 'error', *args, **kw_args)

    def debug(self, *args, **kw_args) :
        Plog.pretty_log(self.logger, 'debug', *args, **kw_args)


    @staticmethod
    def pretty_log(logger, level, message, obj) :
        method = getattr(logger, level)
        method(message + '\n' + pformat(obj))

The instance object of the class Plog in this module helps to visualize various objects exactly the same way that the normal pprint function does. It prints a normal log text line followed by a structured python object.

Usage Examples

The usage is simple, too. In the main flask_app.py module; you could place these lines in the global area:

import logging
from plog import Plog

#~ logging.basicConfig(format='%(asctime)s [%(levelname)-8s] %(message)s', stream=sys.stderr, level=logging.DEBUG)
logging.basicConfig(format='[%(levelname)-8s] %(message)s', stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)
plog = Plog(logger)

You could use (un-comment) the basicConfig with %(asctime)s specifier, if your server does not insert current date/time automatically. In other modules:

import logging
from plog import Plog

logger = logging.getLogger(__name__)
plog = Plog(logger)

Apart from our new plog pretty logger, using logger defined as the codes above … instead of directly using logging itself … is the preferred and recommended way of using the logging module according to the documentations.

Now, to simply print a message (in its simplest form) in the system log, we could use one of the methods:

logger.info('some text message')
logger.warning('some text message')
logger.error('some text message')
logger.debug('some text message')

It prints a message it the system log with much more discipline than the normal print function. Each message line follows the rules that you set using the basicConfig method. Each of these methods (info, warning, …) are the various levels of the log which make the associated message text to be hidden or visible when you set or change the level of logging (refer to the documentation for a thorough insight).

And if you wish to pretty-print a python object, say, a dict object with a caption:

plog.info('some caption', some_object_such_as_a_list_or_a_dict)
plog.warning('some caption', some_object_such_as_a_list_or_a_dict)
plog.error('some caption', some_object_such_as_a_list_or_a_dict)
plog.debug('some caption', some_object_such_as_a_list_or_a_dict)

That’s it. For the text messages and captions, the f-string representation of the string literals in python 3.6 helps a lot in writing concise readable program codes for the logging stuff.

References:

This entry was posted in Flask, Python, Telegram Bot and tagged , , , , , , , , , . Bookmark the permalink.

Leave a comment