Projet

Général

Profil

0002-debian-add-journald-support-to-debian_config_common-.patch

Benjamin Dauvergne, 05 décembre 2018 13:59

Télécharger (13,9 ko)

Voir les différences:

Subject: [PATCH 2/2] debian: add journald support to debian_config_common
 (fixes #23471)

 debian/control                                |   3 +-
 debian/debian_config_common.py                |  22 +-
 hobo/journal.py                               | 224 ++++++++++++++++++
 .../test_request_context_filter.py            |  57 +++++
 tox.ini                                       |   1 +
 5 files changed, 294 insertions(+), 13 deletions(-)
 create mode 100644 hobo/journal.py
debian/control
21 21
Recommends: python-django (>= 1.8),
22 22
    python-gadjo,
23 23
    python-django-mellon (>= 1.2.22.26),
24
    memcached
24
    memcached,
25
    python-systemd
25 26
Description: Rapid Remote Deployment python module
26 27

  
27 28
Package: hobo
debian/debian_config_common.py
9 9
# execfile('/etc/%s/settings.py' % PROJECT_NAME)
10 10

  
11 11
import os
12
import warnings
13

  
12 14
from django.conf import global_settings
13 15
from django.core.exceptions import ImproperlyConfigured
14 16

  
......
147 149
    },
148 150
}
149 151

  
150
# Graylog support
151
if 'GRAYLOG_URL' in os.environ:
152
# Journald support
153
if os.path.exists('/run/systemd/journal/socket'):
152 154
    try:
153
        from graypy import GELFHandler
155
        from systemd import journal
154 156
    except ImportError:
155
        raise ImproperlyConfigured('cannot configure graypy, import of GELFHandler failed')
157
        warnings.warn('journald will not be used directly, please install python-systemd')
156 158
    else:
157
        host = os.environ['GRAYLOG_URL'].split(':')[0]
158
        port = int(os.environ['GRAYLOG_URL'].split(':')[1])
159
        LOGGING['handlers']['gelf'] = {
160
            'class': 'graypy.GELFHandler',
161
            'fqdn': True,
162
            'host': host,
163
            'port': port,
159
        LOGGING['handlers']['journald'] = {
160
            'class': 'hobo.journal.JournalHandler',
164 161
        }
165
        LOGGING['loggers']['']['handlers'].append('gelf')
162
        LOGGING['loggers']['']['handlers'].remove('syslog')
163
        LOGGING['loggers']['']['handlers'].append('journald')
166 164

  
167 165
# Sentry support
168 166
if 'SENTRY_DSN' in os.environ:
hobo/journal.py
1
#  -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */
2
#
3
#
4
#  Copyright 2012 David Strauss <david@davidstrauss.net>
5
#  Copyright 2012 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
6
#  Copyright 2012 Marti Raudsepp <marti@juffo.org>
7
#
8
#  python-systemd is free software; you can redistribute it and/or modify it
9
#  under the terms of the GNU Lesser General Public License as published by
10
#  the Free Software Foundation; either version 2.1 of the License, or
11
#  (at your option) any later version.
12
#
13
#  python-systemd is distributed in the hope that it will be useful, but
14
#  WITHOUT ANY WARRANTY; without even the implied warranty of
15
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
#  Lesser General Public License for more details.
17
#
18
#  You should have received a copy of the GNU Lesser General Public License
19
#  along with python-systemd; If not, see <http://www.gnu.org/licenses/>.
20

  
21
import sys as _sys
22
import traceback as _traceback
23
import logging as _logging
24
from syslog import (LOG_ALERT, LOG_CRIT, LOG_ERR,
25
                    LOG_WARNING, LOG_INFO, LOG_DEBUG)
26

  
27
from systemd._journal import sendv
28

  
29
_IDENT_CHARACTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_0123456789')
30

  
31

  
32
def _valid_field_name(s):
33
    return not (set(s) - _IDENT_CHARACTER)
34

  
35

  
36
def _make_line(field, value):
37
    if isinstance(value, bytes):
38
        return field.encode('utf-8') + b'=' + value
39
    elif isinstance(value, str):
40
        return field + '=' + value
41
    else:
42
        return field + '=' + str(value)
43

  
44

  
45
def send(MESSAGE, MESSAGE_ID=None,
46
         CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
47
         **kwargs):
48
    r"""Send a message to the journal.
49

  
50
    >>> from systemd import journal
51
    >>> journal.send('Hello world')
52
    >>> journal.send('Hello, again, world', FIELD2='Greetings!')
53
    >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
54

  
55
    Value of the MESSAGE argument will be used for the MESSAGE= field. MESSAGE
56
    must be a string and will be sent as UTF-8 to the journal.
57

  
58
    MESSAGE_ID can be given to uniquely identify the type of message. It must be
59
    a string or a uuid.UUID object.
60

  
61
    CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to identify the caller.
62
    Unless at least on of the three is given, values are extracted from the
63
    stack frame of the caller of send(). CODE_FILE and CODE_FUNC must be
64
    strings, CODE_LINE must be an integer.
65

  
66
    Additional fields for the journal entry can only be specified as keyword
67
    arguments. The payload can be either a string or bytes. A string will be
68
    sent as UTF-8, and bytes will be sent as-is to the journal.
69

  
70
    Other useful fields include PRIORITY, SYSLOG_FACILITY, SYSLOG_IDENTIFIER,
71
    SYSLOG_PID.
72
    """
73

  
74
    args = ['MESSAGE=' + MESSAGE]
75

  
76
    if MESSAGE_ID is not None:
77
        id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID)
