Skip to main content

Flexible integration of logging module and Telegram Bot Api with HTML support

Project description

markup-tg-logger

Flexible integration of Python's standard logging module and Telegram Bot API for logging to Telegram chats or channels with built-in HTML support.

Features

  • Sending messages to multiple chats from a single handler.
  • Ability to format log messages, fmt strings, stack and traceback output, or the final output.
  • Full built-in HTML support. Extensible for Markdown and MarkdownV2 usage.
  • Splitting long log entries into multiple messages that don't exceed Telegram's character limit while maintaining valid markup during splitting.
  • Configurable escaping of markup special characters at different formatting stages.
  • Configurable notification disabling based on log level or other parameters.
  • Choice of HTTP client library for Telegram Bot API interaction: http.client or requests. Option to install the library without dependencies.
  • The library is fully documented in docstrings.

Table of Contents

Installation

Requires Python version 3.12+, 3.13 recommended.

Installation without dependencies:

pip install markup-tg-logger

Installation with requests library:

pip install markup-tg-logger[requests]

Import:

import markup_tg_logger

Quick Start

Basic knowledge of Python's standard logging library is recommended to work with this library.
Python logging Docs

Minimal Example

TelegramHandler sends messages to a chat or channel via a Telegram bot. At minimum, you only need to provide the bot token and the chat ID where logs will be sent.

import logging

from markup_tg_logger import TelegramHandler


handler = TelegramHandler(
    bot_token = 'bot_token',
    chat_id = 12345,
)
handler.setLevel(logging.INFO)

logger = logging.getLogger('example')
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)

logger.info('This message sended via markup-tg-logger')

You can also specify multiple recipients in chat_id, for example: chat_id = {12345, '@logchannel'}.

For private messages, chat_id is the same as user_id, which can be obtained through the Bot API. For instance, you can use one of the currently available bots at the time of writing:
Show Json Bot.

Using HTML Markup

To control markup, configure and set the appropriate formatter in the handler. For most tasks, HtmlFormatter is sufficient. In the following example, HTML is used in the fmt string.

from markup_tg_logger import HtmlFormatter

FMT = (
    '<b>{levelname}</b>\n\n'
    '<u>{asctime}</u>\n\n'
    '<i>{message}</i>\n\n'
    '(Line: {lineno} [<code>{pathname}</code>])'
)

formatter = HtmlFormatter(
    fmt = FMT,
    datefmt = '%d-%m-%Y %H:%M:%S',
    style = '{',
)

For more details about HTML in Telegram, read Bot API documentation.

Escaping

By default, message text is escaped. If you want to use markup directly when calling the logger (logger.info('<code>example</code>')), set the escape_message=False parameter in the constructor of HtmlFormatter.

It's recommended to keep escape_stack_info and escape_exception parameters enabled, as stack and traceback outputs typically contain < and > characters by default.

Templates

You can also wrap stack and/or traceback outputs in HTML templates. For this, specify stack_info_template and exception_template parameters with a string like <tag>{text}</tag>.

Commonly used templates include:

  • Monospace font: <code>{text}</code>
  • Syntax-highlighted code block: <pre><code class="language-python">{text}</code></pre>

For convenience, you can import ready-made templates for Python and Bash code blocks:

from markup_tg_logger.defaults import HTML_PYTHON_TEMPLATE, HTML_BASH_TEMPLATE

Alternatively, you can wrap the entire logger output in an HTML template using the result_template parameter. In this case, the formatter will fully format the text (similar to standard logging.Formatter), including stack and traceback outputs, and then insert the formatted text into the specified template. It's recommended to enable escape_result and disable other escape_* flags in this scenario.

Level Names

Telegram allows using emojis in messages, so by default level names are replaced with colored emojis for better visual distinction between messages.

⬛️ - DEBUG
⬜️ - INFO
🟨 - WARNING
🟥 - ERROR
🟪 - CRITICAL

You can override level names using the level_names parameter:

formatter = HtmlFormatter(
    ...
    level_names = {logging.DEBUG: '⬛⬛️⬛️', logging.INFO: '⬜️⬜️⬜️'}
)

Levels not explicitly specified in the dictionary will keep their default names from the original library. To reset all names to default, specify level_names = {}.

Binding with Handler

After configuring the formatter, set it to the TelegramHandler instance:

...
handler.setFormatter(formatter)

Notification Settings

You can enable or disable message notifications when creating the handler. This parameter is taken directly from the Telegram Bot API. Notifications are enabled by default.

handler = TelegramHandler(
    bot_token = 'bot_token',
    chat_id = 12345,
    disable_notification = True,
)

