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,
fmtstrings, 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.clientorrequests. 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
requestslibrary
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
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 onlogging.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 theLogRecordparameters.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 onlogging.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
- Python logging Docs
- Telegram - Create Bot
- Telegram Bot API - sendMessage
- Telegram Bot API - HTML
- Show Json Bot - Get user_id
Feedback
Developer: Andrey Korovyansky | andrey.korovyansky@gmail.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d510cf4bb083864f5b631fe5982da1b181aa84f3ea382803958ebe4f9982a096
|
|
| MD5 |
ba330e4f923a2b715659f7a2dc1458c8
|
|
| BLAKE2b-256 |
da5458c0471d18362ea12595f8895abc09a765c4b6b7bb33d5e5b85bb18b3b17
|
Provenance
The following attestation bundles were made for markup_tg_logger-1.1.1.tar.gz:
Publisher:
release.yml on korandr/markup-tg-logger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
markup_tg_logger-1.1.1.tar.gz -
Subject digest:
d510cf4bb083864f5b631fe5982da1b181aa84f3ea382803958ebe4f9982a096 - Sigstore transparency entry: 1282203182
- Sigstore integration time:
-
Permalink:
korandr/markup-tg-logger@188626d54ca2587844cff1917d62a316d792bba5 -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/korandr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@188626d54ca2587844cff1917d62a316d792bba5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file markup_tg_logger-1.1.1-py3-none-any.whl.
File metadata
- Download URL: markup_tg_logger-1.1.1-py3-none-any.whl
- Upload date:
- Size: 31.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56891d200341a6afaf079c56db8a6a12723e0709444fa16639501e39581b0971
|
|
| MD5 |
80e31f9ad2f76c789007115317630d94
|
|
| BLAKE2b-256 |
b23a6b1cbfe4d1770a02cd9c089c407b0d0ec69d66e626bd3b1490ef120de74c
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
markup_tg_logger-1.1.1-py3-none-any.whl -
Subject digest:
56891d200341a6afaf079c56db8a6a12723e0709444fa16639501e39581b0971 - Sigstore transparency entry: 1282203199
- Sigstore integration time:
-
Permalink:
korandr/markup-tg-logger@188626d54ca2587844cff1917d62a316d792bba5 -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/korandr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@188626d54ca2587844cff1917d62a316d792bba5 -
Trigger Event:
push
-
Statement type: