Skip to content

Commit 20e7c43

Browse files
authored
Rendering and notification field (#14)
* UI Change * Do not send duplicate notification
1 parent a662057 commit 20e7c43

11 files changed

Lines changed: 181 additions & 53 deletions

File tree

error_tracker/django/middleware.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
from error_tracker.django import get_masking_module, get_context_builder, get_ticketing_module, \
1010
get_exception_model, get_notification_module, APP_ERROR_SUBJECT_PREFIX, APP_ERROR_EMAIL_SENDER, \
11-
APP_ERROR_RECIPIENT_EMAIL, TRACK_ALL_EXCEPTIONS
11+
APP_ERROR_RECIPIENT_EMAIL, TRACK_ALL_EXCEPTIONS, APP_ERROR_NOTIFICATION_ONCE, \
12+
APP_ERROR_TICKET_ONCE
1213
from error_tracker.libs.utils import get_exception_name, get_context_detail, get_notification_subject
1314

1415
model = get_exception_model()
@@ -60,13 +61,25 @@ def _raise_ticket(request, error):
6061

6162
@staticmethod
6263
def _post_process(request, frame_str, frames, error):
64+
send_notification = True
65+
raise_ticket = True
66+
6367
if request is not None:
6468
message = ('URL: %s' % request.path) + '\n\n'
6569
else:
6670
message = ""
6771
message += frame_str
68-
ErrorTracker._send_notification(request, message, frames[-1][:-1], error)
69-
ErrorTracker._raise_ticket(request, error)
72+
73+
if APP_ERROR_NOTIFICATION_ONCE is True and error.notification_sent is True:
74+
send_notification = False
75+
76+
if APP_ERROR_TICKET_ONCE is True and error.ticket_raised is True:
77+
raise_ticket = False
78+
79+
if send_notification:
80+
ErrorTracker._send_notification(request, message, frames[-1][:-1], error)
81+
if raise_ticket:
82+
ErrorTracker._raise_ticket(request, error)
7083

7184
def capture_exception(self, request=None, exception=None, additional_context=None):
7285
"""
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 3.1.2 on 2020-10-18 11:11
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('django', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='errormodel',
15+
name='notification_sent',
16+
field=models.BooleanField(default=False),
17+
),
18+
migrations.AddField(
19+
model_name='errormodel',
20+
name='ticket_raised',
21+
field=models.BooleanField(default=False),
22+
),
23+
]

error_tracker/django/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class ErrorModel(models.Model, ModelMixin):
3030
count = models.IntegerField(default=0)
3131
created_on = models.DateTimeField(auto_now=True)
3232
last_seen = models.DateTimeField(auto_now=True, db_index=True)
33+
notification_sent = models.BooleanField(default=False)
34+
ticket_raised = models.BooleanField(default=False)
3335

3436
@classmethod
3537
def get_exceptions_per_page(cls, page_number=1):
@@ -62,6 +64,8 @@ def create_or_update_entity(cls, rhash, host, path, method, request_data, except
6264
obj.count += 1
6365
obj.last_seen = now()
6466
obj.save(update_fields=['count', 'last_seen'])
67+
68+
return obj
6569
except Exception:
6670
print_exc()
6771

error_tracker/django/settings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,8 @@ def get(key, default):
4444
APP_ERROR_DB_MODEL = get('APP_ERROR_DB_MODEL', None)
4545
# Check error views are visible to others or not
4646
APP_ERROR_VIEW_PERMISSION = get('APP_ERROR_VIEW_PERMISSION', None)
47+
# Send email notification once
48+
APP_ERROR_NOTIFICATION_ONCE = get('APP_ERROR_NOTIFICATION_ONCE', False)
49+
# Raise ticket once
50+
APP_ERROR_TICKET_ONCE = get('APP_ERROR_NOTIFICATION_ONCE', False)
51+
Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1">
6-
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
7-
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
8-
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
9-
</head>
10-
<title>{{ title }}</title>
11-
<body>
12-
<div class="container">
13-
{% block content_block %}
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
7+
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
8+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
9+
{% block head_script %}
10+
<style>
11+
body{
12+
font-size: 14px;
13+
}
14+
</style>
1415
{% endblock %}
15-
</div>
16-
</body>
16+
</head>
17+
<title>{{ title }}</title>
18+
<body>
19+
<div class="container">
20+
{% block header_block %}
21+
<h2 class="text-center mb-5 mt-3">
22+
<a href="{% url 'view_errors' %}" class="text-dark">Errors Seen</a>
23+
</h2>
24+
{% endblock %}
25+
{% block content_block %}
26+
{% endblock %}
27+
</div>
28+
</body>
1729
</html>
Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
11
{% extends 'error_tracker/base.html' %}
22
{% load error_tracker %}
3+
34
{% block content_block %}
4-
<style>
5-
hr {
6-
margin-top: 10px !important;
7-
margin-bottom: 10px !important;
8-
}
5+
{% if error %}
6+
<h1 class="text-center alert alert-danger">{{ error }}</h1>
7+
{% else %}
98

10-
.row {
11-
border: 1px #eee solid;
12-
padding: 10px 15px;
13-
}
14-
</style>
15-
{% if error %}
16-
<h1 class="text-center alert alert-danger">{{ error }}</h1>
17-
{% else %}
18-
<h2 class="text-center"><a href="{% url 'view_errors' %}">Errors Seen</a></h2>
19-
<div class="row">
20-
<p><strong>URL:</strong>{{ obj.host }}{{ obj.path }}</p>
21-
<hr/>
22-
<p><strong>Method: </strong>{{ obj.method }}</p>
23-
<hr/>
24-
<p><strong>First time seen: </strong>{{ obj.created_on }}</p>
25-
<hr/>
26-
<p><strong>Last seen: </strong>{{ obj.last_seen }}</p>
27-
<hr/>
28-
<p><strong>Occurrences: </strong>{{ obj.count }}</p>
29-
<hr/>
30-
<div><strong>Request data: </strong>{{ obj.request_data }}</div>
31-
<hr/>
32-
<div><strong>Exception detail: </strong><br/>
33-
{{ obj.traceback|escape|replace_new_line_with_br|safe }}</div>
9+
<div class="row mb-4">
10+
<div class="col-md-2 bg-dark p-1 text-white text-center font-weight-bold">Method</div>
11+
<div class="col-md-10 bg-dark p-1 text-white text-center font-weight-bold">Referrer</div>
12+
<div class="col-md-2 p-1 text-center bg-light">
13+
{{ obj.method }}
14+
</div>
15+
<div class="col-md-10 p-1 text-center bg-light">
16+
{{ obj.host }}{{ obj.path }}
17+
</div>
18+
</div>
19+
20+
<div class="row mb-4 mt-2">
21+
<div class="col-md-4 p-0">
22+
<div class="col-md-12 text-center bg-dark text-white font-weight-bold p-0">First time seen</div>
23+
<div class="col-md-12 text-center bg-light">{{ obj.created_on }}</div>
24+
</div>
25+
<div class="col-md-4 p-0">
26+
<div class="col-md-12 text-center bg-dark text-white font-weight-bold">Last seen</div>
27+
<div class="col-md-12 text-center bg-light">{{ obj.last_seen }}</div>
3428
</div>
35-
{% endif %}
29+
<div class="col-md-4 p-0">
30+
<div class="col-md-12 text-center bg-dark text-white font-weight-bold" >Occurrences</div>
31+
<div class="col-md-12 text-center bg-light">{{ obj.count }}</div>
32+
</div>
33+
</div>
34+
<div class="row">
35+
<div class="col-md-12 text-center bg-dark text-white font-weight-bold p-1">Request data</div>
36+
<div class="col-md-12 col-lg-12 bg-light">{{obj.request_data|to_pretty}}</div>
37+
</div>
38+
<div class="row">
39+
<div class="col-md-12 text-center bg-dark text-white font-weight-bold p-1">Exception detail</div>
40+
<div class="col-md-12 bg-light"><pre> {{obj.traceback|escape|replace_new_line_with_br|safe}}</pre>
41+
</div>
42+
</div>
43+
44+
45+
{% endif %}
3646
{% endblock %}

error_tracker/django/templates/error_tracker/list.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{% extends 'error_tracker/base.html' %}
2+
23
{% block content_block %}
4+
<div class="row mb-4">
35
{% if error %}
46
<h1 class="text-center alert alert-danger">{{ error }}</h1>
57
{% else %}
6-
<h2 class="text-center">Errors Seen</h2>
7-
<table class="table table-condensed">
8+
<table class="table table-striped">
89
<thead>
910
<tr>
1011
<th>Host</th>
@@ -55,4 +56,5 @@ <h2 class="text-center">Errors Seen</h2>
5556
</div>
5657
{% endif %}
5758
{% endif %}
59+
</div>
5860
{% endblock %}

error_tracker/django/templatetags/error_tracker.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,34 @@
77
#
88

99
from django import template
10+
import json
11+
from django.utils.safestring import mark_safe
1012

1113
register = template.Library()
1214

1315

1416
@register.filter
1517
def replace_new_line_with_br(value):
1618
return value.replace("\n", "<br/>")
19+
20+
21+
@register.filter("to_pretty")
22+
def to_pretty(x):
23+
html = x
24+
try:
25+
x = json.loads(x)
26+
except Exception as e:
27+
try:
28+
x = x.replace("'", '"').replace("\\\\", "\\")
29+
x = json.loads(x)
30+
except Exception as e:
31+
pass
32+
pass
33+
34+
try:
35+
html = "<pre>{}</pre>".format(
36+
json.dumps(x, indent=4, sort_keys=True))
37+
except Exception as e:
38+
pass
39+
40+
return mark_safe(html)

error_tracker/django/utils.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
import re
1010
import json
11-
1211
from django.http import RawPostDataException
12+
from http.cookies import SimpleCookie
1313

1414
from error_tracker.libs.mixins import ContextBuilderMixin, NotificationMixin, ViewPermissionMixin
1515
from error_tracker.libs.utils import get_context_dict
@@ -56,8 +56,8 @@ def _get_form_data(request):
5656
def _get_headers(request):
5757
if request is not None:
5858
try:
59-
headers = request.headers.dict()
60-
except AttributeError:
59+
headers = parse_headers(request.headers)
60+
except AttributeError as e:
6161
regex = re.compile('^HTTP_')
6262
headers = dict((regex.sub('', header), value) for (header, value)
6363
in request.META.items() if header.startswith('HTTP_'))
@@ -88,7 +88,8 @@ def notify(self, request, exception,
8888
recipient_list=None):
8989
if recipient_list is not None and from_email is not None:
9090
send_mail(email_subject, email_body, from_email, recipient_list, fail_silently=True)
91-
91+
exception.notification_sent = True
92+
exception.save()
9293

9394
class DefaultDjangoViewPermission(ViewPermissionMixin):
9495

@@ -182,3 +183,34 @@ def capture_exception(request=None, exception=None, additional_context=None):
182183
from error_tracker.django.middleware import error_tracker
183184
error_tracker.capture_exception(request=request, exception=exception,
184185
additional_context=additional_context)
186+
187+
188+
def clean_value(x):
189+
x = x.value.replace('[["', "").replace('"]]', "").replace('"', "")
190+
return x
191+
192+
193+
def parse_headers(headers):
194+
"""
195+
Parse request headers to extract cookie.
196+
:param headers (request headers])
197+
:return: [dict]: return parse header with cookie as dict
198+
"""
199+
new_headers = {}
200+
for key, value in headers.items():
201+
try:
202+
# Pare each key, value from headers items and Test if could be "json loaded". If not, we set the correspondant value to empty except for cookie key.
203+
json.loads('{"%s":"%s"}' % (key, value))
204+
except Exception as e:
205+
if key in ["Cookie", "cookie"]:
206+
try:
207+
cookie = SimpleCookie()
208+
cookie.load(value)
209+
value = {k: clean_value(v) for k, v in cookie.items()}
210+
except Exception as e:
211+
value = ""
212+
else:
213+
value = ""
214+
215+
new_headers[key] = value
216+
return new_headers

error_tracker/django/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@
1010
from django.urls import reverse
1111
from django.views.decorators.http import require_GET
1212
from error_tracker.django import get_exception_model, get_view_permission
13+
from django.contrib.auth.decorators import login_required
1314

1415
model = get_exception_model()
1516

1617
view_permission = get_view_permission()
1718

1819

1920
def has_view_permission(func):
21+
@login_required
2022
def wrapper(request, *args, **kwargs):
2123
if view_permission(request):
2224
return func(request, *args, **kwargs)
2325
return HttpResponse(status=401)
24-
2526
return wrapper
2627

2728

0 commit comments

Comments
 (0)