For more flexible configuration, you can pass an implementation of the INotifier interface to disable_notification to control notifications. The library includes a ready-made LevelNotifier class for enabling notifications when certain log levels are reached.

from markup_tg_logger import LevelNotifier

handler = TelegramHandler(
    ...
    disable_notification = LevelNotifier(logging.ERROR),
)

Splitting Long Texts

Telegram supports messages up to 4096 characters. Log entries may exceed this limit, especially with long tracebacks. By default, TelegramHandler is configured to automatically split long texts into multiple messages while preserving markup in each message.

To override this behavior, pass a reconfigured splitter factory to message_splitter_factory.

from markup_tg_logger import MessageSplitterFactory
from markup_tg_logger.interfaces import IMessageSplitter

class CustomHtmlSplitter(IMessageSplitter):
    ...

handler = TelegramHandler(
    ...
    message_splitter_factory = MessageSplitterFactory({'HTML': CustomHtmlSplitter})
)

API Adapters

You can choose an HTTP client for interacting with Telegram Bot API to better integrate with your project's dependencies, or implement your own ITelegramSender.

Currently, the library includes implementations using:

  • Python's standard http.client
  • The popular third-party requests library

If the requests library is installed, TelegramHandler will use it by default automatically.

For more control, you can pass a sender instance to the sender parameter when creating TelegramHandler.

from markup_tg_logger import TelegramHandler
from markup_tg_logger.telegram_senders.http_client import HttpClientTelegramSender


handler = TelegramHandler(
    ...
    sender = HttpClientTelegramSender(),
)

If you need to proxy requests to the Telegram Bot API, you can specify a custom base_url in the sender constructor:

sender = HttpClientTelegramSender(base_url='https://api.telegram.yourproxy.com')

Configuration

The library supports configuration using the standard logging.config module and based on user-defined objects via dictConfig function.

Using python dictionary

For starters, you can use the configuration dictionary defined in your code and import library objects directly.

import logging
import markup_tg_logger

config = {
    'version': 1,
    'handlers': {
        'telegram': {
            '()': markup_tg_logger.TelegramHandler,
            'level': 'DEBUG',
            'formatter': 'telegram',
            'bot_token': 'bot_token',
            'chat_id': 12345,
        },
    },
    'loggers': {
        'example': {},
    },
    'root': {
        'level': 'DEBUG',
        'handlers': ['telegram'],
    },
}

dictConfigClass(config).configure()
logger = logging.getLogger('example')
logger.info('This message sended via markup-tg-logger')

User-defined objects format defines the '()' special key, which is used to specify the custom class. The remaining dictionary keys will be passed to the constructor of the specified class. You can use other library objects in the same way as in the TelegramHandler class constructor. For exmaple, you can specify 'disable_notification': LevelNotifier(logging.ERROR).

Formatters can be used in the same way:

config = {
    'formatters': {
        'telegram': {
            '()': markup_tg_logger.HtmlFormatter,
            'fmt': '<b>{levelname}</b> <code>{message}</code>',
            'style': '{',
            ...
        },
    },
    'handlers': {
        'telegram': {
            '()': markup_tg_logger.TelegramHandler,
            'formatter': 'telegram', # insert name of your formatter from `formatters` section
            ...
        },
    },
    ...
}

Using JSON or YAML configuration file

In configuration file you can't use imported library objects directly, but you can specify a import path as a string:

{
    "handlers": {
        "telegram": {
            "()": "markup_tg_logger.TelegramHandler",
            "level": "DEBUG",
            "bot_token": "bot_token",
            "chat_id": 12345,
        }
    },
    ...
}

Load the configuration as a dictionary and use it as in the previous example:

import json

with open('config.json', 'r') as f:
    config = json.load(f)

dictConfigClass(config).configure()
logger = logging.getLogger('example')
logger.info('This message sended via markup-tg-logger')

To pass notifiers and other classes in TelegramHandler arguments the library provides the ability to use similar dictionaries with "()" keys:

{
    "handlers": {
        "telegram": {
            "()": "markup_tg_logger.TelegramHandler",
            "level": "DEBUG",
            "bot_token": "bot_token",
            "chat_id": 12345,
            "disable_notification": {
                "()": "markup_tg_logger.LevelNotifier",
                "level": "ERROR",
            },
            "sender": {
                "()": "markup_tg_logger.telegram_senders.http_client.HttpClientTelegramSender",
            },
            "message_splitter_factory": {
                "": {"()": "markup_tg_logger.BaseSplitter"},
                "HTML": {"()": "markup_tg_logger.HtmlSplitter"},
            },
        },
    },
    ...
}

