Logs in Telegram bots

Logs in Telegram bots

Hello, Habre!

Logging in is not just writing text messages to a file or console. This is an integral part of the life of any competently made application. With cart bots, logging becomes your eyes and ears, helping you monitor data flows, understand bot behavior, and most importantly, quickly find and fix bugs.

After all, who among us does not like to fix something in 5 minutes instead of spending all day looking for a problem?

The logging library

logging is a standard Python library for logging. It is powerful, flexible and easily customizable.

Maybe someone will ask why loggingand not just print?
print for debugging is the use of hammers instead of a surgical scalpel. print can help you in simple scenarios, but as your project grows, you need a tool that can help you better understand what’s going on in your application. logging allows you to set message levels, format them, and send them to different locations, such as files or even remote servers.

Import and basic setup:

import logging

# Базовая конфигурация: уровень логирования и формат сообщения
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")

Logging levels:

  • DEBUGDetailed information, usually interesting only when diagnosing problems.

  • INFO: Confirming that things are working as expected

  • WARNING: an indication of some unexpected event or problem in the future

  • ERROR: An error that prevented the execution of a particular task from being completed.

  • CRITICAL: A serious error indicating that the program may not be able to continue.

Record logs:

logging.debug('Это сообщение уровня DEBUG')
logging.info('Это сообщение уровня INFO')
logging.warning('Это сообщение уровня WARNING')
logging.error('Это сообщение уровня ERROR')
logging.critical('Это сообщение уровня CRITICAL')

Later in the article we will use loggingbecause it is the de facto Python standard for logging.

Log format is a template that defines what each log entry will look like.

Some formats look like this:

  • %(asctime)s – Event time.

  • %(name)s – Name of the logger.

  • %(levelname)s – Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL).

  • %(message)s – Message text.

  • %(filename)s and %(lineno)d – File name and line number where the log message was generated.

We connect logging

First of all, let’s set up the logger.

import logging

logging.basicConfig(level=logging.INFO,
                    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

Now let’s integrate logging into our handlers.

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters

def start(update, context):
    logger.info("User %s started the conversation.", update.message.from_user)
    update.message.reply_text('Привет! Я твой логирующий бот.')

def help_command(update, context):
    logger.info("User %s asked for help.", update.message.from_user)
    update.message.reply_text('Спроси, и я отвечу!')

def echo(update, context):
    user_text = update.message.text
    logger.info("User %s said: %s", update.message.from_user, user_text)
    update.message.reply_text(user_text)

Let’s not forget about mistakes!

def error(update, context):
    logger.warning('Update "%s" caused error "%s"', update, context.error)

Let’s collect our bot:

def main():
    updater = Updater("YOUR_TELEGRAM_TOKEN", use_context=True)
    dp = updater.dispatcher

    dp.add_handler(CommandHandler("start", start))
    dp.add_handler(CommandHandler("help", help_command))
    dp.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))

    dp.add_error_handler(error)

    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()

When and what level to use?

Debugging:

When you need detailed information to diagnose a problem. This is your debugging toolkit.

Include detailed messages to help you understand execution flow and job status. For example, you can log the state of variables, responses from APIs, etc

Info:

When you need to confirm that things are working as expected. This is your application log.

Record the main points of the bot’s execution, such as when the bot starts and ends.

Warning:

When something unexpected happens, or there is a potential problem on the horizon. This is your signal to pay attention.

Use for situations that are not bugs but may cause problems in the future, such as deprecated features, bad data, or possible performance bottlenecks.

Error:

When an error occurred that prevented a certain task from being completed. This is your red flag.

Record errors that affect the operation of the bot, but do not lead to a complete failure. This could be an error in logic, an exception, or other issues that need attention.

Critical:

When a serious error has occurred that could cause the program to crash.

Understand when to use each level. Don’t clutter logs with Debug messages if you don’t need them in production.

Where to send logs

The most basic and clear way writing logs to a file on the serverwhere your bot works. It can be either a text file or a JSON file.

To begin, we need to configure the logger in Python using logging. We will set the basic configuration so that the logs are written to a file.

import logging

