Passed
Push — main ( da4e6c...a03b0b )
by Jochen
01:53
created

syslog2irc.processor   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 119
Duplicated Lines 0 %

Test Coverage

Coverage 69.39%

Importance

Changes 0
Metric Value
eloc 72
dl 0
loc 119
rs 10
c 0
b 0
f 0
ccs 34
cts 49
cp 0.6939
wmc 13

5 Methods

Rating   Name   Duplication   Size   Complexity  
A Processor.handle_message() 0 12 3
A Processor.__init__() 0 14 2
A Processor.handle_syslog_message() 0 17 1
A Processor.connect_to_signals() 0 4 1
A Processor.run() 0 9 3

1 Function

Rating   Name   Duplication   Size   Complexity  
A format_syslog_message() 0 25 3
1
"""
2
syslog2irc.processor
3
~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2007-2021 Jochen Kupperschmidt
6
:License: MIT, see LICENSE for details.
7
"""
8
9 1
import logging
10 1
from time import sleep
11 1
from typing import Any, Callable, Iterator, Optional, Set, Tuple
12
13 1
from syslogmp import Message as SyslogMessage
14
15 1
from .router import Router
16 1
from .signals import (
17
    irc_channel_joined,
18
    message_approved,
19
    message_received,
20
    syslog_message_received,
21
)
22
23
24 1
MESSAGE_TEXT_ENCODING = 'utf-8'
25
26
27 1
logger = logging.getLogger(__name__)
28
29
30 1
class Processor:
31 1
    def __init__(
32
        self,
33
        router: Router,
34
        syslog_message_formatter: Optional[
35
            Callable[[SyslogMessage], str]
36
        ] = None,
37
    ) -> None:
38 1
        super(Processor, self).__init__()
39 1
        self.router = router
40
41 1
        if syslog_message_formatter is not None:
42
            self.syslog_message_formatter = syslog_message_formatter
43
        else:
44 1
            self.syslog_message_formatter = format_syslog_message
45
46 1
    def connect_to_signals(self) -> None:
47 1
        irc_channel_joined.connect(self.router.enable_channel)
48 1
        syslog_message_received.connect(self.handle_syslog_message)
49 1
        message_received.connect(self.handle_message)
50
51 1
    def handle_syslog_message(
52
        self,
53
        port: int,
54
        source_address: Optional[Tuple[str, int]] = None,
55
        message: Optional[SyslogMessage] = None,
56
    ) -> None:
57
        """Process an incoming syslog message."""
58
        channel_names = self.router.get_channel_names_for_port(port)
59
60
        formatted_source = f'{source_address[0]}:{source_address[1]:d}'
61
        formatted_message = self.syslog_message_formatter(message)
62
        text = f'{formatted_source} {formatted_message}'
63
64
        message_received.send(
65
            channel_names=channel_names,
66
            text=text,
67
            source_address=source_address,
68
        )
69
70 1
    def handle_message(
71
        self,
72
        sender: Any,
73
        *,
74
        channel_names: Optional[Set[str]] = None,
75
        text: Optional[str] = None,
76
        source_address: Optional[Tuple[str, int]] = None,
77
    ) -> None:
78
        """Process an incoming message."""
79
        for channel_name in channel_names:
80
            if self.router.is_channel_enabled(channel_name):
81
                message_approved.send(channel_name=channel_name, text=text)
82
83 1
    def run(self, seconds_to_sleep: float = 0.5) -> None:
84
        """Run the main loop."""
85
        try:
86
            while True:
87
                sleep(seconds_to_sleep)
88
        except KeyboardInterrupt:
89
            pass
90
91
        logger.info('Shutting down ...')
92
93
94 1
def format_syslog_message(message: SyslogMessage) -> str:
95
    """Format a syslog message to be displayed on IRC."""
96
97 1
    def _generate() -> Iterator[str]:
98 1
        if message.timestamp is not None:
99 1
            timestamp_format = '%Y-%m-%d %H:%M:%S'
100 1
            formatted_timestamp = message.timestamp.strftime(timestamp_format)
101 1
            yield f'[{formatted_timestamp}] '
102
103 1
        if message.hostname is not None:
104 1
            yield f'({message.hostname}) '
105
106 1
        severity_name = message.severity.name
107
108
        # Important: The message text is a byte string.
109 1
        message_text = message.message.decode(MESSAGE_TEXT_ENCODING)
110
111
        # Remove leading and trailing newlines. Those would result in
112
        # additional lines on IRC with the usual metadata but with an
113
        # empty message text.
114 1
        message_text = message_text.strip('\n')
115
116 1
        yield f'[{severity_name}]: {message_text}'
117
118
    return ''.join(_generate())
119