You can also pass your custom classes in the same way. Specified by "()" key value will be imported using standard logging import resolver.

Note: this implementation does not support the special "." keys due to lack of need, but you can implement custom factory instead if needed.

Code of the given examples

Code for the examples shown

To run, set BOT_TOKEN and CHAT_ID environment variables.

Documentation

The library is fully documented in docstrings.

Structure

markup_tg_logger/

  • formatters/ - Classes based on logging.Formatter.
  • interfaces/ - Library interfaces for implementing custom classes.
  • message_splitters/ - Classes that split text with markup into messages that do not exceeda given length.
  • notifiers/ - Classes that decide whether to turn on notifications in Telegram depending on the LogRecord parameters.
  • telegram_senders/ - Classes that interact with the Telegram API. Adapters for different HTTP libraries.
  • config.py - Immutable data for the library.
  • defaults.py - Some pre-configured values for class constructor parameters.
  • exceptions.py - All library exceptions.
  • handler.py - The central library class based on logging.Handler.
  • resolve_object_from_config.py - Utility function for working with user-defined objects.
  • types.py - Custom data types.

Markdown Support

The library provides a built-in implementation for working with HTML only, but also provides an interface-based API for implementing other markup languages.

For full Markdown support, MarkdownMessageSplitter should be implemented based on IMessageSplitter to split messages while preserving markup.

from markup_tg_logger import (
    TelegramHandler, EscapeMarkupFormatter,
    MessageSplitterFactory, BaseMessageSplitter, HtmlMessageSplitter,
)
from markup_tg_logger.interfaces import IMessageSplitter
from markup_tg_logger.types import ParseMode


class MarkdownMessageSplitter(IMessageSplitter):
    @property
    def parse_mode(self) -> ParseMode:
        return 'Markdown'
    
    def split(self, text: str) -> list[str]:
        ...

message_splitter_factory = MessageSplitterFactory(
    parse_mode_to_splitter = {
        '': BaseMessageSplitter(),
        'HTML': HtmlMessageSplitter(),
        'Markdown': MarkdownMessageSplitter(),
    }
)

formatter = EscapeMarkupFormatter(
    ...
    parse_mode = 'Markdown',
)

handler = TelegramHandler(
    ...
    message_splitter_factory = message_splitter_factory,
)
handler.setFormatter(formatter)

The BaseMarkupFormatter and EscapeMarkupFormatter classes are markup language independent and can be used for Markdown without modification.

If you know for sure that your messages will not exceed the Telegram character limit, you can immediately use Markdown in combination with BaseMessageSplitter.

message_splitter_factory = MessageSplitterFactory(
    parse_mode_to_splitter = {
        '': BaseMessageSplitter(),
        'HTML': HtmlMessageSplitter(),
        'Markdown': BaseMessageSplitter(),
    }
)

Testing

Installing dependencies for tests:

pip install -r requirements.txt

Running tests from the repository root:

pytest

Useful Links

Feedback

Developer: Andrey Korovyansky | andrey.korovyansky@gmail.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

markup_tg_logger-1.1.1.tar.gz (29.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

markup_tg_logger-1.1.1-py3-none-any.whl (31.2 kB view details)

Uploaded Python 3

File details

Details for the file markup_tg_logger-1.1.1.tar.gz.

File metadata

  • Download URL: markup_tg_logger-1.1.1.tar.gz
  • Upload date:
  • Size: 29.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for markup_tg_logger-1.1.1.tar.gz
Algorithm Hash digest
SHA256 d510cf4bb083864f5b631fe5982da1b181aa84f3ea382803958ebe4f9982a096
MD5 ba330e4f923a2b715659f7a2dc1458c8
BLAKE2b-256 da5458c0471d18362ea12595f8895abc09a765c4b6b7bb33d5e5b85bb18b3b17

See more details on using hashes here.

Provenance

The following attestation bundles were made for markup_tg_logger-1.1.1.tar.gz:

Publisher: release.yml on korandr/markup-tg-logger

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file markup_tg_logger-1.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for markup_tg_logger-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 56891d200341a6afaf079c56db8a6a12723e0709444fa16639501e39581b0971
MD5 80e31f9ad2f76c789007115317630d94
BLAKE2b-256 b23a6b1cbfe4d1770a02cd9c089c407b0d0ec69d66e626bd3b1490ef120de74c

See more details on using hashes here.

Provenance

The following attestation bundles were made for markup_tg_logger-1.1.1-py3-none-any.whl:

Publisher: release.yml on korandr/markup-tg-logger

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page