Passed
Push — master ( 21e60a...651cb4 )
by Daniil
03:38
created

logger.TelegramHandler.emit()   A

Complexity

Conditions 3

Size

Total Lines 15
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 15
rs 9.65
c 0
b 0
f 0
cc 3
nop 2
1
"""
2
3
Info about logging levels:
4
5
DEBUG: Detailed information, typically of interest only when diagnosing problems.
6
7
INFO: Confirmation that things are working as expected.
8
9
WARNING: An indication that something unexpected happened, or indicative of some
10
problem in the near future (e.g. ‘disk space low’). The software is still working
11
as expected.
12
13
ERROR: Due to a more serious problem, the software has not been able to perform
14
some function.
15
16
CRITICAL: A serious error, indicating that the program itself may be unable to
17
continue running.
18
19
"""
20
21
import logging
22
import os
23
import sys
24
import traceback
25
from datetime import datetime
26
from datetime import timedelta
27
from datetime import timezone
28
from logging import Formatter
29
from logging import Handler
30
from logging import LogRecord
31
32
import requests
33
34
35
def handle_exception(exc_type, exc_value, exc_traceback):
36
    if issubclass(exc_type, KeyboardInterrupt):
37
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
38
        return
39
    logger = Logger().init()
40
    logger.critical("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
41
42
43
class TelegramHandler(Handler):
44
    def emit(self, record: LogRecord) -> None:
45
        log_entry = self.format(record)
46
        token = os.environ["TG_TOKEN"]
47
        chat_ids = os.environ["TG_CHATS"].split(",")
48
        notifications = False
49
        if record.levelno < 30:
50
            notifications = True
51
        for chat in chat_ids:
52
            requests.get(
53
                f"https://api.telegram.org/bot{token}/sendMessage",
54
                params={
55
                    "chat_id": chat,
56
                    "text": log_entry,
57
                    "parse_mode": "markdown",
58
                    "disable_notification": notifications,
59
                },
60
            )
61
62
63 View Code Duplication
class BaseFormatter(Formatter):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
64
    def format(self, record: LogRecord) -> str:
65
        message = record.msg
66
        levelname = record.levelname
67
        module = record.module
68
        timestamp = datetime.utcfromtimestamp(record.created)
69
        ts = (
70
            timestamp.replace(tzinfo=timezone.utc)
71
            .astimezone(tz=timezone(timedelta(hours=3)))
72
            .strftime("%d.%m.%Y %H:%M:%S")
73
        )
74
        fmt = f"[{levelname}] ({module}): {ts} {message}"
75
        if record.exc_info:
76
            fmt += f"\n{''.join(traceback.format_exception(*record.exc_info))}"
77
        return fmt
78
79
80 View Code Duplication
class MarkdownFormatter(Formatter):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
81
    def format(self, record: LogRecord) -> str:
82
        message = record.msg
83
        levelname = record.levelname
84
        module = record.module
85
        timestamp = datetime.utcfromtimestamp(record.created)
86
        ts = (
87
            timestamp.replace(tzinfo=timezone.utc)
88
            .astimezone(tz=timezone(timedelta(hours=3)))
89
            .strftime("%d.%m.%Y %H:%M:%S")
90
        )
91
        fmt = f"*[{levelname}]* ({module}): {ts} {message}"
92
        if record.exc_info:
93
            fmt += f"\n```{''.join(traceback.format_exception(*record.exc_info))}```"
94
        return fmt
95
96
97
class Logger:
98
    def __init__(self):
99
        self.logger = logging.getLogger(__name__)
100
        if self.logger.hasHandlers():
101
            self.logger.handlers.clear()
102
103
        sys.excepthook = handle_exception
104
105
    def init(self):
106
        self.logger.setLevel("INFO")
107
        console = logging.StreamHandler()
108
        base_formatter = BaseFormatter()
109
        console.setFormatter(base_formatter)
110
        self.logger.addHandler(console)
111
        if "PRODUCTION" in os.environ:
112
            tg = TelegramHandler()
113
            md_formatter = MarkdownFormatter()
114
            tg.setFormatter(md_formatter)
115
            self.logger.addHandler(tg)
116
        return self.logger
117