Passed
Push — main ( fedced...62aa48 )
by Jochen
01:57
created

syslog2irc.syslog.RequestHandler.handle()   A

Complexity

Conditions 2

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.1481

Importance

Changes 0
Metric Value
cc 2
eloc 16
nop 1
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
ccs 6
cts 9
cp 0.6667
crap 2.1481
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
    facility_name = message.facility.name
93 1
    severity_name = message.severity.name
94 1
    timestamp_str = message.timestamp.isoformat()
95 1
    hostname = message.hostname
96
97 1
    return (
98
        f'facility={facility_name}, '
99
        f'severity={severity_name}, '
100
        f'timestamp={timestamp_str}, '
101
        f'hostname={hostname}, '
102
        f'message={message.message}'
103
    )
104