78
        args.append('MESSAGE_ID=' + id)
79

  
80
    if CODE_LINE is CODE_FILE is CODE_FUNC is None:
81
        CODE_FILE, CODE_LINE, CODE_FUNC = _traceback.extract_stack(limit=2)[0][:3]
82
    if CODE_FILE is not None:
83
        args.append('CODE_FILE=' + CODE_FILE)
84
    if CODE_LINE is not None:
85
        args.append('CODE_LINE={:d}'.format(CODE_LINE))
86
    if CODE_FUNC is not None:
87
        args.append('CODE_FUNC=' + CODE_FUNC)
88

  
89
    args.extend(_make_line(key, val) for key, val in kwargs.items())
90
    return sendv(*args)
91

  
92

  
93
class JournalHandler(_logging.Handler):
94
    """Journal handler class for the Python logging framework.
95

  
96
    Please see the Python logging module documentation for an overview:
97
    http://docs.python.org/library/logging.html.
98

  
99
    To create a custom logger whose messages go only to journal:
100

  
101
    >>> import logging
102
    >>> log = logging.getLogger('custom_logger_name')
103
    >>> log.propagate = False
104
    >>> log.addHandler(JournalHandler())
105
    >>> log.warning("Some message: %s", 'detail')
106

  
107
    Note that by default, message levels `INFO` and `DEBUG` are ignored by the
108
    logging framework. To enable those log levels:
109

  
110
    >>> log.setLevel(logging.DEBUG)
111

  
112
    To redirect all logging messages to journal regardless of where they come
113
    from, attach it to the root logger:
114

  
115
    >>> logging.root.addHandler(JournalHandler())
116

  
117
    For more complex configurations when using `dictConfig` or `fileConfig`,
118
    specify `systemd.journal.JournalHandler` as the handler class.  Only
119
    standard handler configuration options are supported: `level`, `formatter`,
120
    `filters`.
121

  
122
    To attach journal MESSAGE_ID, an extra field is supported:
123

  
124
    >>> import uuid
125
    >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF')
126
    >>> log.warning("Message with ID", extra={'MESSAGE_ID': mid})
127

  
128
    Fields to be attached to all messages sent through this handler can be
129
    specified as keyword arguments. This probably makes sense only for
130
    SYSLOG_IDENTIFIER and similar fields which are constant for the whole
131
    program:
132

  
133
    >>> JournalHandler(SYSLOG_IDENTIFIER='my-cool-app')
134
    <...JournalHandler ...>
135

  
136
    The following journal fields will be sent: `MESSAGE`, `PRIORITY`,
137
    `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`, `CODE_FUNC`, `LOGGER` (name as
138
    supplied to getLogger call), `MESSAGE_ID` (optional, see above),
139
    `SYSLOG_IDENTIFIER` (defaults to sys.argv[0]).
140

  
141
    The function used to actually send messages can be overridden using
142
    the `sender_function` parameter.
143
    """
144

  
145
    def __init__(self, level=_logging.NOTSET, sender_function=send, **kwargs):
146
        super(JournalHandler, self).__init__(level)
147

  
148
        for name in kwargs:
149
            if not _valid_field_name(name):
150
                raise ValueError('Invalid field name: ' + name)
151
        if 'SYSLOG_IDENTIFIER' not in kwargs:
152
            kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0]
153

  
154
        self.send = sender_function
155
        self._extra = kwargs
156

  
157
    def emit(self, record):
158
        """Write `record` as a journal event.
159

  
160
        MESSAGE is taken from the message provided by the user, and PRIORITY,
161
        LOGGER, THREAD_NAME, CODE_{FILE,LINE,FUNC} fields are appended
162
        automatically. In addition, record.MESSAGE_ID will be used if present.
163
        """
164
        try:
165
            msg = self.format(record)
166
            pri = self.map_priority(record.levelno)
167
            # defaults
168
            extras = self._extra.copy()
169

  
170
            # higher priority
171
            if record.exc_text:
172
                extras['EXCEPTION_TEXT'] = record.exc_text
173

  
174
            if record.exc_info:
175
                extras['EXCEPTION_INFO'] = record.exc_info
176

  
177
            if record.args:
178
                extras['CODE_ARGS'] = str(record.args)
