Passed
Push — main ( 4b9f06...da4e6c )
by Jochen
01:55
created

syslog2irc.syslog.RequestHandler.__init__()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nop 4
crap 1
1
"""
2
syslog2irc.syslog
3
~~~~~~~~~~~~~~~~~
4
5
BSD syslog message reception and handling
6
7
:Copyright: 2007-2021 Jochen Kupperschmidt
8
:License: MIT, see LICENSE for details.
9
"""
10
11 1
from functools import partial
12 1
import logging
13 1
from socketserver import BaseRequestHandler, ThreadingUDPServer
14 1
import sys
15 1
from typing import Iterable
16
17 1
import syslogmp
18 1
from syslogmp import Message as SyslogMessage
19
20 1
from .signals import syslog_message_received
21 1
from .util import start_thread
22
23
24 1
logger = logging.getLogger(__name__)
25
26
27 1
class RequestHandler(BaseRequestHandler):
28
    """Handler for syslog messages."""
29
30 1
    def __init__(self, port: int, *args, **kwargs) -> None:
31 1
        self.port = port
32 1
        super().__init__(*args, **kwargs)
33
34 1
    def handle(self) -> None:
35 1
        try:
36 1
            data = self.request[0]
37 1
            message = syslogmp.parse(data)
38
        except ValueError:
39
            logger.info(
40
                'Invalid message received from %s:%d.', *self.client_address
41
            )
42
            return None
43
44 1
        logger.debug(
45
            'Received message from %s:%d on port %d -> %s',
46
            self.client_address[0],
47
            self.client_address[1],
48
            self.port,
49
            format_message_for_log(message),
50
        )
51
52 1
        syslog_message_received.send(
53
            self.port, source_address=self.client_address, message=message
54
        )
55
56
57 1
def create_server(port: int) -> ThreadingUDPServer:
58
    """Create a threading UDP server to receive syslog messages."""
59
    address = ('', port)
60
    handler_class = partial(RequestHandler, port)
61
    return ThreadingUDPServer(address, handler_class)
62
63
64 1
def start_server(port: int) -> None:
65
    """Start a server, in a separate thread."""
66
    try:
67
        server = create_server(port)
68
    except OSError as e:
69
        sys.stderr.write(f'Error {e.errno:d}: {e.strerror}\n')
70
        sys.stderr.write(
71
            f'Probably no permission to open port {port:d}. '
72
            'Try to specify a port number above 1,024 (or even '
73
            '4,096) and up to 65,535.\n'
74
        )
75
        sys.exit(1)
76
77
    thread_name = f'{server.__class__.__name__}-port{port:d}'
78
    start_thread(server.serve_forever, thread_name)
79
    logger.info(
80
        'Listening for syslog messages on %s:%d.', *server.server_address
81
    )
82
83
84 1
def start_syslog_message_receivers(ports: Iterable[int]) -> None:
85
    """Start one syslog message receiving server for each port."""
86
    for port in ports:
87
        start_server(port)
88
89
90 1
def format_message_for_log(message: SyslogMessage) -> str:
91
    """Format a syslog message to be logged."""
92 1
    return (
93
        f'facility={message.facility.name}, '
94
        f'severity={message.severity.name}, '
95
        f'timestamp={message.timestamp.isoformat()}, '
96
        f'hostname={message.hostname}, '
97
        f'message={message.message}'
98
    )
99