Merge pull request #29 from jjkester/threadlocals

Use threadlocals and log remote address
This commit is contained in:
Jan-Jelle Kester 2015-07-21 23:50:40 +02:00
commit ef4b93a551
5 changed files with 46 additions and 11 deletions

View file

@ -25,7 +25,8 @@ Actors
------
When using automatic logging, the actor is empty by default. However, auditlog can set the actor from the current
request automatically. This does not need any custom code, adding a middleware class is enough.
request automatically. This does not need any custom code, adding a middleware class is enough. When an actor is logged
the remote address of that actor will be logged as well.
To enable the automatic logging of the actors, simply add the following to your ``MIDDLEWARE_CLASSES`` setting in your
project's configuration file::

View file

@ -1,5 +1,6 @@
from __future__ import unicode_literals
import threading
import time
from django.conf import settings
@ -9,6 +10,9 @@ from django.db.models.loading import get_model
from auditlog.models import LogEntry
threadlocal = threading.local()
class AuditlogMiddleware(object):
"""
Middleware to couple the request's user to log items. This is accomplished by currying the signal receiver with the
@ -20,19 +24,27 @@ class AuditlogMiddleware(object):
Gets the current user from the request and prepares and connects a signal receiver with the user already
attached to it.
"""
# Initialize thread local storage
threadlocal.auditlog = {
'signal_duid': (self.__class__, time.time()),
'remote_addr': request.META.get('REMOTE_ADDR'),
}
# In case of proxy, set 'original' address
if request.META.get('HTTP_X_FORWARDED_FOR'):
threadlocal.auditlog['remote_addr'] = request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0]
# Connect signal for automatic logging
if hasattr(request, 'user') and hasattr(request.user, 'is_authenticated') and request.user.is_authenticated():
user = request.user
request.auditlog_ts = time.time()
set_actor = curry(self.set_actor, user)
pre_save.connect(set_actor, sender=LogEntry, dispatch_uid=(self.__class__, request.auditlog_ts), weak=False)
set_actor = curry(self.set_actor, request.user)
pre_save.connect(set_actor, sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'], weak=False)
def process_response(self, request, response):
"""
Disconnects the signal receiver to prevent it from staying active.
"""
# Disconnecting the signal receiver is required because it will not be garbage collected (non-weak reference)
if hasattr(request, 'auditlog_ts'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=(self.__class__, request.auditlog_ts))
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return response
@ -40,8 +52,8 @@ class AuditlogMiddleware(object):
"""
Disconnects the signal receiver to prevent it from staying active in case of an exception.
"""
if hasattr(request, 'auditlog_ts'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=(self.__class__, request.auditlog_ts))
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return None
@ -58,3 +70,5 @@ class AuditlogMiddleware(object):
auth_user_model = get_model('auth', 'user')
if sender == LogEntry and isinstance(user, auth_user_model) and instance.actor is None:
instance.actor = user
if hasattr(threadlocal, 'auditlog'):
instance.remote_addr = threading.local().auditlog['remote_addr']

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('auditlog', '0002_auto_support_long_primary_keys'),
]
operations = [
migrations.AddField(
model_name='logentry',
name='remote_addr',
field=models.GenericIPAddressField(null=True, verbose_name='remote address', blank=True),
),
]

View file

@ -143,6 +143,7 @@ class LogEntry(models.Model):
action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("action"))
changes = models.TextField(blank=True, verbose_name=_("change message"))
actor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_("actor"))
remote_addr = models.GenericIPAddressField(blank=True, null=True, verbose_name=_("remote address"))
timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_("timestamp"))
additional_data = JSONField(blank=True, null=True, verbose_name=_("additional data"))

View file

@ -19,6 +19,6 @@ MIDDLEWARE_CLASSES = (
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'auditlog_tests',
'NAME': 'auditlog_tests.db',
}
}