Skip to content

Commit 775f865

Browse files
committed
made more generic "request_logging" app
1 parent bc863bd commit 775f865

File tree

12 files changed

+250
-54
lines changed

12 files changed

+250
-54
lines changed

README.rst

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
Log django request data for the diagnostic purposes
2+
---------------------------------------------------
3+
4+
All requests matching filter functions will be logged
5+
in the database.
6+
7+
The following data is logged::
8+
* user (if authenticated)
9+
* IP address
10+
* request path (url)
11+
* request method
12+
* request parameters
13+
* session key
14+
* session snapshot
15+
* request META dictionary
16+
* COOKIES dictionary
17+
18+
Filters are functions defined via settings parameter
19+
`REQUEST_LOGGING_FILTERS`. For example::
20+
21+
from myapp.request_logging_filters import post_from_some_ips
22+
23+
REQUEST_LOGGING_FILTERS = (
24+
'myapp.request_logging_filters.authenticated_posts',
25+
posts_from_some_ips
26+
)
27+
28+
Filters may be defined either as a callable function or
29+
as a python dotted path to such function.
30+
Filter functions must take `request` instance as argument
31+
and return either `True` or `False`, when the request
32+
matches the filter or not, correspondingly.
33+
34+
.. note::
35+
Keep in mind that `request_logging` will store data
36+
in the database which will take storage space and
37+
computing resources. In production use sparingly,
38+
for diagnostinc purposes.
39+
40+
Configuration settings
41+
======================
42+
43+
The following `settings.py` entries are available::
44+
* `REQUEST_LOGGING_FILTERS` - a tuple of filter functions or dotted paths.
45+
* `REQUEST_LOGGING_HIDE_PARAMETERS` - a tuple of parameter names that will be hidden
46+
(e.g. `('password', 'credit_card_number')`)

askbot_log/admin.py

-17
This file was deleted.

askbot_log/middleware/__init__.py

Whitespace-only changes.

askbot_log/middleware/watch_users.py

-22
This file was deleted.

askbot_log/models.py

-12
This file was deleted.
File renamed without changes.

request_logging/admin.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from django.contrib import admin
2+
from .models import RequestLog
3+
from .utils import format_as_table
4+
5+
class RequestLogAdmin(admin.ModelAdmin):
6+
fieldsets = (
7+
(None, {
8+
'fields': ('user', 'url', 'timestamp',
9+
'method', 'parameters_disp', 'ip_addr')
10+
}),
11+
('Session', {
12+
'fields': ('session_key', 'session_data_disp'),
13+
'classes': ('collapse',)
14+
}),
15+
('Cookies', {
16+
'fields': ('cookies_disp',),
17+
'classes': ('collapse',)
18+
}),
19+
('Request META', {
20+
'fields': ('meta_disp',),
21+
'classes': ('collapse',)
22+
})
23+
)
24+
readonly_fields = (
25+
'user', 'timestamp', 'ip_addr', 'method', 'url', 'parameters_disp',
26+
'cookies_disp', 'meta_disp', 'session_key',
27+
'session_data_disp'
28+
)
29+
30+
list_display = ('user', 'url', 'timestamp')
31+
32+
def parameters_disp(self, instance):
33+
return format_as_table(instance.parameters)
34+
parameters_disp.allow_tags = True
35+
parameters_disp.short_description = 'Parameters'
36+
37+
def cookies_disp(self, instance):
38+
return format_as_table(instance.cookies)
39+
cookies_disp.allow_tags = True
40+
cookies_disp.short_description = 'Cookies'
41+
42+
def meta_disp(self, instance):
43+
return format_as_table(instance.meta)
44+
meta_disp.allow_tags = True
45+
meta_disp.short_description = 'Meta'
46+
47+
def session_data_disp(self, instance):
48+
return format_as_table(instance.session_data)
49+
session_data_disp.allow_tags = True
50+
session_data_disp.short_description = 'Session data'
51+
52+
admin.site.register(RequestLog, RequestLogAdmin)

request_logging/conf.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.conf import settings
2+
from appconf import AppConf
3+
4+
class RequestLoggingConf(AppConf):
5+
FILTERS = ()
6+
HIDE_PARAMETERS = ()
7+
8+
class Meta:
9+
prefix = 'request_logging'

