Instantly share code, notes, and snippets.

Embed
What would you like to do?
Django Slack Logger
import requests
import json
import time
import math
from copy import copy
from django.conf import settings
from django.utils.log import AdminEmailHandler
from django.views.debug import ExceptionReporter
class SlackExceptionHandler(AdminEmailHandler):
# replacing default django emit (https://github.com/django/django/blob/master/django/utils/log.py)
def emit(self, record, *args, **kwargs):
# original AdminEmailHandler "emit" method code (but without actually sending email)
try:
request = record.request
subject = '%s (%s IP): %s' % (
record.levelname,
('internal' if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS
else 'EXTERNAL'),
record.getMessage()
)
except Exception:
subject = '%s: %s' % (
record.levelname,
record.getMessage()
)
request = None
subject = self.format_subject(subject)
# Since we add a nicely formatted traceback on our own, create a copy
# of the log record without the exception data.
no_exc_record = copy(record)
no_exc_record.exc_info = None
no_exc_record.exc_text = None
if record.exc_info:
exc_info = record.exc_info
else:
exc_info = (None, record.getMessage(), None)
reporter = ExceptionReporter(request, is_email=True, *exc_info)
message = "%s\n\n%s" % (self.format(no_exc_record), reporter.get_traceback_text())
html_message = reporter.get_traceback_html() if self.include_html else None
#self.send_mail(subject, message, fail_silently=True, html_message=html_message)
# this is where original "emit" method code ends
# construct slack attachment detail fields
attachments = [
{
'title': subject,
'color': 'danger',
'fields': [
{
"title": "Level",
"value": record.levelname,
"short": True,
},
{
"title": "Method",
"value": request.method if request else 'No Request',
"short": True,
},
{
"title": "Path",
"value": request.path if request else 'No Request',
"short": True,
},
{
"title": "User",
"value": ( (request.user.username + ' (' + str(request.user.pk) + ')'
if request.user.is_authenticated else 'Anonymous' )
if request else 'No Request' ),
"short": True,
},
{
"title": "Status Code",
"value": record.status_code,
"short": True,
},
{
"title": "UA",
"value": ( request.META['HTTP_USER_AGENT']
if request and request.META else 'No Request' ),
"short": False,
},
{
"title": 'GET Params',
"value": json.dumps(request.GET) if request else 'No Request',
"short": False,
},
{
"title": "POST Data",
"value": json.dumps(request.POST) if request else 'No Request',
"short": False,
},
],
},
]
# add main error message body
# slack message attachment text has max of 8000 bytes
# lets split it up into 7900 bytes long chunks to be on the safe side
split = 7900
parts = range( math.ceil( len( message.encode('utf8') ) / split ) )
for part in parts:
start = 0 if part == 0 else split * part
end = split if part == 0 else split * part + split
# combine final text and prepend it with line breaks
# so the details in slack message will fully collapse
detail_text = '\r\n\r\n\r\n\r\n\r\n\r\n\r\n' + message[start:end]
attachments.append({
'color': 'danger',
'title': 'Details (Part {})'.format(part + 1),
'text': detail_text,
'ts': time.time(),
})
# construct main text
main_text = 'Error at ' + time.strftime("%A, %d %b %Y %H:%M:%S +0000", time.gmtime())
# construct data
data = {
'payload': json.dumps({'main_text': main_text,'attachments': attachments}),
}
# setup channel webhook
webhook_url = 'https://hooks.slack.com/services/xxx/xxx/xxx'
# send it
r = requests.post(webhook_url, data=data)
from django.utils.log import DEFAULT_LOGGING
LOGGING = DEFAULT_LOGGING
LOGGING['handlers']['slack_admins'] = {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'config.helpers.slack_logger.SlackExceptionHandler',
}
LOGGING['loggers']['django'] = {
'handlers': ['console', 'slack_admins'],
'level': 'INFO',
}
@Speedy1991

This comment has been minimized.

Speedy1991 commented Jun 7, 2018

This line
"value": json.dumps(request.POST) if request else 'No Request',
https://gist.github.com/DominikSerafin/0e14ea2ea60022201c9a0c04454a2926#file-a-slack_logger-py-L100
is realy dangerous. E.g. password filled forms will be exposed to Slack in cleartext

@DominikSerafin

This comment has been minimized.

Owner

DominikSerafin commented Jul 24, 2018

Hey, @Speedy1991 I'm not 100% sure it will work (since it was long time ago when I wrote this), but I subclassed AdminEmailHandler and used its emit method from Django standard email reporting and Django sensitive information filtering should work in this logger just as in email reporting - https://docs.djangoproject.com/en/1.11/howto/error-reporting/#filtering-sensitive-information

@chasetb

This comment has been minimized.

chasetb commented Nov 9, 2018

Do you find that this is still working with Django 2.1? I was running a modified version of this successfully until upgrading to 2.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment