Skip to content

Commit

Permalink
add monitoring and example app
Browse files Browse the repository at this point in the history
  • Loading branch information
lociii committed Sep 3, 2016
1 parent a259163 commit f5bd94f
Show file tree
Hide file tree
Showing 53 changed files with 1,607 additions and 19 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,3 +1,4 @@
*.pyc
.idea/
.vagrant/
.vagrant/
.pycharm_helpers/
40 changes: 40 additions & 0 deletions README.md
@@ -0,0 +1,40 @@
Django Monitoring
=================
Extensive documentation will be provided soon.

Improve django_monitoring
-------------------------

We've included an example app to show how django_monitoring works and to make it easy to improve it.
Start by launching the included vagrant machine:
```bash
vagrant up
```

Then setup the example app environment:
```bash
./manage.py migrate
./manage.py loaddata example
```
The installed superuser is "example" with password "monitoring".

Run the development webserver:
```bash
./manage.py runserver 0.0.0.0:8000
```

Login on the admin interface and open http://dm.dev:8000/ afterwards.
You'll be prompted with an empty dashboard. That's because we didn't run any checks yet.
Let's enqueue an update:
```bash
./manage.py monitoring_run_checks
```

Now we need to start a celery worker to handle the updates:
```bash
celery worker -A example -l DEBUG -Q django_monitoring
```

You will see some failed check now after you refreshed the dashboard view.

