Skip to content

Commit

Permalink
Add a pluggable logging backend (#124)
Browse files Browse the repository at this point in the history
* refork & resync and address changes

* fix the typo

* use `user_model` instead of `user` when `user_login_failed`
  • Loading branch information
atakanarikan authored Feb 20, 2020
1 parent 0e720f3 commit c9081e1
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 64 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,35 @@ Below are some of the settings you may want to use. These should be defined in y

To clarify, this is only _truly_ necessary for the model signals.

* `DJANGO_EASY_AUDIT_LOGGING_BACKEND`

A pluggable backend option for logging. Defaults to `easyaudit.backends.ModelBackend`.
This class expects to have 3 methods:
* `login(self, login_info_dict):`
* `crud(self, crud_info_dict):`
* `request(self, request_info_dict):`

each of these methods accept a dictionary containing the info regarding the event.
example overriding:
```python
import logging

class PythonLoggerBackend:
logging.basicConfig()
logger = logging.getLogger('your-kibana-logger')
logger.setLevel(logging.DEBUG)

def request(self, request_info):
return request_info # if you don't need it

def login(self, login_info):
self.logger.info(msg='your message', extra=login_info)
return login_info

def crud(self, crud_info):
self.logger.info(msg='your message', extra=crud_info)
return crud_info
```
## What does it do

Django Easy Audit uses [Django signals](https://docs.djangoproject.com/en/dev/topics/signals/)
Expand Down
16 changes: 16 additions & 0 deletions easyaudit/backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import logging
from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent

logger = logging.getLogger(__name__)


class ModelBackend:

def request(self, request_info):
return RequestEvent.objects.create(**request_info)

def crud(self, crud_info):
return CRUDEvent.objects.create(**crud_info)

def login(self, login_info):
return LoginEvent.objects.create(**login_info)
2 changes: 2 additions & 0 deletions easyaudit/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def get_model_list(class_list):

USER_DB_CONSTRAINT = bool(getattr(settings, 'DJANGO_EASY_AUDIT_USER_DB_CONSTRAINT', True))

# logging backend settings
LOGGING_BACKEND = getattr(settings, 'DJANGO_EASY_AUDIT_LOGGING_BACKEND', 'easyaudit.backends.ModelBackend')

# Models which Django Easy Audit will not log.
# By default, all but some models will be audited.
Expand Down
34 changes: 22 additions & 12 deletions easyaudit/signals/auth_signals.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
from django.contrib.auth import signals, get_user_model
from django.db import transaction
from django.utils.module_loading import import_string

from easyaudit.middleware.easyaudit import get_current_request
from easyaudit.models import LoginEvent
from easyaudit.settings import REMOTE_ADDR_HEADER, WATCH_AUTH_EVENTS
from easyaudit.settings import REMOTE_ADDR_HEADER, WATCH_AUTH_EVENTS, LOGGING_BACKEND

audit_logger = import_string(LOGGING_BACKEND)()


def user_logged_in(sender, request, user, **kwargs):
try:
with transaction.atomic():
login_event = LoginEvent.objects.create(login_type=LoginEvent.LOGIN,
username=getattr(user, user.USERNAME_FIELD),
user_id=getattr(user, 'id', None),
remote_ip=request.META[REMOTE_ADDR_HEADER])
login_event = audit_logger.login({
'login_type': LoginEvent.LOGIN,
'username': getattr(user, user.USERNAME_FIELD),
'user_id': getattr(user, 'id', None),
'remote_ip': request.META[REMOTE_ADDR_HEADER]
})
except:
pass


def user_logged_out(sender, request, user, **kwargs):
try:
with transaction.atomic():
login_event = LoginEvent.objects.create(login_type=LoginEvent.LOGOUT,
username=getattr(user, user.USERNAME_FIELD, None),
user_id=getattr(user, 'id', None),
remote_ip=request.META[REMOTE_ADDR_HEADER])
login_event = audit_logger.login({
'login_type': LoginEvent.LOGOUT,
'username': getattr(user, user.USERNAME_FIELD),
'user_id': getattr(user, 'id', None),
'remote_ip': request.META[REMOTE_ADDR_HEADER]
})
except:
pass

Expand All @@ -32,9 +40,11 @@ def user_login_failed(sender, credentials, **kwargs):
with transaction.atomic():
request = get_current_request() # request argument not available in django < 1.11
user_model = get_user_model()
login_event = LoginEvent.objects.create(login_type=LoginEvent.FAILED,
username=credentials[user_model.USERNAME_FIELD],
remote_ip=request.META[REMOTE_ADDR_HEADER])
login_event = audit_logger.login({
'login_type': LoginEvent.FAILED,
'username': credentials[user_model.USERNAME_FIELD],
'remote_ip': request.META[REMOTE_ADDR_HEADER]
})
except:
pass

Expand Down
87 changes: 45 additions & 42 deletions easyaudit/signals/model_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
from django.db.models import signals
from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.module_loading import import_string

from easyaudit.middleware.easyaudit import get_current_request, \
get_current_user
from easyaudit.models import CRUDEvent
from easyaudit.settings import REGISTERED_CLASSES, UNREGISTERED_CLASSES, \
WATCH_MODEL_EVENTS, CRUD_DIFFERENCE_CALLBACKS, DATABASE_ALIAS
WATCH_MODEL_EVENTS, CRUD_DIFFERENCE_CALLBACKS, LOGGING_BACKEND, \
DATABASE_ALIAS
from easyaudit.utils import model_delta

logger = logging.getLogger(__name__)
audit_logger = import_string(LOGGING_BACKEND)()


def should_audit(instance):
Expand Down Expand Up @@ -97,17 +100,17 @@ def crud_flow():
try:
# atomicity based on the easyaudit database alias
with transaction.atomic(using=DATABASE_ALIAS):
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
changed_fields=changed_fields,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
crud_event = audit_logger.crud({
'event_type': event_type,
'object_repr': str(instance),
'object_json_repr': object_json_repr,
'changed_fields': changed_fields,
'content_type_id': c_t.id,
'object_id': instance.pk,
'user_id': getattr(user, 'id', None),
'datetime': timezone.now(),
'user_pk_as_string': str(user.pk) if user else user
})
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
Expand Down Expand Up @@ -161,16 +164,16 @@ def post_save(sender, instance, created, raw, using, update_fields, **kwargs):
def crud_flow():
try:
with transaction.atomic(using=DATABASE_ALIAS):
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
crud_event = audit_logger.crud({
'event_type': event_type,
'object_repr': str(instance),
'object_json_repr': object_json_repr,
'content_type_id': c_t.id,
'object_id': instance.pk,
'user_id': getattr(user, 'id', None),
'datetime': timezone.now(),
'user_pk_as_string': str(user.pk) if user else user
})
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
Expand Down Expand Up @@ -244,16 +247,16 @@ def m2m_changed(sender, instance, action, reverse, model, pk_set, using, **kwarg
def crud_flow():
try:
with transaction.atomic(using=DATABASE_ALIAS):
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
crud_event = audit_logger.crud({
'event_type': event_type,
'object_repr': str(instance),
'object_json_repr': object_json_repr,
'content_type_id': c_t.id,
'object_id': instance.pk,
'user_id': getattr(user, 'id', None),
'datetime': timezone.now(),
'user_pk_as_string': str(user.pk) if user else user
})
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
Expand Down Expand Up @@ -292,16 +295,16 @@ def crud_flow():
try:
with transaction.atomic(using=DATABASE_ALIAS):
# crud event
crud_event = CRUDEvent.objects.create(
event_type=CRUDEvent.DELETE,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
crud_event = audit_logger.crud({
'event_type': CRUDEvent.DELETE,
'object_repr': str(instance),
'object_json_repr': object_json_repr,
'content_type_id': c_t.id,
'object_id': instance.pk,
'user_id': getattr(user, 'id', None),
'datetime': timezone.now(),
'user_pk_as_string': str(user.pk) if user else user
})

except Exception as e:
logger.exception(
Expand Down
23 changes: 13 additions & 10 deletions easyaudit/signals/request_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
from django.http.cookie import SimpleCookie
from django.utils import timezone
from django.conf import settings
from django.utils.module_loading import import_string

from easyaudit.models import RequestEvent
from easyaudit.settings import REMOTE_ADDR_HEADER, UNREGISTERED_URLS, REGISTERED_URLS, WATCH_REQUEST_EVENTS
from easyaudit.settings import REMOTE_ADDR_HEADER, UNREGISTERED_URLS, REGISTERED_URLS, WATCH_REQUEST_EVENTS, \
LOGGING_BACKEND

import re

audit_logger = import_string(LOGGING_BACKEND)()


def should_log_url(url):
# check if current url is blacklisted
Expand Down Expand Up @@ -56,14 +59,14 @@ def request_started_handler(sender, environ, **kwargs):
except:
user = None

request_event = RequestEvent.objects.create(
url=environ['PATH_INFO'],
method=environ['REQUEST_METHOD'],
query_string=environ['QUERY_STRING'],
user_id=getattr(user, 'id', None),
remote_ip=environ[REMOTE_ADDR_HEADER],
datetime=timezone.now()
)
request_event = audit_logger.request({
'url': environ['PATH_INFO'],
'method': environ['REQUEST_METHOD'],
'query_string': environ['QUERY_STRING'],
'user_id': getattr(user, 'id', None),
'remote_ip': environ[REMOTE_ADDR_HEADER],
'datetime': timezone.now()
})


if WATCH_REQUEST_EVENTS:
Expand Down

0 comments on commit c9081e1

Please sign in to comment.