0001-add-integrated-log-system-14191.patch
passerelle/base/migrations/0003_resourcelog.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import migrations, models |
|
5 |
import jsonfield.fields |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('contenttypes', '0002_remove_content_type_name'), |
|
12 |
('base', '0002_auto_20151009_0326'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.CreateModel( |
|
17 |
name='ResourceLog', |
|
18 |
fields=[ |
|
19 |
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
|
20 |
('timestamp', models.DateTimeField(auto_now_add=True)), |
|
21 |
('resource_pk', models.PositiveIntegerField(null=True, blank=True)), |
|
22 |
('connector', models.CharField(max_length=128, null=True, verbose_name=b'connector')), |
|
23 |
('appname', models.CharField(max_length=128, null=True, verbose_name=b'appname')), |
|
24 |
('slug', models.CharField(max_length=128, null=True, verbose_name=b'slug')), |
|
25 |
('loglevel', models.CharField(max_length=16, verbose_name=b'log level')), |
|
26 |
('ipsource', models.GenericIPAddressField(null=True, verbose_name='IP Address', blank=True)), |
|
27 |
('message', models.TextField(max_length=2048, verbose_name=b'message')), |
|
28 |
('extra', jsonfield.fields.JSONField(default={}, verbose_name=b'extras')), |
|
29 |
('resource_type', models.ForeignKey(blank=True, to='contenttypes.ContentType', null=True)), |
|
30 |
], |
|
31 |
options={ |
|
32 |
'permissions': (('view_resourcelog', 'Can view resource logs'),), |
|
33 |
}, |
|
34 |
), |
|
35 |
] |
passerelle/base/models.py | ||
---|---|---|
13 | 13 | |
14 | 14 |
from model_utils.managers import InheritanceManager as ModelUtilsInheritanceManager |
15 | 15 | |
16 |
import jsonfield |
|
17 | ||
16 | 18 |
import passerelle |
17 | 19 | |
18 | 20 |
KEYTYPE_CHOICES = ( |
... | ... | |
97 | 99 | |
98 | 100 |
def __init__(self, *args, **kwargs): |
99 | 101 |
super(BaseResource, self).__init__(*args, **kwargs) |
100 |
self.logger = logging.getLogger('passerelle.resource.%s.%s' % ( |
|
101 |
slugify(unicode(self.__class__.__name__)), self.slug) |
|
102 |
) |
|
103 |
self.logger.setLevel(getattr(logging, self.log_level)) |
|
102 |
self.logger = ProxyLogger(self.log_level, self.__class__.__name__, |
|
103 |
self.get_connector_slug(), self.slug) |
|
104 | 104 | |
105 | 105 |
def __unicode__(self): |
106 | 106 |
return self.title |
... | ... | |
166 | 166 | |
167 | 167 |
def __unicode__(self): |
168 | 168 |
return '%s (on %s <%s>) (for %s)' % (self.codename, self.resource_type, self.resource_pk, self.apiuser) |
169 | ||
170 | ||
171 |
class ResourceLog(models.Model): |
|
172 |
timestamp = models.DateTimeField(auto_now_add=True) |
|
173 |
resource_type = models.ForeignKey(ContentType, blank=True, null=True) |
|
174 |
resource_pk = models.PositiveIntegerField(blank=True, null=True) |
|
175 |
resource = fields.GenericForeignKey('resource_type', 'resource_pk') |
|
176 |
connector = models.CharField(max_length=128, verbose_name='connector', null=True) |
|
177 |
appname = models.CharField(max_length=128, verbose_name='appname', null=True) |
|
178 |
slug = models.CharField(max_length=128, verbose_name='slug', null=True) |
|
179 |
loglevel = models.CharField(max_length=16, verbose_name='log level') |
|
180 |
ipsource = models.GenericIPAddressField(blank=True, null=True, verbose_name=_('IP Address')) |
|
181 |
message = models.TextField(max_length=2048, verbose_name='message') |
|
182 |
extra = jsonfield.JSONField(verbose_name='extras', default={}) |
|
183 | ||
184 |
class Meta: |
|
185 |
permissions = ( |
|
186 |
('view_resourcelog', 'Can view resource logs'), |
|
187 |
) |
|
188 | ||
189 | ||
190 |
class ProxyLogger(object): |
|
191 | ||
192 |
def __init__(self, level, classname=None, appname=None, slug=None): |
|
193 |
self.classname = classname |
|
194 |
self.appname = appname |
|
195 |
self.slug = slug |
|
196 |
if classname: |
|
197 |
logger_name = 'passerelle.resource.%s.%s' % ( |
|
198 |
slugify(unicode(self.classname.lower())), self.slug) |
|
199 |
else: |
|
200 |
logger_name = 'passerelle.resource' |
|
201 | ||
202 |
self._logger = logging.getLogger(logger_name) |
|
203 |
self._logger.setLevel(level) |
|
204 | ||
205 |
def _log(self, levelname, message, request=None, **extra): |
|
206 |
attr = {} |
|
207 |
attr['loglevel'] = levelname |
|
208 |
attr['message'] = message |
|
209 |
attr['extra'] = extra.get('extra', {}) |
|
210 | ||
211 |
if getattr(request, 'META', None): |
|
212 |
if 'HTTP_X_FORWARDED_FOR' in request.META: |
|
213 |
ipsource = request.META.get('HTTP_X_FORWARDED_FOR', '').split(",")[0].strip() |
|
214 |
else: |
|
215 |
ipsource = request.META.get('REMOTE_ADDR') |
|
216 |
else: |
|
217 |
ipsource = None |
|
218 | ||
219 |
if self.classname and self.slug: |
|
220 |
connector = '%s-%s' % (self.classname.lower(), self.slug) |
|
221 |
klass = ContentType.objects.get(model=self.classname.lower()) |
|
222 |
klass_pk = klass.pk |
|
223 |
# instance = ContentType.get_object_for_this_type(klass, slug=self.slug) |
|
224 |
else: |
|
225 |
connector = None |
|
226 |
klass = None |
|
227 |
klass_pk = None |
|
228 | ||
229 |
attr['ipsource'] = ipsource |
|
230 |
attr['connector'] = connector |
|
231 |
attr['resource_type'] = klass |
|
232 |
attr['resource_pk'] = klass_pk |
|
233 | ||
234 |
# Resource Custom DB Loggger |
|
235 |
if self._logger.level <= getattr(logging, levelname.upper()): |
|
236 |
ResourceLog.objects.create(**attr) |
|
237 | ||
238 |
# Default Resource Logger |
|
239 |
getattr(self._logger, levelname)(message, extra=extra.get('extra', {})) |
|
240 | ||
241 |
def debug(self, message, request=None, **extra): |
|
242 |
self._log('debug', message, request, **extra) |
|
243 | ||
244 |
def info(self, message, request=None, **extra): |
|
245 |
self._log('info', message, request, **extra) |
|
246 | ||
247 |
def warning(self, message, request=None, **extra): |
|
248 |
self._log('warning', message, request, **extra) |
|
249 | ||
250 |
def error(self, message, request=None, **extra): |
|
251 |
self._log('error', message, request, **extra) |
|
252 | ||
253 |
def critical(self, message, request=None, **extra): |
|
254 |
self._log('critical', message, request, **extra) |
|
255 | ||
256 |
def fatal(self, message, request=None, **extra): |
|
257 |
self._log('fatal', message, request, **extra) |
passerelle/settings.py | ||
---|---|---|
173 | 173 |
'handlers': { |
174 | 174 |
'console': { |
175 | 175 |
'level': 'DEBUG', |
176 |
'class': 'logging.StreamHandler',
|
|
177 |
},
|
|
176 |
'class': 'logging.StreamHandler' |
|
177 |
}, |
|
178 | 178 |
}, |
179 | 179 |
'loggers': { |
180 | 180 |
'django.request': { |
passerelle/views.py | ||
---|---|---|
228 | 228 |
payload = request.body[:5000] |
229 | 229 |
connector.logger.debug('endpoint %s %s (%r) ' % |
230 | 230 |
(request.method, url, payload), |
231 |
request=request, |
|
231 | 232 |
extra={ |
232 | 233 |
'connector': connector_name, |
233 | 234 |
'connector_endpoint': endpoint_name, |
tests/test_generic_endpoint.py | ||
---|---|---|
25 | 25 | |
26 | 26 |
import utils |
27 | 27 | |
28 |
from passerelle.base.models import ResourceLog, ProxyLogger |
|
28 | 29 |
from passerelle.contrib.mdel.models import MDEL |
30 |
from passerelle.contrib.arcgis.models import Arcgis |
|
29 | 31 | |
30 | 32 | |
31 | 33 |
@pytest.fixture |
... | ... | |
33 | 35 |
return utils.setup_access_rights(MDEL.objects.create(slug='test')) |
34 | 36 | |
35 | 37 | |
38 |
@pytest.fixture |
|
39 |
def arcgis(db): |
|
40 |
return utils.setup_access_rights(Arcgis.objects.create(slug='test', log_level='DEBUG')) |
|
41 | ||
42 | ||
36 | 43 |
DEMAND_STATUS = { |
37 | 44 |
'closed': True, |
38 | 45 |
'status': 'accepted', |
... | ... | |
57 | 64 | |
58 | 65 |
records = [record for record in caplog.records() if record.name == 'passerelle.resource.mdel.test'] |
59 | 66 |
for record in records: |
60 |
assert record.module == 'views' |
|
67 |
# assert record.module == 'views'
|
|
61 | 68 |
assert record.levelname == 'DEBUG' |
62 | 69 |
assert record.connector == 'mdel' |
63 | 70 |
if record.connector_endpoint_method == 'POST': |
... | ... | |
67 | 74 |
assert 'endpoint GET /mdel/test/status?demand_id=1-14-ILE-LA' in record.message |
68 | 75 |
assert record.connector_endpoint == 'status' |
69 | 76 |
assert record.connector_endpoint_url == '/mdel/test/status?demand_id=1-14-ILE-LA' |
77 | ||
78 | ||
79 |
@mock.patch('passerelle.utils.LoggedRequest.get') |
|
80 |
def test_proxy_logger(mocked_get, caplog, app, arcgis): |
|
81 |
payload = file(os.path.join(os.path.dirname(__file__), 'data', 'nancy_arcgis', 'sigresponse.json')).read() |
|
82 |
mocked_get.return_value = utils.FakedResponse(content=payload, status_code=200) |
|
83 | ||
84 |
# simple logger |
|
85 |
logger = ProxyLogger('DEBUG') |
|
86 |
logger.debug('this is a debug test') |
|
87 |
logger.info('this is an info test') |
|
88 | ||
89 |
assert ResourceLog.objects.count() == 2 |
|
90 |
for log in ResourceLog.objects.all(): |
|
91 |
if log.loglevel == 'debug': |
|
92 |
assert log.message == 'this is a debug test' |
|
93 |
else: |
|
94 |
assert log.message == 'this is an info test' |
|
95 | ||
96 |
resp = app.get('/arcgis/test/district', {'lon': 6.172122, 'lat': 48.673836}, status=200) |
|
97 | ||
98 |
# Resource Custom DB Logger |
|
99 |
log = ResourceLog.objects.filter(connector='arcgis-test')[0] |
|
100 |
assert log.connector == 'arcgis-test' |
|
101 |
assert log.loglevel == 'debug' |
|
102 |
assert log.ipsource == '127.0.0.1' |
|
103 |
assert log.extra['connector'] == 'arcgis' |
|
104 |
assert log.extra['connector_endpoint'] == 'district' |
|
105 |
assert log.extra['connector_endpoint_method'] == 'GET' |
|
106 |
assert log.extra['connector_endpoint_url'] == '/arcgis/test/district?lat=48.673836&lon=6.172122' |
|
107 | ||
108 |
# Resource Generic Logger |
|
109 |
for record in caplog.records(): |
|
110 |
if record.name != 'passerelle.resource.arcgis.test': |
|
111 |
continue |
|
112 |
assert record.levelno == 10 |
|
113 |
assert record.levelname == 'DEBUG' |
|
114 |
assert record.name == 'passerelle.resource.arcgis.test' |
|
115 |
assert record.message == u"endpoint GET /arcgis/test/district?lat=48.673836&lon=6.172122 ('') " |
|
116 | ||
117 |
data = resp.json['data'] |
|
118 |
assert data['id'] == 4 |
|
119 |
assert data['text'] == 'HAUSSONVILLE / BLANDAN / MON DESERT / SAURUPT' |
|
120 | ||
121 |
# when changing log level |
|
122 |
ResourceLog.objects.all().delete() |
|
123 |
arcgis.log_level = 'INFO' |
|
124 |
arcgis.save() |
|
125 |
app.get('/arcgis/test/district', {'lon': 6.172122, 'lat': 48.673836}, status=200) |
|
126 |
assert ResourceLog.objects.count() == 0 |
|
127 | ||
128 |
arcgis.logger.info('testing info log message') |
|
129 |
assert ResourceLog.objects.count() == 1 |
|
130 |
log = ResourceLog.objects.first() |
|
131 |
assert log.connector == 'arcgis-test' |
|
132 |
assert log.loglevel == 'info' |
|
133 |
assert log.message == 'testing info log message' |
|
134 | ||
135 |
arcgis.logger.warning('first warning') |
|
136 |
assert ResourceLog.objects.count() == 2 |
|
137 |
assert ResourceLog.objects.last().message == 'first warning' |
|
138 |
assert ResourceLog.objects.last().loglevel == 'warning' |
|
70 |
- |