request_logging/middleware.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Middleware that creates log records
2+
for watched users"""
3+
from .models import RequestLog
4+
from .conf import settings
5+
from .utils import clean_dict, replace_dict_values, import_attribute
6+
from django.conf import settings as django_settings
7+
import inspect
8+
9+
class RequestLoggingMiddleware(object):
10+
def process_request(self, request):
11+
for test_filter in self.get_filters():
12+
callable_filter = self.get_callable_filter(test_filter)
13+
filter_name = self.get_filter_name(test_filter)
14+
if callable_filter(request):
15+
self.log_request(request, filter_name)
16+
return
17+
18+
@classmethod
19+
def get_filters(cls):
20+
#todo: get filters configurable via django admin
21+
return settings.REQUEST_LOGGING_FILTERS
22+
23+
@classmethod
24+
def get_callable_filter(cls, path_or_filter):
25+
if callable(path_or_filter):
26+
return path_or_filter
27+
try:
28+
return import_attribute(path_or_filter)
29+
except ImportError:
30+
ImproperlyConfigured(
31+
'{} is not an importable python path'.format(path_or_filter)
32+
)
33+
34+
@classmethod
35+
def get_filter_name(cls, path_or_filter):
36+
if isinstance(path_or_filter, basestring):
37+
return path_or_filter
38+
elif inspect.isfunction(path_or_filter):
39+
funcname = path_or_filter.__name__
40+
module = inspect.getmodule(path_or_filter)
41+
if module:
42+
modname = module.__name__
43+
return '{}.{}'.format(modname, funcname)
44+
return funcname
45+
raise ImproperlyConfigured(
46+
'Invalid filter {}, must be dotted path'
47+
'or function'.format(unicode(path_or_filter))
48+
)
49+
50+
@classmethod
51+
def log_request(cls, request, filter_name):
52+
"""logs request data"""
53+
log = RequestLog()
54+
log.filter_name = filter_name
55+
log.ip_addr = request.META['REMOTE_ADDR']
56+
user = getattr(request, 'user', None)
57+
if user.is_authenticated():
58+
log.user = user
59+
log.url = request.path
60+
parameters = clean_dict(request.REQUEST)
61+
replace_dict_values(
62+
parameters,
63+
settings.REQUEST_LOGGING_HIDE_PARAMETERS,
64+
'********'
65+
)
66+
log.parameters = parameters
67+
log.session_key = request.session.session_key
68+
log.session_data = clean_dict(request.session)
69+
log.method = request.method
70+
log.meta = clean_dict(request.META)
71+
log.cookies = clean_dict(request.COOKIES)
72+
log.save()

request_logging/models.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from django.db import models
2+
from jsonfield import JSONField
3+
from django.contrib.auth.models import User
4+
5+
class RequestLog(models.Model):
6+
user = models.ForeignKey(User, null=True, blank=True)
7+
filter_name = models.CharField(max_length=255)
8+
ip_addr = models.GenericIPAddressField()
9+
url = models.TextField()
10+
parameters = JSONField()
11+
session_key = models.CharField(max_length=40)
12+
session_data = JSONField()
13+
method = models.CharField(max_length=16)
14+
meta = JSONField()
15+
cookies = JSONField()
16+
timestamp = models.DateTimeField(auto_now_add=True)
17+
18+
def __unicode__(self):
19+
return u'%s %s %s' % (self.user.username, self.ip_addr, self.url)

request_logging/utils.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Utilities for the django_request_logging module"""
2+
from django.utils.safestring import mark_safe
3+
4+
def clean_dict(data):
5+
"""
6+
converts all items to unicode, so that
7+
it can de json serialized
8+
"""
9+
data = dict(data)
10+
for key, value in data.items():
11+
data[key] = unicode(value)
12+
return data
13+
14+
def format_as_table(data):
15+
"""
16+
formats dictionary as HTML table
17+
"""
18+
output = ''
19+
for key in data:
20+
row = '<tr><td>%s</td><td>%s</td></tr>' % (key, data[key])
21+
output += row
22+
return mark_safe('<table>'+ output + '</table>')
23+
24+
def replace_dict_values(the_dict, keys, replacement):
25+
"""
26+
replaces values in dictionary for keys
27+
if present in the dictionary
28+
"""
29+
for key in keys:
30+
if key in the_dict:
31+
the_dict[key] = replacement
32+
33+
34+
def import_attribute(path):
35+
"""
36+
given path x.y.z
37+
does the same as statement
38+
from x.y import z
39+
40+
Code copied from appconfig.utils.import_attribute and simplified
41+
https://github.com/jezdez/django-appconf/blob/develop/appconf/utils.py
42+
"""
43+
try:
44+
from importlib import import_module
45+
except ImportError:
46+
from django.utils.importlib import import_module
47+
module_name, object_name = path.rsplit('.', 1)
48+
module = import_module(module_name)
49+
return getattr(module, object_name)

setup.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import sys
55

66
setup(
7-
name = "askbot_log",
8-
version = '0.0.1',
9-
description = 'Log post activity of watched users',
7+
name = "request_logging",
8+
version = '0.1.0',
9+
description = 'Log requests in the database for the diagrostic purposes',
1010
packages = find_packages(),
1111
author = 'Evgeny.Fadeev',
1212
author_email = '[email protected]',

0 commit comments

Comments
 (0)