logging.basicConfig(filename="bot.log", level=logging.INFO, 
                    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

filename="bot.log" indicates that all logs will be written to a file bot.log.

level=logging.INFO means that messages of the INFO level and higher (WARNING, ERROR, CRITICAL) will be recorded.

format="..." determines the format of our leagues.

Message to channel or group

import logging
from telegram import Bot
from telegram.ext import Updater, CommandHandler

TOKEN = 'YOUR_BOT_TOKEN'
# ID вашего канала или группы (например, -100123456789)
LOG_CHANNEL = 'YOUR_CHANNEL_OR_GROUP_ID'

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# создадимм экземпляра бота
bot = Bot(token=TOKEN)

def log_to_channel(level, message):
    """
    Функция для отправки логов в канал или группу.
    """
    try:
        # Отправка сообщения в канал/группу
        bot.send_message(chat_id=LOG_CHANNEL, text=f"{level}: {message}")
    except Exception as e:
        # Логирование ошибки, если сообщение не было отправлено
        logger.error(f"Ошибка при отправке сообщения в канал: {e}")

# Пример использования функции для отправки лога
log_to_channel('INFO', 'Бот запущен и готов к работе!')

def start(update, context):
    user = update.message.from_user
    # Отправка лога при начале разговора с пользователем
    log_to_channel('INFO', f"Пользователь {user.username} ({user.id}) начал разговор.")
    update.message.reply_text('Привет! Я твой бот.')

def main():
    updater = Updater(TOKEN, use_context=True)
    dp = updater.dispatcher
    dp.add_handler(CommandHandler("start", start))
    updater.start_polling()
    # Отправка лога о начале работы бота
    log_to_channel('INFO', 'Бот начал работу.')
    updater.idle()

if __name__ == '__main__':
    main()

log_to_channel takes a login level and a message and then sends that message to your channel or group.

We catch and log exceptions

Exceptions can tell you about problems with your bot that you might have missed. They indicate code errors or problems in external services.

In Python, exceptions are caught using a construct try-except. In bot code, you’ll most likely use this inside command handlers or in methods that are called in response to user actions:

def some_bot_operation(update, context):
    try:
        risky_operation()
    except Exception as e:
        logger.error(f"Произошла ошибка: {e}")
        update.message.reply_text('Упс! Что-то пошло не так.')

python-telegram-bot gives us a special handler to catch all exceptions that weren’t caught inside command handlers:

from telegram.ext import Updater, CommandHandler

def error_handler(update, context):
    logger.error(f"Ошибка в обработчике {update}: {context.error}")

def main():
    updater = Updater("YOUR_TOKEN", use_context=True)
    dp = updater.dispatcher
    dp.add_error_handler(error_handler)
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()

For more detailed analysis, you can log additional information about the context of the error, such as user data or the content of the message that caused the error:

def error_handler(update, context):
    user = update.message.from_user
    logger.error(f"Ошибка от пользователя {user.username} ({user.id}): {context.error}")

Even if it seems that the error is not critical, log it anyway. Sometimes a small mistake can be a sign of a big problem.

An example of implementation

For example, we have a pizza ordering bot with a login.

To begin with, we will set up logging in the file and in the channel cart.

import logging
from pythonjsonlogger import jsonlogger
from telegram import Bot

TOKEN = 'YOUR_BOT_TOKEN'
LOG_CHANNEL = 'YOUR_CHANNEL_OR_GROUP_ID'

# настроем логирование в файл в формате JSON
logHandler = logging.FileHandler('pizza_bot.log')
formatter = jsonlogger.JsonFormatter('%(asctime)s %(levelname)s %(message)s')
logHandler.setFormatter(formatter)

# настроим логер
logger = logging.getLogger(__name__)
logger.addHandler(logHandler)
logger.setLevel(logging.INFO)

# функция для отправки логов в Telegram-канал
bot = Bot(token=TOKEN)
def log_to_channel(message):
    try:
        bot.send_message(chat_id=LOG_CHANNEL, text=message)
    except Exception as e:
        logger.error(f"Ошибка при отправке сообщения в канал: {e}")

Let’s create the basic logic of our bot for ordering pizza

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, ConversationHandler

# оппеделение состояний разговора
(CHOOSING_PIZZA, CHOOSING_DRINK) = range(2)

# Словарь с пиццами и напитками
PIZZAS = {'Маргарита': 5, 'Пепперони': 6, 'Гавайская': 7}
DRINKS = {'Кока-Кола': 2, 'Спрайт': 2, 'Фанта': 2}

# Обработчики команд
def start(update, context):
    update.message.reply_text('Привет! Я бот для заказа пиццы. Что вы хотите заказать?')
    logger.info(f"Пользователь {update.message.from_user.username} начал разговор.")
    log_to_channel(f"Пользователь {update.message.from_user.username} начал разговор.")
    return CHOOSING_PIZZA

def choose_pizza(update, context):
    user_choice = update.message.text
    if user_choice in PIZZAS:
        price = PIZZAS[user_choice]
        update.message.reply_text(f"Вы выбрали {user_choice}. Хотите что-нибудь к пицце?")
        logger.info(f"Пользователь {update.message.from_user.username} выбрал пиццу {user_choice}.")
        log_to_channel(f"Пользователь {update.message.from_user.username} выбрал пиццу {user_choice}.")
        return CHOOSING_DRINK
    else:
        update.message.reply_text("Пожалуйста, выберите пиццу из списка.")
        return CHOOSING_PIZZA

def choose_drink(update, context):
    user_choice = update.message.text
    if user_choice in DRINKS:
        price = DRINKS[user_choice]
        update.message.reply_text(f"Вы добавили {user_choice} к заказу. Спасибо за заказ!")
        logger.info(f"Пользователь {update.message.from_user.username} добавил к заказу {user_choice}.")
        log_to_channel(f"Пользователь {update.message.from_user.username} добавил к заказу {user_choice}.")
    else:
        update.message.reply_text("Пожалуйста, выберите напиток из списка.")
    return ConversationHandler.END

def cancel(update, context):
    update.message.reply_text('Заказ отменен. До свидания!')
    logger.info(f"Пользователь {update.message.from_user.username} отменил заказ.")
    log_to_channel(f"Пользователь {update.message.from_user.username} отменил заказ.")
    return ConversationHandler.END

# Основная функция
def main():
    updater = Updater(TOKEN, use_context=True)
    dp = updater.dispatcher

    # Настройка разговорного обработчика
    conv_handler = ConversationHandler(
        entry_points=[CommandHandler('start', start)],
        states={
            CHOOSING_PIZZA: [MessageHandler(Filters.text, choose_pizza)],
            CHOOSING_DRINK: [MessageHandler(Filters.text, choose_drink)]
        },
        fallbacks=[CommandHandler('cancel', cancel)]
    )

    dp.add_handler(conv_handler)

    # Начать опрос и работу бота
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()

We defined several states for the conversation: choosing a pizza and choosing a drink. We have dictionaries PIZZAS and DRINKS with available options and prices. Functions start, choose_pizza, choose_drink and cancel process different stages of the order.

We use ConversationHandler to control the flow of the conversation.

In each function, we log events both to a file and to a Telegram channel.

How not to lose logs

Rotating File Handler is a special handler in the module logging. It allows you to “rotate” logs, that is, automatically create new log files after reaching a certain size or time period, and, if necessary, delete old ones

Without log file rotation, you run the risk of running into several problems:

Over time, files can become so large that they become difficult to open and analyze. Especially if your bot generates a lot of logs, it can fill up disk space on the server.

And in general, it is easy to miss important messages in a large file of leagues.

Import required modules:

import logging
from logging.handlers import RotatingFileHandler

Rotating File Handler settings:

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)  # Устанавливаем уровень логирования

# настроим rfl
handler = RotatingFileHandler('bot.log', maxBytes=5000000, backupCount=5)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

# Добавляем обработчик к логгеру
logger.addHandler(handler)

'bot.log' – The name of the log file. maxBytes=5000000 – The maximum size of the file in bytes (in this case 5 MB) before it is rotated. backupCount=5 — the number of log files to be stored. Once this number is reached, the oldest file will be deleted.

The logger code is configured, you can use it in your bot as usual.


Well-configured logging helps you not only in its operation, but also in improving its operation. Understand what the jams are, what to fix and how to optimize the bot. And you can get practical skills in logging, analytics and development as part of online courses from leading market experts. Look in the catalog, to my colleagues from OTUS and choose the appropriate direction.

Keep coding, keep improvingand to new meetings in Habra.

and… Happy New Year! 🎄

Related posts