0001-general-reduce-logging-infrastructure-do-not-expose-.patch
tests/admin_pages/test_settings.py | ||
---|---|---|
1 | 1 |
import io |
2 |
import logging |
|
3 | 2 |
import os |
4 | 3 |
import urllib.parse |
5 | 4 |
import zipfile |
... | ... | |
726 | 725 |
} |
727 | 726 | |
728 | 727 | |
729 |
def test_settings_logs(pub): |
|
730 |
# reset logging state |
|
731 |
logging.shutdown() |
|
732 |
if os.path.exists(os.path.join(pub.app_dir, 'wcs.log')): |
|
733 |
os.unlink(os.path.join(pub.app_dir, 'wcs.log')) |
|
734 | ||
735 |
create_superuser(pub) |
|
736 |
app = login(get_app(pub)) |
|
737 |
resp = app.get('/backoffice/settings/') |
|
738 |
assert 'Logs' in resp.text |
|
739 |
resp = resp.click('Logs') |
|
740 |
assert '<td class="message">login</td>' in resp.text |
|
741 | ||
742 |
resp = app.get('/backoffice/settings/debug_options') |
|
743 |
assert resp.form['logger'].checked is True |
|
744 |
resp.form['logger'].checked = False |
|
745 |
resp = resp.form.submit() |
|
746 |
resp = resp.follow() |
|
747 |
assert 'Logs' not in resp.text |
|
748 | ||
749 | ||
750 | 728 |
def test_settings_geolocation(pub): |
751 | 729 |
create_superuser(pub) |
752 | 730 |
app = login(get_app(pub)) |
tests/form_pages/test_formdata.py | ||
---|---|---|
770 | 770 |
user.name = 'Foo Baré' |
771 | 771 |
user.store() |
772 | 772 | |
773 |
pub.cfg['debug'] = {'logger': True}
|
|
773 |
pub.cfg['debug'] = {} |
|
774 | 774 |
pub.write_cfg() |
775 | 775 |
wf = Workflow(name='status') |
776 | 776 |
st1 = wf.add_status('Status1', 'st1') |
... | ... | |
814 | 814 |
http_post_request.return_value = None, 200, 'null', None |
815 | 815 |
resp = resp.form.submit('button_export_to') |
816 | 816 |
assert http_post_request.call_count == 1 |
817 |
if locale.getpreferredencoding() == 'UTF-8': |
|
818 |
assert caplog.records[-1].message == "file 'template.pdf' pushed to portfolio of 'Foo Baré'" |
|
819 |
else: # Python < 3.7 |
|
820 |
assert caplog.records[-1].message == "file 'template.pdf' pushed to portfolio of 'Foo Bar\xe9'" |
|
821 | 817 | |
822 | 818 |
resp = resp.follow() # $form/$id/create_doc |
823 | 819 |
resp = resp.follow() # $form/$id/create_doc/ |
tests/test_datasource_chrono.py | ||
---|---|---|
1 | 1 |
import io |
2 | 2 |
import json |
3 |
import os |
|
4 | 3 |
from unittest import mock |
5 | 4 | |
6 | 5 |
import pytest |
... | ... | |
24 | 23 |
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'}) |
25 | 24 |
pub.set_app_dir(req) |
26 | 25 |
pub._set_request(req) |
27 | ||
28 |
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: |
|
29 |
fd.write( |
|
30 |
''' |
|
31 |
[debug] |
|
32 |
logger=true |
|
33 |
''' |
|
34 |
) |
|
35 | ||
36 | 26 |
pub.load_site_options() |
37 | 27 | |
38 | 28 |
return pub |
tests/test_fc_auth.py | ||
---|---|---|
66 | 66 |
# setup an hobo profile |
67 | 67 |
CmdCheckHobos().update_profile(PROFILE, pub) |
68 | 68 |
pub.cfg['users']['field_name'] = ['_prenoms', '_nom'] |
69 |
pub.cfg['debug'] = {'logger': True} |
|
70 | 69 |
pub.user_class.wipe() |
71 | 70 |
pub.write_cfg() |
72 | 71 |
tests/test_saml_auth.py | ||
---|---|---|
267 | 267 | |
268 | 268 |
CmdCheckHobos().update_profile(PROFILE, pub) |
269 | 269 | |
270 |
pub.cfg['debug'] = {'logger': True} |
|
271 |
pub.write_cfg() |
|
272 | 270 |
pub.set_config() |
273 | 271 | |
274 | 272 |
pub.role_class.wipe() |
wcs/admin/settings.py | ||
---|---|---|
41 | 41 |
from wcs.qommon import _, errors, get_cfg, ident, misc, template |
42 | 42 |
from wcs.qommon.admin.cfg import cfg_submit |
43 | 43 |
from wcs.qommon.admin.emails import EmailsDirectory |
44 |
from wcs.qommon.admin.logger import LoggerDirectory |
|
45 | 44 |
from wcs.qommon.admin.menu import error_page |
46 | 45 |
from wcs.qommon.admin.settings import SettingsDirectory as QommonSettingsDirectory |
47 | 46 |
from wcs.qommon.admin.texts import TextsDirectory |
... | ... | |
518 | 517 |
('user-templates', 'user_templates'), |
519 | 518 |
('data-sources', 'data_sources'), |
520 | 519 |
'wscalls', |
521 |
'logs', |
|
522 | 520 |
('api-access', 'api_access'), |
523 | 521 |
] |
524 | 522 | |
... | ... | |
530 | 528 |
filetypes = FileTypesDirectory() |
531 | 529 |
data_sources = NamedDataSourcesDirectory() |
532 | 530 |
wscalls = NamedWsCallsDirectory() |
533 |
logs = LoggerDirectory() |
|
534 | 531 |
api_access = ApiAccessDirectory() |
535 | 532 | |
536 | 533 |
def _q_index(self): |
... | ... | |
619 | 616 |
_('Debug Options'), |
620 | 617 |
_('Configure options useful for debugging'), |
621 | 618 |
) |
622 |
if get_cfg('debug', {}).get('logger', True): |
|
623 |
r += htmltext('<dt><a href="logs/">%s</a></dt> <dd>%s</dd>') % ( |
|
624 |
_('Logs'), |
|
625 |
_('Access application log files'), |
|
626 |
) |
|
627 | 619 |
r += htmltext('</dl>') |
628 | 620 |
r += htmltext('</div>') |
629 | 621 |
wcs/qommon/admin/logger.py | ||
---|---|---|
1 |
# w.c.s. - web application for online forms |
|
2 |
# Copyright (C) 2005-2010 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or modify |
|
5 |
# it under the terms of the GNU General Public License as published by |
|
6 |
# the Free Software Foundation; either version 2 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import os |
|
18 |
import random |
|
19 | ||
20 |
from quixote import get_publisher, get_request, get_response |
|
21 |
from quixote.directory import Directory |
|
22 |
from quixote.html import TemplateIO, htmltext |
|
23 | ||
24 |
from .. import _, errors, logger |
|
25 |
from ..admin.menu import error_page |
|
26 |
from ..backoffice.menu import html_top |
|
27 | ||
28 | ||
29 |
class ByUserDirectory(Directory): |
|
30 |
def _q_lookup(self, component): |
|
31 |
return ByUserPages(component) |
|
32 | ||
33 | ||
34 |
class LoggerDirectory(Directory): |
|
35 |
_q_exports = ['', 'download', 'by_user'] |
|
36 | ||
37 |
by_user = ByUserDirectory() |
|
38 | ||
39 |
def _q_index(self): |
|
40 |
get_response().breadcrumb.append(('logs/', _('Logs'))) |
|
41 |
html_top('logs', title=_('Logs')) |
|
42 |
r = TemplateIO(html=False) |
|
43 |
request = get_request() |
|
44 |
logfile = str(request.get_field('logfile', get_publisher().APP_NAME + '.log')) |
|
45 |
if not logfile.startswith(str(get_publisher().APP_NAME + '.log')) or '/' in str(logfile): |
|
46 |
return error_page('logs/', _('Bad log file: %s') % logfile) |
|
47 |
logfilename = str(os.path.join(get_publisher().app_dir, logfile)) |
|
48 | ||
49 |
if not os.path.exists(logfilename): |
|
50 |
r += _('Nothing to show') |
|
51 |
else: |
|
52 |
get_response().filter['sidebar'] = self.get_sidebar(logfile) |
|
53 | ||
54 |
user_color_keys = {} |
|
55 |
last_date = None |
|
56 |
r += htmltext('<table id="logs">\n') |
|
57 |
r += htmltext('<thead> <tr>') |
|
58 |
r += htmltext(' <th>%s</th>') % _('Time') |
|
59 |
r += htmltext(' <th>%s</th>') % _('User') |
|
60 |
r += htmltext(' <th>%s</th>') % _('Message') |
|
61 |
r += htmltext('<tr></thead>\n') |
|
62 |
r += htmltext('<tbody>\n') |
|
63 |
userlabels = {} |
|
64 |
with open(logfilename) as fd: |
|
65 |
for d in logger.parse_logstream(fd): |
|
66 |
if not d: |
|
67 |
continue |
|
68 | ||
69 |
if d.get('user_id'): |
|
70 |
user_color_key = d['user_id'] |
|
71 |
if user_color_key == 'anonymous': |
|
72 |
user_color_key += d['ip'] |
|
73 |
if user_color_key not in user_color_keys: |
|
74 |
user_color_keys[user_color_key] = ''.join( |
|
75 |
['%x' % random.randint(0xC, 0xF) for x in range(3)] |
|
76 |
) |
|
77 |
r += htmltext('<tr class="level-%s" style="background: #%s;">') % ( |
|
78 |
d['level'].lower(), |
|
79 |
user_color_keys[user_color_key], |
|
80 |
) |
|
81 |
else: |
|
82 |
r += htmltext('<tr class="level-%s">') % d['level'].lower() |
|
83 | ||
84 |
if last_date != d['date']: |
|
85 |
r += htmltext(' <td class="time">%s %s</td>') % (d['date'], d['hour'][:-4]) |
|
86 |
last_date = d['date'] |
|
87 |
else: |
|
88 |
r += htmltext(' <td class="time">%s</td>') % (d['hour'][:-4]) |
|
89 | ||
90 |
user_id = d.get('user_id') |
|
91 |
if not user_id: |
|
92 |
userlabel = None |
|
93 |
elif user_id == 'anonymous': |
|
94 |
userlabel = _('Anonymous') |
|
95 |
ip = d['ip'] |
|
96 |
r += htmltext(' <td class="userlabel"><span title="%s">%s</span></td>') % ( |
|
97 |
ip, |
|
98 |
userlabel, |
|
99 |
) |
|
100 |
elif user_id == 'unlogged': |
|
101 |
userlabel = _('Unlogged') |
|
102 |
ip = d['ip'] |
|
103 |
r += htmltext(' <td class="userlabel"><span title="%s">%s</span></td>') % ( |
|
104 |
ip, |
|
105 |
userlabel, |
|
106 |
) |
|
107 |
elif user_id == 'bot': |
|
108 |
userlabel = _('Bot') |
|
109 |
r += htmltext(' <td class="userlabel">%s</td>') % userlabel |
|
110 |
else: |
|
111 |
userlabel = userlabels.get(user_id) |
|
112 |
if not userlabel: |
|
113 |
try: |
|
114 |
user = get_publisher().user_class.get(user_id) |
|
115 |
userlabel = htmltext(user.display_name.replace(' ', ' ')) |
|
116 |
except KeyError: |
|
117 |
userlabel = _('Unknown') |
|
118 |
userlabels[user_id] = userlabel |
|
119 |
r += htmltext(' <td class="userlabel">%s</td>') % userlabel |
|
120 |
if userlabel: |
|
121 |
r += htmltext(' <td class="message">%s</td>') % d['message'] |
|
122 |
else: |
|
123 |
r += htmltext('<td class="message" colspan="2">%s</td>') % d['message'] |
|
124 |
r += htmltext('</tr>\n') |
|
125 |
r += htmltext('</tbody>\n') |
|
126 |
r += htmltext('</table>\n') |
|
127 |
return r.getvalue() |
|
128 | ||
129 |
def get_sidebar(self, logfile): |
|
130 |
r = TemplateIO(html=True) |
|
131 |
r += htmltext('<ul>') |
|
132 |
if logfile: |
|
133 |
r += htmltext('<li><a href="download?logfile=%s">%s</a></li>') % ( |
|
134 |
logfile, |
|
135 |
_('Download Raw Log File'), |
|
136 |
) |
|
137 |
else: |
|
138 |
r += htmltext('<li><a href="download">%s</a></li>') % _('Download Raw Log File') |
|
139 |
r += htmltext('</ul>') |
|
140 | ||
141 |
logfiles = [ |
|
142 |
x for x in os.listdir(get_publisher().app_dir) if x.startswith(get_publisher().APP_NAME + '.log') |
|
143 |
] |
|
144 |
if len(logfiles) > 1: |
|
145 |
options = [] |
|
146 |
for lfile in logfiles: |
|
147 |
with open(os.path.join(get_publisher().app_dir, lfile)) as fd: |
|
148 |
firstline = fd.readline() |
|
149 |
d = logger.readline(firstline) |
|
150 |
if not d: |
|
151 |
continue |
|
152 |
if logfile == lfile: |
|
153 |
selected = 'selected="selected" ' |
|
154 |
else: |
|
155 |
selected = '' |
|
156 |
options.append( |
|
157 |
{'selected': selected, 'lfile': lfile, 'date': '%s %s' % (d['date'], d['hour'])} |
|
158 |
) |
|
159 | ||
160 |
r += htmltext('<form id="other-log-select">') |
|
161 |
r += _('Select another logfile:') |
|
162 |
r += htmltext('<select name="logfile">') |
|
163 |
options.sort(key=lambda x: x['date']) |
|
164 |
options.reverse() |
|
165 |
for option in options: |
|
166 |
option['since'] = str(_('Since: %s') % option['date'])[:-4] |
|
167 |
r += htmltext('<option value="%(lfile)s"%(selected)s>%(since)s</option>') % option |
|
168 |
r += htmltext('</select>') |
|
169 |
r += htmltext('<input type="submit" value="%s" />') % _('Submit') |
|
170 | ||
171 |
return r.getvalue() |
|
172 | ||
173 |
def download(self): |
|
174 |
request = get_request() |
|
175 |
logfile = request.get_field('logfile', get_publisher().APP_NAME + '.log') |
|
176 |
if not logfile.startswith(get_publisher().APP_NAME + '.log') or '/' in logfile: |
|
177 |
return error_page('logs/', _('Bad log file: %s') % logfile) |
|
178 |
logfilename = os.path.join(get_publisher().app_dir, logfile) |
|
179 |
response = get_response() |
|
180 |
response.set_content_type('text/x-log', 'iso-8859-1') |
|
181 |
response.set_header('content-disposition', 'attachment; filename=%s' % logfile) |
|
182 |
with open(logfilename) as fd: |
|
183 |
return fd.read() |
|
184 | ||
185 | ||
186 |
class ByUserPages(Directory): |
|
187 |
_q_exports = [''] |
|
188 | ||
189 |
def __init__(self, component): |
|
190 |
try: |
|
191 |
self.user = get_publisher().user_class.get(component) |
|
192 |
except KeyError: |
|
193 |
raise errors.TraversalError() |
|
194 | ||
195 |
def _q_index(self): |
|
196 |
html_top('logs', title=_('Logs')) |
|
197 |
r = TemplateIO(html=True) |
|
198 |
r += htmltext('<h2>%s - %s</h2>') % (_('User'), self.user.name) |
|
199 | ||
200 |
last_date = None |
|
201 |
r += htmltext('<table id="logs">') |
|
202 |
r += htmltext('<thead> <tr>') |
|
203 |
r += htmltext(' <th>%s</th>') % _('Time') |
|
204 |
r += htmltext(' <th>%s</th>') % _('Message') |
|
205 |
r += htmltext('<tr></thead>') |
|
206 |
r += htmltext('<tbody>') |
|
207 |
logfilename = str(os.path.join(get_publisher().app_dir, get_publisher().APP_NAME + '.log')) |
|
208 |
if os.path.exists(logfilename): |
|
209 |
with open(logfilename) as fd: |
|
210 |
for line in fd: |
|
211 |
d = logger.readline(line) |
|
212 |
if not d or d['user_id'] != str(self.user.id): |
|
213 |
continue |
|
214 |
r += htmltext('<tr>') |
|
215 |
if last_date != d['date']: |
|
216 |
r += htmltext(' <td class="time">%s %s</td>') % (d['date'], d['hour'][:-4]) |
|
217 |
last_date = d['date'] |
|
218 |
else: |
|
219 |
r += htmltext(' <td class="time">%s</td>') % (d['hour'][:-4]) |
|
220 |
r += htmltext(' <td><a href="%s">%s</a></td>') % (d['url'], d['message']) |
|
221 |
r += htmltext('</tr>') |
|
222 |
r += htmltext('</tbody>') |
|
223 |
r += htmltext('</table>') |
|
224 |
return r.getvalue() |
wcs/qommon/admin/settings.py | ||
---|---|---|
73 | 73 |
title=_('Email for Tracebacks'), |
74 | 74 |
value=debug_cfg.get('error_email', ''), |
75 | 75 |
) |
76 |
form.add(CheckboxWidget, 'logger', title=_('Logger'), value=debug_cfg.get('logger', True)) |
|
77 | 76 |
form.add( |
78 | 77 |
CheckboxWidget, |
79 | 78 |
'debug_mode', |
... | ... | |
104 | 103 |
cfg_submit( |
105 | 104 |
form, |
106 | 105 |
'debug', |
107 |
('error_email', 'logger', 'debug_mode', 'mail_redirection'),
|
|
106 |
('error_email', 'debug_mode', 'mail_redirection'), |
|
108 | 107 |
) |
109 | 108 |
return redirect('.') |
wcs/qommon/logger.py | ||
---|---|---|
97 | 97 |
) or '[nosession]' |
98 | 98 | |
99 | 99 |
return logging.Formatter.format(self, record).replace('\n', '\n ') |
100 | ||
101 | ||
102 |
def parse_logstream(stream): |
|
103 |
""" |
|
104 |
Parse a stream of lines making a log file, each log line start with a |
|
105 |
non-blank character continue until the lines does not start with a blank |
|
106 |
character |
|
107 |
""" |
|
108 |
line = next(stream) |
|
109 |
while True: |
|
110 |
r = readline(line) |
|
111 |
try: |
|
112 |
# Skip badly formatted lines |
|
113 |
if r is None: |
|
114 |
line = next(stream) |
|
115 |
continue |
|
116 |
while True: |
|
117 |
line = next(stream) |
|
118 |
if line.startswith(' '): |
|
119 |
# Append the line without the first blank |
|
120 |
r['message'] = r['message'] + line[1:] |
|
121 |
continue |
|
122 |
break |
|
123 |
except StopIteration: |
|
124 |
# Ont last line return the current one |
|
125 |
if r is not None: |
|
126 |
r['message'] = r['message'].strip() |
|
127 |
yield r |
|
128 |
break |
|
129 |
r['message'] = r['message'].strip() |
|
130 |
yield r |
|
131 | ||
132 | ||
133 |
def readline(line): |
|
134 |
if not line: |
|
135 |
return None |
|
136 |
try: |
|
137 |
date, hour, level, ip, session_id, url, user_id, dash, message = line.split(' ', 8) |
|
138 |
except ValueError: |
|
139 |
try: |
|
140 |
date, hour, level, message = line.split(' ', 3) |
|
141 |
except ValueError: |
|
142 |
return None # misformatted line |
|
143 |
del line |
|
144 |
return locals() |
|
145 |
if dash != '-': |
|
146 |
return None |
|
147 |
del line |
|
148 |
return locals() |
wcs/qommon/publisher.py | ||
---|---|---|
694 | 694 |
cls.root_directory_class(), |
695 | 695 |
session_cookie_name=cls.APP_NAME, |
696 | 696 |
session_cookie_path='/', |
697 |
logger=logger.ApplicationLogger(error_log=cls.ERROR_LOG),
|
|
697 |
logger=logger.ApplicationLogger(), |
|
698 | 698 |
) |
699 | 699 |
publisher.substitutions = Substitutions() |
700 | 700 |
publisher.app_dir = cls.APP_DIR |
701 |
- |