Merge branch 'upstream-update_1.4.2-1' into 'master'
New upstream release 1.4.2-1
See merge request debian/logbook!21
Agustin Henze
4 years ago
0 | 0 | Logbook Changelog |
1 | 1 | ================= |
2 | ||
3 | Not yet released | |
4 | ||
5 | - Use correct record delimiters (null for UNIX, newline for network) in SyslogHandler (thanks Jonathan Kamens) | |
6 | - Try to reconnect to SyslogHandler TCP sockets when they are disconnected (thanks Jonathan Kamens) | |
7 | - Use RFC 5424 format for networking logging in SyslogHandler (thanks Jonathan Kamens) | |
2 | 8 | |
3 | 9 | Here you can see the full list of changes between each Logbook release. |
4 | 10 |
0 | logbook (1.4.2-1) UNRELEASED; urgency=medium | |
1 | ||
2 | * New upstream version 1.4.2 | |
3 | ||
4 | -- Downstreamer <salsa-ci-team@debian.org> Thu, 20 Dec 2018 18:20:07 +0000 | |
5 | ||
0 | 6 | logbook (1.4.1-1) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Ondřej Nový ] |
497 | 497 | |
498 | 498 | if (not suppression_count and |
499 | 499 | len(self._record_limits) >= self.max_record_cache): |
500 | cache_items = self._record_limits.items() | |
501 | cache_items.sort() | |
500 | cache_items = sorted(self._record_limits.items()) | |
502 | 501 | del cache_items[:int(self._record_limits) |
503 | 502 | * self.record_cache_prune] |
504 | 503 | self._record_limits = dict(cache_items) |
1534 | 1533 | def __init__(self, application_name=None, address=None, |
1535 | 1534 | facility='user', socktype=socket.SOCK_DGRAM, |
1536 | 1535 | level=NOTSET, format_string=None, filter=None, |
1537 | bubble=False): | |
1536 | bubble=False, record_delimiter=None): | |
1538 | 1537 | Handler.__init__(self, level, filter, bubble) |
1539 | 1538 | StringFormatterHandlerMixin.__init__(self, format_string) |
1540 | 1539 | self.application_name = application_name |
1545 | 1544 | else: |
1546 | 1545 | address = '/dev/log' |
1547 | 1546 | |
1548 | self.address = address | |
1547 | self.remote_address = self.address = address | |
1549 | 1548 | self.facility = facility |
1550 | 1549 | self.socktype = socktype |
1551 | 1550 | |
1552 | 1551 | if isinstance(address, string_types): |
1553 | 1552 | self._connect_unixsocket() |
1553 | self.enveloper = self.unix_envelope | |
1554 | default_delimiter = u'\x00' | |
1554 | 1555 | else: |
1555 | 1556 | self._connect_netsocket() |
1557 | self.enveloper = self.net_envelope | |
1558 | default_delimiter = u'\n' | |
1559 | ||
1560 | self.record_delimiter = default_delimiter \ | |
1561 | if record_delimiter is None else record_delimiter | |
1562 | ||
1563 | self.connection_exception = getattr( | |
1564 | __builtins__, 'BrokenPipeError', socket.error) | |
1556 | 1565 | |
1557 | 1566 | def _connect_unixsocket(self): |
1558 | 1567 | self.unixsocket = True |
1568 | 1577 | self.unixsocket = False |
1569 | 1578 | self.socket = socket.socket(socket.AF_INET, self.socktype) |
1570 | 1579 | if self.socktype == socket.SOCK_STREAM: |
1571 | self.socket.connect(self.address) | |
1580 | self.socket.connect(self.remote_address) | |
1572 | 1581 | self.address = self.socket.getsockname() |
1573 | 1582 | |
1574 | 1583 | def encode_priority(self, record): |
1577 | 1586 | self.LOG_WARNING) |
1578 | 1587 | return (facility << 3) | priority |
1579 | 1588 | |
1589 | def wrap_segments(self, record, before): | |
1590 | msg = self.format(record) | |
1591 | segments = [segment for segment in msg.split(self.record_delimiter)] | |
1592 | return (before + segment + self.record_delimiter | |
1593 | for segment in segments) | |
1594 | ||
1595 | def unix_envelope(self, record): | |
1596 | before = u'<{}>{}'.format( | |
1597 | self.encode_priority(record), | |
1598 | self.application_name + ':' if self.application_name else '') | |
1599 | return self.wrap_segments(record, before) | |
1600 | ||
1601 | def net_envelope(self, record): | |
1602 | # Gross but effective | |
1603 | try: | |
1604 | format_string = self.format_string | |
1605 | application_name = self.application_name | |
1606 | if not application_name and record.channel and \ | |
1607 | '{record.channel}: ' in format_string: | |
1608 | self.format_string = format_string.replace( | |
1609 | '{record.channel}: ', '') | |
1610 | self.application_name = record.channel | |
1611 | # RFC 5424: <PRIVAL>version timestamp hostname app-name procid | |
1612 | # msgid structured-data message | |
1613 | before = u'<{}>1 {}Z {} {} {} - - '.format( | |
1614 | self.encode_priority(record), | |
1615 | record.time.isoformat(), | |
1616 | socket.gethostname(), | |
1617 | self.application_name if self.application_name else '-', | |
1618 | record.process) | |
1619 | return self.wrap_segments(record, before) | |
1620 | finally: | |
1621 | self.format_string = format_string | |
1622 | self.application_name = application_name | |
1623 | ||
1580 | 1624 | def emit(self, record): |
1581 | prefix = u('') | |
1582 | if self.application_name is not None: | |
1583 | prefix = self.application_name + u(':') | |
1584 | self.send_to_socket((u('<%d>%s%s\x00') % ( | |
1585 | self.encode_priority(record), | |
1586 | prefix, | |
1587 | self.format(record) | |
1588 | )).encode('utf-8')) | |
1625 | for segment in self.enveloper(record): | |
1626 | self.send_to_socket(segment.encode('utf-8')) | |
1589 | 1627 | |
1590 | 1628 | def send_to_socket(self, data): |
1591 | 1629 | if self.unixsocket: |
1598 | 1636 | # the flags are no longer optional on Python 3 |
1599 | 1637 | self.socket.sendto(data, 0, self.address) |
1600 | 1638 | else: |
1601 | self.socket.sendall(data) | |
1639 | try: | |
1640 | self.socket.sendall(data) | |
1641 | except self.connection_exception: | |
1642 | self._connect_netsocket() | |
1643 | self.socket.send(data) | |
1602 | 1644 | |
1603 | 1645 | def close(self): |
1604 | 1646 | self.socket.close() |
32 | 32 | with redirected_logging(set_root_logger_level): |
33 | 33 | logger.debug('This is from the old system') |
34 | 34 | logger.info('This is from the old system') |
35 | logger.warn('This is from the old %s', 'system') | |
35 | logger.warning('This is from the old %s', 'system') | |
36 | 36 | logger.error('This is from the old system') |
37 | 37 | logger.critical('This is from the old system') |
38 | 38 | logger.error('This is a %(what)s %(where)s', {'what': 'mapping', 'where': 'test'}) |
39 | 39 | header, data = mail.split('\n\n', 1) |
40 | 40 | if 'Content-Transfer-Encoding: base64' in header: |
41 | 41 | data = base64.b64decode(data).decode('utf-8') |
42 | assert re.search('Message type:\s+ERROR', data) | |
43 | assert re.search('Location:.*%s' % | |
42 | assert re.search(r'Message type:\s+ERROR', data) | |
43 | assert re.search(r'Location:.*%s' % | |
44 | 44 | re.escape(__file_without_pyc__), data) |
45 | assert re.search('Module:\s+%s' % __name__, data) | |
46 | assert re.search('Function:\s+test_mail_handler', data) | |
45 | assert re.search(r'Module:\s+%s' % __name__, data) | |
46 | assert re.search(r'Function:\s+test_mail_handler', data) | |
47 | 47 | body = u('Viva la Espa\xf1a') |
48 | 48 | if sys.version_info < (3, 0): |
49 | 49 | body = body.encode('utf-8') |
71 | 71 | body, rest = pieces |
72 | 72 | rest = rest.replace('\r', '') |
73 | 73 | |
74 | assert re.search('Message type:\s+ERROR', body) | |
75 | assert re.search('Module:\s+%s' % __name__, body) | |
76 | assert re.search('Function:\s+test_mail_handler_batching', body) | |
74 | assert re.search(r'Message type:\s+ERROR', body) | |
75 | assert re.search(r'Module:\s+%s' % __name__, body) | |
76 | assert re.search(r'Function:\s+test_mail_handler_batching', body) | |
77 | 77 | |
78 | 78 | related = rest.strip().split('\n\n') |
79 | 79 | assert len(related) == 2 |
80 | assert re.search('Message type:\s+WARNING', related[0]) | |
81 | assert re.search('Message type:\s+DEBUG', related[1]) | |
80 | assert re.search(r'Message type:\s+WARNING', related[0]) | |
81 | assert re.search(r'Message type:\s+DEBUG', related[1]) | |
82 | 82 | |
83 | 83 | assert 'And this triggers it again' in mail_handler.mails[1][2] |
84 | 84 | |
100 | 100 | body, rest = pieces |
101 | 101 | rest = rest.replace('\r', '') |
102 | 102 | |
103 | assert re.search('Message type:\\s+ERROR', body) | |
104 | assert re.search('Module:\s+' + __name__, body) | |
105 | assert re.search('Function:\s+test_group_handler_mail_combo', body) | |
103 | assert re.search(r'Message type:\s+ERROR', body) | |
104 | assert re.search(r'Module:\s+' + __name__, body) | |
105 | assert re.search(r'Function:\s+test_group_handler_mail_combo', body) | |
106 | 106 | |
107 | 107 | related = rest.strip().split('\n\n') |
108 | 108 | assert len(related) == 2 |
109 | assert re.search('Message type:\s+WARNING', related[0]) | |
110 | assert re.search('Message type:\s+DEBUG', related[1]) | |
109 | assert re.search(r'Message type:\s+WARNING', related[0]) | |
110 | assert re.search(r'Message type:\s+DEBUG', related[1]) | |
111 | 111 | |
112 | 112 | |
113 | 113 | def test_mail_handler_arguments(): |
0 | 0 | import os |
1 | import re | |
1 | 2 | import socket |
2 | 3 | from contextlib import closing |
3 | 4 | |
6 | 7 | |
7 | 8 | import pytest |
8 | 9 | |
10 | unix_socket = "/tmp/__unixsock_logbook.test" | |
9 | 11 | |
10 | def test_syslog_handler(logger, activation_strategy, unix_sock_path): | |
11 | to_test = [ | |
12 | (socket.AF_INET, ('127.0.0.1', 0)), | |
13 | ] | |
14 | if hasattr(socket, 'AF_UNIX'): | |
15 | to_test.append((socket.AF_UNIX, unix_sock_path)) | |
16 | for sock_family, address in to_test: | |
17 | with closing(socket.socket(sock_family, socket.SOCK_DGRAM)) as inc: | |
18 | inc.bind(address) | |
19 | inc.settimeout(1) | |
20 | for app_name in [None, 'Testing']: | |
21 | handler = logbook.SyslogHandler(app_name, inc.getsockname()) | |
22 | with activation_strategy(handler): | |
23 | logger.warn('Syslog is weird') | |
24 | try: | |
12 | to_test = [ | |
13 | (socket.AF_INET, socket.SOCK_DGRAM, ('127.0.0.1', 0)), | |
14 | (socket.AF_INET, socket.SOCK_STREAM, ('127.0.0.1', 0)), | |
15 | ] | |
16 | if hasattr(socket, 'AF_UNIX'): | |
17 | to_test.append((socket.AF_UNIX, socket.SOCK_DGRAM, unix_socket)) | |
18 | ||
19 | @pytest.mark.usefixtures("unix_sock_path") | |
20 | @pytest.mark.parametrize("sock_family,socktype,address", to_test) | |
21 | def test_syslog_handler(logger, activation_strategy, | |
22 | sock_family, socktype, address): | |
23 | delimiter = {socket.AF_UNIX: '\x00', | |
24 | socket.AF_INET: '\n'}[sock_family] | |
25 | with closing(socket.socket(sock_family, socktype)) as inc: | |
26 | inc.bind(address) | |
27 | if socktype == socket.SOCK_STREAM: | |
28 | inc.listen(0) | |
29 | inc.settimeout(1) | |
30 | for app_name in [None, 'Testing']: | |
31 | if sock_family == socket.AF_UNIX: | |
32 | expected = (r'^<12>%stestlogger: Syslog is weird%s$' % | |
33 | (app_name + ':' if app_name else '', | |
34 | delimiter)) | |
35 | else: | |
36 | expected = (r'^<12>1 \d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z %s %s %d ' | |
37 | '- - %sSyslog is weird%s$' % | |
38 | (socket.gethostname(), | |
39 | app_name if app_name else 'testlogger', | |
40 | os.getpid(), 'testlogger: ' if app_name else '', | |
41 | delimiter)) | |
42 | ||
43 | handler = logbook.SyslogHandler(app_name, inc.getsockname(), | |
44 | socktype=socktype) | |
45 | with activation_strategy(handler): | |
46 | logger.warn('Syslog is weird') | |
47 | try: | |
48 | if socktype == socket.SOCK_STREAM: | |
49 | with closing(inc.accept()[0]) as inc2: | |
50 | rv = inc2.recv(1024) | |
51 | else: | |
25 | 52 | rv = inc.recvfrom(1024)[0] |
26 | except socket.error: | |
27 | assert False, 'got timeout on socket' | |
28 | assert rv == ( | |
29 | u('<12>%stestlogger: Syslog is weird\x00') % | |
30 | ((app_name and (app_name + u(':'))) or u(''))).encode('utf-8') | |
53 | except socket.error: | |
54 | assert False, 'got timeout on socket' | |
55 | rv = rv.decode('utf-8') | |
56 | assert re.match(expected, rv), \ | |
57 | 'expected {}, got {}'.format(expected, rv) | |
31 | 58 | |
32 | 59 | |
33 | 60 | @pytest.fixture |
34 | 61 | def unix_sock_path(request): |
35 | returned = "/tmp/__unixsock_logbook.test" | |
62 | returned = unix_socket | |
36 | 63 | |
37 | 64 | @request.addfinalizer |
38 | 65 | def cleanup(): |