Merge ~blake-rouse/maas:fix-1801389 into maas:master

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: 4e15fb66122a591d6dea99e42adfaad52917fdc7
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~blake-rouse/maas:fix-1801389
Merge into: maas:master
Diff against target: 128 lines (+79/-2)
3 files modified
src/provisioningserver/logger/__init__.py (+5/-1)
src/provisioningserver/logger/_logging.py (+1/-1)
src/provisioningserver/logger/_maaslog.py (+73/-0)
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+358935@code.launchpad.net

Commit message

Fixes LP: #1801389 - Queue messages to syslog until the logging handler can actually connect.

Description of the change

I spent too much time trying to unit test this handler. Upstream python tests it against a running syslog if available, but this would dirty the system running the unit tests so I didn't like that approach.

I could setup a syslog just to test this one test, but that just seems way overkill for something as simple as queue messages.

I have tested this running and it works correctly, no more stack trace and the messages get queued and wrote to the socket.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Code looks good; good comments. Just a few grammar nits below.

review: Approve
~blake-rouse/maas:fix-1801389 updated
4e15fb6... by Blake Rouse

Fix comments.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Fixed the comments, thanks for the review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/provisioningserver/logger/__init__.py b/src/provisioningserver/logger/__init__.py
2index ceeaf19..2c109e2 100644
3--- a/src/provisioningserver/logger/__init__.py
4+++ b/src/provisioningserver/logger/__init__.py
5@@ -54,6 +54,7 @@ __all__ = [
6 "get_maas_logger",
7 "LegacyLogger",
8 "LoggingMode",
9+ "MAASSysLogHandler",
10 "set_verbosity",
11 "VerbosityOptions",
12 ]
13@@ -68,7 +69,10 @@ from provisioningserver.logger._logging import (
14 configure_standard_logging,
15 set_standard_verbosity,
16 )
17-from provisioningserver.logger._maaslog import get_maas_logger
18+from provisioningserver.logger._maaslog import (
19+ get_maas_logger,
20+ MAASSysLogHandler,
21+)
22 from provisioningserver.logger._tftp import configure_tftp_logging
23 from provisioningserver.logger._twisted import (
24 configure_twisted_logging,
25diff --git a/src/provisioningserver/logger/_logging.py b/src/provisioningserver/logger/_logging.py
26index 96bca1e..b134650 100644
27--- a/src/provisioningserver/logger/_logging.py
28+++ b/src/provisioningserver/logger/_logging.py
29@@ -110,7 +110,7 @@ def get_logging_config(verbosity: int):
30 'formatter': 'stdout',
31 },
32 'syslog': {
33- 'class': 'logging.handlers.SysLogHandler',
34+ 'class': 'provisioningserver.logger.MAASSysLogHandler',
35 'facility': logging.handlers.SysLogHandler.LOG_DAEMON,
36 'address': get_syslog_address_path(),
37 'formatter': 'syslog',
38diff --git a/src/provisioningserver/logger/_maaslog.py b/src/provisioningserver/logger/_maaslog.py
39index 5630ae5..89dd064 100644
40--- a/src/provisioningserver/logger/_maaslog.py
41+++ b/src/provisioningserver/logger/_maaslog.py
42@@ -7,7 +7,10 @@ __all__ = [
43 "get_maas_logger",
44 ]
45
46+from collections import deque
47 import logging
48+import logging.handlers
49+import socket
50
51
52 class MAASLogger(logging.getLoggerClass()):
53@@ -19,6 +22,76 @@ class MAASLogger(logging.getLoggerClass()):
54 "Django logger instead")
55
56
57+class MAASSysLogHandler(logging.handlers.SysLogHandler):
58+ """A syslog handler that queuess messages when connection to the socket
59+ cannot be performed.
60+
61+ Once the connection is made the queue is flushed to the socket.
62+ """
63+
64+ def __init__(self, *args, **kwargs):
65+ # `queue` must be set before `super().__init__`, because
66+ # `_connect_unixsocket` is called inside.
67+ self.queue = deque()
68+ super().__init__(*args, **kwargs)
69+
70+ def _connect_unixsocket(self, address):
71+ super()._connect_unixsocket(address)
72+ # Successfully connected to the socket. Write any queued messages.
73+ while len(self.queue) > 0:
74+ msg = self.queue.popleft()
75+ try:
76+ self.socket.send(msg)
77+ except OSError:
78+ self.queue.appendleft(msg)
79+ raise
80+
81+ def emit(self, record):
82+ """
83+ Emit a record.
84+
85+ The record is formatted, and then sent to the syslog server. If
86+ exception information is present, it is NOT sent to the server.
87+
88+ Note: This is copied directly from the python3 source code, only the
89+ modification of appending to the queue was added.
90+ """
91+ try:
92+ msg = self.format(record)
93+ if self.ident:
94+ msg = self.ident + msg
95+ if self.append_nul:
96+ msg += '\000'
97+
98+ # We need to convert record level to lowercase, maybe this will
99+ # change in the future.
100+ prio = '<%d>' % self.encodePriority(
101+ self.facility, self.mapPriority(record.levelname))
102+ prio = prio.encode('utf-8')
103+ # Message is a string. Convert to bytes as required by RFC 5424
104+ msg = msg.encode('utf-8')
105+ msg = prio + msg
106+ if self.unixsocket:
107+ try:
108+ self.socket.send(msg)
109+ except OSError:
110+ self.socket.close()
111+ try:
112+ self._connect_unixsocket(self.address)
113+ except OSError:
114+ # Queue the message to send when the connection
115+ # is finally made to the socket.
116+ self.queue.append(msg)
117+ return
118+ self.socket.send(msg)
119+ elif self.socktype == socket.SOCK_DGRAM:
120+ self.socket.sendto(msg, self.address)
121+ else:
122+ self.socket.sendall(msg)
123+ except Exception:
124+ self.handleError(record)
125+
126+
127 def get_maas_logger(syslog_tag=None):
128 """Return a MAAS logger that will log to syslog.
129

Subscribers

People subscribed via source and target branches