Skip to content

Commit

Permalink
Merge pull request #6 from guyshe/release/1.4
Browse files Browse the repository at this point in the history
Release/1.4
  • Loading branch information
guyshe authored Jan 21, 2022
2 parents e288c24 + 242ef90 commit f133c18
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 114 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ venv/
.idea/
*.sqlite
*.egg-info
build/
dist/
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# telegram_handler
Logging handler that sends log messages directly to either a telegram channel or chat
Telegram logging handler for logging library in python.

Telegram log handler sends log messages directly to either a telegram channel or chat for your choice

## Motivation
Tracking program execution state remotely - directly from your telegram account
Expand All @@ -12,14 +14,14 @@ Basic usage example:
```python
import logging

from telegram_handler import AsyncTelegramHandler
from telegram_handler import TelegramLoggingHandler

BOT_TOKEN = '1612485124:AAFW9JXxjqY9d-XayMKh8Q4-_iyHkXSw3N8'
CHANNEL_NAME = 'example_channel_logger'


def main():
telegram_log_handler = AsyncTelegramHandler(BOT_TOKEN, CHANNEL_NAME)
telegram_log_handler = TelegramLoggingHandler(BOT_TOKEN, CHANNEL_NAME)
my_logger = logging.getLogger('My-Logger')
my_logger.setLevel(logging.INFO)
my_logger.addHandler(logging.StreamHandler())
Expand All @@ -37,14 +39,14 @@ Another option is to add the handler to the root logger:
```python
import logging

from telegram_handler import AsyncTelegramHandler
from telegram_handler import TelegramLoggingHandler

BOT_TOKEN = '1612485124:AAFW9JXxjqY9d-XayMKh8Q4-_iyHkXSw3N8'
CHANNEL_NAME = 'example_channel_logger'


def main():
telegram_log_handler = AsyncTelegramHandler(BOT_TOKEN, CHANNEL_NAME)
telegram_log_handler = TelegramLoggingHandler(BOT_TOKEN, CHANNEL_NAME)
logging.basicConfig(
handlers = [
telegram_log_handler
Expand Down Expand Up @@ -73,11 +75,7 @@ In order to use the package you should:
[here](https://www.logaster.com/blog/how-create-telegram-channel/).

## How to use?
### There are 2 possibilities:
- Use `AsyncTelegramHandler` and send messages from a different thread (__recommended__)
- Use `BlockingTelegramHandler` and send messages from the same thread.
:warning: __warning__: sometimes the logger should wait for a cooldown (telegram constrains),
using this handler will block the entire program
- Use `TelegramLoggingHandler` and send messages from a different thread (__recommended__)

### Parameters:
- `bot_token` - The token that returns from the `BotFather` when creating the bot.
Expand All @@ -86,4 +84,4 @@ In order to use the package you should:
- Channel name is the `chat id` for public channels.
So for the __public channel__ `example_channel_logger` the `chat id` will be `example_channel_logger`
- The `channel_name` can be any `chat id`, you can see how to obtain chat id
[here](http://techblog.sillifish.co.uk/2020/03/30/telegram-chat-id-and-token-id/).
[here](http://techblog.sillifish.co.uk/2020/03/30/telegram-chat-id-and-token-id/).
22 changes: 9 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@
with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
long_description = f.read()

setup(
name='telegram-handler',
version='1.3.1',
description='async telegram handler',
author='Guyshe',
url='https://github.com/guyshe/telegram_handler',
long_description=long_description,
long_description_content_type='text/markdown',
packages=setuptools.find_packages(),
install_requires=[
'requests'
]
)
setup(name='telegram-handler',
version='1.4',
description='async telegram handler',
author='Guyshe',
url='https://github.com/guyshe/telegram_handler',
long_description=long_description,
long_description_content_type='text/markdown',
packages=setuptools.find_packages(),
install_requires=['requests~=2.27.0', 'retry~=0.9.0'])
3 changes: 1 addition & 2 deletions telegram_handler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from telegram_handler.blocking_telegram_handler import BlockingTelegramHandler
from telegram_handler.async_telegram_handler import AsyncTelegramHandler
from telegram_handler.handler import TelegramLoggingHandler
38 changes: 0 additions & 38 deletions telegram_handler/async_telegram_handler.py

This file was deleted.

29 changes: 0 additions & 29 deletions telegram_handler/blocking_telegram_handler.py

This file was deleted.

12 changes: 8 additions & 4 deletions telegram_handler/consts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
FLUSH_INTERVAL = 0.5

API_HOST = 'api.telegram.org'
API_URL = f'https://{API_HOST}/bot{{bot_token}}/sendMessage?' \
f'chat_id=@{{channel_name}}&text={{message}}'
NEED_COOLDOWN_STATUS = 429
COOLDOWN_TIME = 1
RETRY_ATTEMPTS = 6
f'chat_id=@{{channel_name}}&parse_mode=HTML&text={{message}}'
RETRY_COOLDOWN_TIME = 3
MAX_RETRYS = 20
# max valid size is 2048, we take buffer to be on the safe side
MAX_MESSAGE_SIZE = 1900
MAX_TELEGRAM_MESSAGES = 300
17 changes: 0 additions & 17 deletions telegram_handler/functools_extension.py

This file was deleted.

77 changes: 77 additions & 0 deletions telegram_handler/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from distutils.log import Log
import logging
from time import sleep
from threading import Thread, RLock
from queue import Empty, Queue
import requests
from retry import retry
from telegram_handler.consts import API_URL, RETRY_COOLDOWN_TIME, MAX_RETRYS, MAX_MESSAGE_SIZE, FLUSH_INTERVAL

logger = logging.getLogger(__name__)


class TelegramLoggingHandler(logging.Handler):

_sentinel = None

def __init__(self, bot_token, channel_name, level=logging.NOTSET):
super().__init__(level)
self.bot_token = bot_token
self.channel_name = channel_name
self._buffer_lock = RLock()
self._buffer = ''
self.telegram_messages_queue = Queue()
self._writer_thread = None
self._start_writer_thread()

@retry((requests.RequestException),
tries=MAX_RETRYS,
delay=RETRY_COOLDOWN_TIME,
logger=logger)
def write(self, message):
url = API_URL.format(bot_token=self.bot_token,
channel_name=self.channel_name,
message=message)
requests.get(url).raise_for_status()

def emit(self, record: logging.LogRecord) -> None:
message = self.format(record)
self._buffer_lock.acquire()
new_buffer = f'{self._buffer}\n{message}'
if len(new_buffer) > MAX_MESSAGE_SIZE:
self._flush_buffer()
new_buffer = message[:MAX_MESSAGE_SIZE]
self._buffer = new_buffer
self._buffer_lock.release()

def close(self):
self._flush_buffer()
self.telegram_messages_queue.put(TelegramLoggingHandler._sentinel)
self.telegram_messages_queue.join()
self._writer_thread.join()

def _flush_buffer(self):
self._buffer_lock.acquire()
# Avoid unnecessary flushing
if self._buffer != '':
self.telegram_messages_queue.put(self._buffer[:MAX_MESSAGE_SIZE])
self._buffer = ''
self._buffer_lock.release()

def _write_manager(self):
q = self.telegram_messages_queue
while True:
try:
message = q.get(timeout=FLUSH_INTERVAL)
if message is self._sentinel:
q.task_done()
break
self.write(message)
q.task_done()
except Empty:
self._flush_buffer()

def _start_writer_thread(self):
self._writer_thread = Thread(target=self._write_manager)
self._writer_thread.daemon = True
self._writer_thread.start()

0 comments on commit f133c18

Please sign in to comment.