179

  
180
            # explicit arguments — highest priority
181
            for key, value in record.__dict__.items():
182
                new_key = key.upper()
183
                if new_key in ['PRIORITY', 'LOGGER', 'THREAD_NAME',
184
                               'PROCESS_NAME', 'CODE_FILE', 'MESSAGE',
185
                               'CODE_LINE', 'CODE_FUNC']:
186
                    continue
187
                if key in ['threadName', 'processName', 'pathname', 'lineno',
188
                           'funcName']:
189
                    continue
190
                extras[key.upper()] = value
191

  
192
            self.send(msg,
193
                      PRIORITY=format(pri),
194
                      LOGGER=record.name,
195
                      THREAD_NAME=record.threadName,
196
                      PROCESS_NAME=record.processName,
197
                      CODE_FILE=record.pathname,
198
                      CODE_LINE=record.lineno,
199
                      CODE_FUNC=record.funcName,
200
                      **extras)
201
        except Exception:
202
            self.handleError(record)
203

  
204
    @staticmethod
205
    def map_priority(levelno):
206
        """Map logging levels to journald priorities.
207

  
208
        Since Python log level numbers are "sparse", we have to map numbers in
209
        between the standard levels too.
210
        """
211
        if levelno <= _logging.DEBUG:
212
            return LOG_DEBUG
213
        elif levelno <= _logging.INFO:
214
            return LOG_INFO
215
        elif levelno <= _logging.WARNING:
216
            return LOG_WARNING
217
        elif levelno <= _logging.ERROR:
218
            return LOG_ERR
219
        elif levelno <= _logging.CRITICAL:
220
            return LOG_CRIT
221
        else:
222
            return LOG_ALERT
223

  
224
    mapPriority = map_priority
tests_multitenant/test_request_context_filter.py
1
import pytest
2

  
1 3
import logging
4

  
2 5
from hobo.logger import RequestContextFilter
6

  
3 7
from tenant_schemas.utils import tenant_context
4 8

  
5 9
from django.contrib.auth.models import User
......
36 40
        assert record.user_display_name == 'John Doe'
37 41
        assert record.user_uuid == 'ab' * 16
38 42
        assert record.application == 'fake-agent'
43

  
44

  
45
@pytest.fixture
46
def journald_handler():
47
    from hobo.journal import JournalHandler
48

  
49
    root_logger = logging.getLogger()
50
    journald_handler = JournalHandler()
51
    root_logger.handlers.append(journald_handler)
52
    try:
53
        yield journald_handler
54
    finally:
55
        root_logger.handlers.remove(journald_handler)
56

  
57

  
58
def test_systemd(settings, tenants, client, journald_handler):
59
    root_logger = logging.getLogger()
60
    assert len(root_logger.handlers) == 2
61
    journald_handler.addFilter(RequestContextFilter())
62

  
63
    for tenant in tenants:
64
        with tenant_context(tenant):
65
            user = User.objects.create(first_name='John', last_name='Doe', username='john.doe',
66
                                       email='jodn.doe@example.com')
67
            user.set_password('john.doe')
68
            user.save()
69
            user.saml_identifiers.create(name_id='ab' * 16, issuer='https://idp.example.com')
70

  
71
    for tenant in tenants:
72
        settings.ALLOWED_HOSTS.append(tenant.domain_url)
73
        with tenant_context(tenant):
74
            client.login(username='john.doe', password='john.doe')
75
        client.get('/', SERVER_NAME=tenant.domain_url,
76
                   HTTP_X_FORWARDED_FOR='99.99.99.99, 127.0.0.1')
77

  
78
    from systemd.journal import Reader
79
    import time
80

  
81
    reader = Reader()
82
    reader.seek_realtime(time.time() - 10)
83
    records = [l for l in reader if l['MESSAGE'] == 'wat!']
84
    assert len(records) == 2
85
    for tenant, record in zip(tenants, records):
86
        assert record['IP'] == '99.99.99.99'
87
        assert record['TENANT'] == tenant.domain_url
88
        assert record['PATH'] == '/'
89
        assert record['REQUEST_ID'].startswith('r:')
90
        assert record['USER'] == user.username
91
        assert record['USER_EMAIL'] == user.email
92
        assert record['USER_NAME'] == user.username
93
        assert record['USER_DISPLAY_NAME'] == 'John Doe'
94
        assert record['USER_UUID'] == 'ab' * 16
95
        assert record['APPLICATION'] == 'fake-agent'
tox.ini
47 47
	passerelle: http://git.entrouvert.org/passerelle.git/snapshot/passerelle-master.tar.gz
48 48
	passerelle: suds
49 49
	passerelle: python-memcached
50
	multitenant: systemd-python
50 51
	http://git.entrouvert.org/debian/django-tenant-schemas.git/snapshot/django-tenant-schemas-master.tar.gz
51 52
	httmock
52 53
	requests
53
-