Skip to content

Commit

Permalink
Add a Slack logging features
Browse files Browse the repository at this point in the history
Handler, Formatter and Filter
  • Loading branch information
Bruno Alla authored and browniebroke committed May 17, 2017
1 parent 6ee5bee commit e561958
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 31 deletions.
9 changes: 7 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
package_dir={'webhook_logger':
'webhook_logger'},
include_package_data=True,
install_requires=[],
install_requires=[
'requests',
],
license="MIT license",
zip_safe=False,
keywords='webhook_logger',
Expand All @@ -41,5 +43,8 @@
'Programming Language :: Python :: 3.5',
],
test_suite='tests',
tests_require=[]
tests_require=[
'requests_mock',
'mock'
]
)
45 changes: 45 additions & 0 deletions tests/test_slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import unittest

import mock
import requests_mock

from webhook_logger.slack import SlackHandler


class TestSlackLogger(unittest.TestCase):
def setUp(self):
self.rm = requests_mock.mock()
self.rm.start()

def tearDown(self):
self.rm.stop()

def _build_logger(self, name, url=None):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
h = SlackHandler(hook_url=url)
h.setLevel(logging.INFO)
logger.addHandler(h)
return logger

def test_incorrect_config(self):
logger = self._build_logger('incorrect')
logger.info("test", extra={'notify_slack': True})
self.assertEqual(self.rm.call_count, 0)

def test_correct_config(self):
self.rm.post('https://some-hook.com/abcde', json={'message': "OK"})
logger = self._build_logger('basic', 'https://some-hook.com/abcde')
logger.info("test", extra={'notify_slack': True})
self.assertEqual(self.rm.call_count, 1)

@mock.patch('webhook_logger.slack.settings')
def test_django_config(self, settings):
settings.SLACK_WEBHOOK_URL = 'https://some-hook.com/abcde'
self.rm.post('https://some-hook.com/abcde', json={'message': "OK"})
logger = self._build_logger('django')
logger.info("Test", extra={'notify_slack': True})
self.assertEqual(self.rm.call_count, 1)
28 changes: 0 additions & 28 deletions tests/test_webhook_logger.py

This file was deleted.

83 changes: 83 additions & 0 deletions webhook_logger/slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# -*- coding: utf-8
from __future__ import unicode_literals, absolute_import

import json
import logging

import requests
try:
from django.conf import settings
except ImportError:
settings = None

logger = logging.getLogger(__name__)


class SlackHandler(logging.Handler):
"""Logging handler to post to Slack to the webhook URL"""
def __init__(self, hook_url=None, *args, **kwargs):
super(SlackHandler, self).__init__(*args, **kwargs)
self._hook_url = hook_url
self.formatter = SlackFormatter()

@property
def hook_url(self):
if self._hook_url is None:
self._hook_url = getattr(settings, 'SLACK_WEBHOOK_URL', '')
return self._hook_url

def emit(self, record):
"""
Submit the record with a POST request
"""
try:
slack_data = self.format(record)
requests.post(
self.hook_url, data=slack_data,
headers={'Content-Type': 'application/json'}
)
except Exception:
self.handleError(record)

def filter(self, record):
"""
Disable the logger if hook_url isn't defined,
we don't want to do it in all environments (e.g local/CI)
"""
if not self.hook_url:
return 0
return super(SlackHandler, self).filter(record)


class SlackLogFilter(logging.Filter):
"""
Logging filter to decide when logging to Slack is requested, using
the `extra` kwargs:
`logger.info("...", extra={'notify_slack': True})`
"""
def filter(self, record):
return getattr(record, 'notify_slack', False)


class SlackFormatter(logging.Formatter):
def format(self, record):
"""
Format message content, timestamp when it was logged and a
coloured border depending on the severity of the message
"""
ret = {
'ts': record.created,
'text': record.getMessage(),
}
try:
loglevel_colour = {
'INFO': 'good',
'WARNING': 'warning',
'ERROR': '#E91E63',
'CRITICAL': 'danger',
}
ret['color'] = loglevel_colour[record.levelname]
except KeyError:
pass
return json.dumps({'attachments': [ret]})
1 change: 0 additions & 1 deletion webhook_logger/webhook_logger.py

This file was deleted.

0 comments on commit e561958

Please sign in to comment.