![Django monitoring dashboard](http://static.jensnistler.de/django_monitoring.png "Django monitoring dashboard")
2 changes: 0 additions & 2 deletions README.rst

This file was deleted.

4 changes: 3 additions & 1 deletion Vagrantfile
Expand Up @@ -31,7 +31,9 @@ Vagrant.configure(2) do |config|
salt.masterless = true
salt.minion_config = "vagrant/salt/minion.conf"
salt.run_highstate = true
salt.verbose = true
salt.bootstrap_options = "-c /tmp/ -P"
salt.verbose = true
salt.colorize = true
salt.log_level = "info"
end
end
Empty file added django_monitoring/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions django_monitoring/admin.py
@@ -0,0 +1,13 @@
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals

from django.contrib import admin

from django_monitoring.models import Check


@admin.register(Check)
class CheckAdmin(admin.ModelAdmin):
list_display = ('slug', 'identifier', 'status')
search_fields = ('slug', 'identifier', 'payload_description')
list_filter = ('status', )
17 changes: 17 additions & 0 deletions django_monitoring/apps.py
@@ -0,0 +1,17 @@
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals, print_function

from django.apps import AppConfig
from django.contrib import admin
from django_monitoring.monitoring import monitor


class DjangoMonitoringConfig(AppConfig):
name = 'django_monitoring'
verbose_name = "DjangoMonitoring"

def ready(self):
super(DjangoMonitoringConfig, self).ready()

monitor.autodiscover_checks()
admin.autodiscover()
133 changes: 133 additions & 0 deletions django_monitoring/base.py
@@ -0,0 +1,133 @@
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
import logging

from django import forms

from django_monitoring import models
from django_monitoring.tasks import django_monitoring_enqueue
from django_monitoring.models import Check
from django_monitoring.monitoring import monitor

logger = logging.getLogger(__name__)


class BaseCheckForm(forms.Form):
def save(self, instance):
instance.config = self.cleaned_data
instance.save(update_fields=['config'])
return instance


class BaseCheck(object):

"""
Any check should inherits from `BaseCheck` and should implements `.generate(self)`
and `.check(self, payload)` methods.
Optionally, you can implements `.get_assigned_user(self, payload)` (resp. `.get_assigned_group(self, payload)`)
to define to which user (resp. group) the system had to assign the check result.
"""

config_form = None
title = ''

def __init__(self):
self.slug = monitor.get_slug(self.__module__, self.__class__.__name__)

def run(self):
django_monitoring_enqueue.apply_async(kwargs=dict(check_slug=self.slug), queue='django_monitoring')

def handle(self, payload):
# check result
unacknowledge = False
try:
check_result = Check.objects.get(
slug=self.slug, identifier=self.get_identifier(payload))
old_status = check_result.status
except Check.DoesNotExist:
check_result = None
status = self.check(payload)
if check_result and old_status > Check.STATUS.ok and status == Check.STATUS.ok:
unacknowledge = True
self.save(payload, status,
unacknowledge=unacknowledge)

def get_config(self, payload):
try:
check_result = Check.objects.get(slug=self.slug, identifier=self.get_identifier(payload))

# check has a configuration
if check_result.config:
return check_result.config
except Check.DoesNotExist:
pass

# get default config from form initial values
form = self.get_form_class()()
return {name: field.initial for name, field in form.fields.items()}

def get_form(self, payload):
return self.get_form_class()(**self.get_config(payload))

def get_form_class(self):
return self.config_form

def save(self, payload, result, unacknowledge=False):
defaults = {
'status': result,
'assigned_to_user': self.get_assigned_user(payload, result),
'assigned_to_group': self.get_assigned_group(payload, result),
'payload_description': self.get_payload_description(payload)
}

# save the check
dataset, created = Check.objects.get_or_create(
slug=self.slug, identifier=self.get_identifier(payload),
defaults=defaults)

# update existing dataset
if not created:
for (key, value) in defaults.items():
setattr(dataset, key, value)
if unacknowledge:
dataset.acknowledge_by = None
dataset.acknowledge_at = None
dataset.acknowledge_until = None
dataset.save()

def generate(self):
"""
yield items to run check for
"""
raise NotImplementedError(".generate() should be overridden")

def check(self, payload):
"""
:param payload: the payload to run the check for
:return:
"""
raise NotImplementedError(".check() should be overridden")

def get_identifier(self, payload):
raise NotImplementedError(".get_identifier() should be overridden")

def get_payload_description(self, payload):
return str(payload)

def get_assigned_user(self, payload, result):
return None

def get_assigned_group(self, payload, result):
return None

def get_context_data(self, payload):
return dict()

def get_title(self):
return self.title

def get_template_name(self):
if hasattr(self, 'template_name'):
return self.template_name
return None
Empty file.
21 changes: 21 additions & 0 deletions django_monitoring/common/views.py
@@ -0,0 +1,21 @@
# -*- coding: UTF-8 -*-
from django.views.generic.edit import FormMixin
from django.views.generic.list import ListView


class FilteredListView(FormMixin, ListView):
def get_form_kwargs(self):
return {
'initial': self.get_initial(),
'prefix': self.get_prefix(),
'data': self.request.GET or None
}

def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()

form = self.get_form(self.get_form_class())
self.object_list = form.filter_queryset(request, self.object_list)

context = self.get_context_data(form=form, object_list=self.object_list)
return self.render_to_response(context)
58 changes: 58 additions & 0 deletions django_monitoring/forms.py
@@ -0,0 +1,58 @@
# -*- coding: UTF-8 -*-
from django import forms
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from model_utils.choices import Choices

from django_monitoring.models import Check


class ResultFilterForm(forms.Form):
STATUS_CHOICES = Choices((0, 'all', _('All')), (1, 'failed', _('Failed')))

user = forms.ModelChoiceField(queryset=get_user_model().objects.all().order_by('last_name', 'first_name'),
label=_('User'), required=False)
status = forms.TypedChoiceField(coerce=int, choices=STATUS_CHOICES, label=_('Status'),
initial=STATUS_CHOICES.failed)

def __init__(self, user, group_filter=None, **kwargs):
super(ResultFilterForm, self).__init__(**kwargs)

if not group_filter:
group_filter = dict()

self.fields['user'].initial = user
self.fields['user'].queryset = self.fields['user'].queryset.filter(**group_filter)

def filter_queryset(self, request, queryset):
# default values if form has not been submitted
if not self.is_bound:
return queryset.failed().unacknowledged().for_user(request.user)
# form has been submitted with invalid values
if not self.is_valid():
return queryset.none()

if self.cleaned_data['user']:
queryset = queryset.for_user(self.cleaned_data['user'])
if self.cleaned_data['status']:
if self.cleaned_data['status'] == self.STATUS_CHOICES.failed:
queryset = queryset.failed().unacknowledged()

return queryset.distinct()


class AcknowledgeForm(forms.ModelForm):
days = forms.IntegerField(label=_('Days to acknowledge'))

class Meta:
model = Check
fields = ['days', 'acknowledged_reason']

def __init__(self, user, **kwargs):
self.user = user
super(AcknowledgeForm, self).__init__(**kwargs)

def save(self, commit=True):
self.instance.acknowledge(user=self.user, days=self.cleaned_data.get('days'),
reason=self.cleaned_data['acknowledged_reason'], commit=commit)
return self.instance
Binary file added django_monitoring/locale/de/LC_MESSAGES/django.mo
Binary file not shown.

0 comments on commit f5bd94f

Please sign in to comment.