From 34047e50e63e8b58dd5c45dbc51c13abdf337b93 Mon Sep 17 00:00:00 2001 From: Victor Ng Date: Mon, 10 Sep 2012 19:22:58 -0400 Subject: [PATCH] added a metlog backend for django-statsd (issue #11) --- .gitignore | 2 + .travis.yml | 2 +- django_statsd/clients/moz_metlog.py | 26 ++++++ django_statsd/mydatabase | 0 django_statsd/tests.py | 132 +++++++++++++++++++++++++++- docs/index.rst | 8 ++ optional.txt | 1 + 7 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 django_statsd/clients/moz_metlog.py create mode 100644 django_statsd/mydatabase create mode 100644 optional.txt diff --git a/.gitignore b/.gitignore index 539da74..78e544b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.py[co] +build +dist diff --git a/.travis.yml b/.travis.yml index 74b2d50..0a0724c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python python: - "2.6" - "2.7" -install: pip install -r requirements.txt --use-mirrors +install: pip install -r requirements.txt -r optional.txt --use-mirrors script: nosetests notifications: irc: "irc.mozilla.org#amo-bots" diff --git a/django_statsd/clients/moz_metlog.py b/django_statsd/clients/moz_metlog.py new file mode 100644 index 0000000..1771e34 --- /dev/null +++ b/django_statsd/clients/moz_metlog.py @@ -0,0 +1,26 @@ +from statsd.client import StatsClient +from django.conf import settings + + +class StatsClient(StatsClient): + """A client that pushes messages to metlog """ + + def __init__(self, *args, **kw): + super(StatsClient, self).__init__(*args, **kw) + if getattr(settings, 'METLOG', None) is None: + raise AttributeError( + "Metlog needs to be configured as settings.METLOG") + + self.metlog = settings.METLOG + + def timing(self, stat, delta, rate=1): + """Send new timing information. `delta` is in milliseconds.""" + self.metlog.timer_send(stat, delta, rate=rate) + + def incr(self, stat, count=1, rate=1): + """Increment a stat by `count`.""" + self.metlog.incr(stat, count, rate=rate) + + def decr(self, stat, count=1, rate=1): + """Decrement a stat by `count`.""" + self.metlog.incr(stat, -count, rate=rate) diff --git a/django_statsd/mydatabase b/django_statsd/mydatabase new file mode 100644 index 0000000..e69de29 diff --git a/django_statsd/tests.py b/django_statsd/tests.py index 0da85ad..a537d7e 100644 --- a/django_statsd/tests.py +++ b/django_statsd/tests.py @@ -1,7 +1,10 @@ +import json import logging import sys from django.conf import settings +from nose.exc import SkipTest +from nose import tools as nose_tools minimal = { 'DATABASES': { @@ -10,8 +13,9 @@ minimal = { 'NAME': 'mydatabase' } }, - 'ROOT_URLCONF':'', - 'STATSD_CLIENT': 'django_statsd.clients.null' + 'ROOT_URLCONF': '', + 'STATSD_CLIENT': 'django_statsd.clients.null', + 'METLOG': None } if not settings.configured: @@ -45,7 +49,6 @@ cfg = { } - @mock.patch.object(middleware.statsd, 'incr') class TestIncr(TestCase): @@ -135,6 +138,129 @@ class TestClient(unittest.TestCase): eq_(client.cache, {'testing|count': [[1, 1]]}) +class TestMetlogClient(unittest.TestCase): + + def check_metlog(self): + try: + from metlog.config import client_from_dict_config + return client_from_dict_config + except ImportError: + raise SkipTest("Metlog is not installed") + + @nose_tools.raises(AttributeError) + def test_no_metlog(self): + with mock.patch.object(settings, 'STATSD_CLIENT', + 'django_statsd.clients.moz_metlog'): + get_client() + + def test_get_client(self): + client_from_dict_config = self.check_metlog() + + METLOG_CONF = { + 'logger': 'django-statsd', + 'sender': { + 'class': 'metlog.senders.DebugCaptureSender', + }, + } + + metlog = client_from_dict_config(METLOG_CONF) + with mock.patch.object(settings, 'METLOG', metlog): + with mock.patch.object(settings, 'STATSD_CLIENT', + 'django_statsd.clients.moz_metlog'): + + client = get_client() + eq_(client.__module__, 'django_statsd.clients.moz_metlog') + + def test_metlog_incr(self): + client_from_dict_config = self.check_metlog() + + # Need to load within the test in case metlog is not installed + from metlog.config import client_from_dict_config + METLOG_CONF = { + 'logger': 'django-statsd', + 'sender': { + 'class': 'metlog.senders.DebugCaptureSender', + }, + } + + metlog = client_from_dict_config(METLOG_CONF) + with mock.patch.object(settings, 'METLOG', metlog): + with mock.patch.object(settings, 'STATSD_CLIENT', + 'django_statsd.clients.moz_metlog'): + + client = get_client() + eq_(len(client.metlog.sender.msgs), 0) + client.incr('testing') + eq_(len(client.metlog.sender.msgs), 1) + + msg = json.loads(client.metlog.sender.msgs[0]) + eq_(msg['severity'], 6) + eq_(msg['payload'], '1') + eq_(msg['fields']['rate'], 1) + eq_(msg['fields']['name'], 'testing') + eq_(msg['type'], 'counter') + + def test_metlog_decr(self): + client_from_dict_config = self.check_metlog() + + # Need to load within the test in case metlog is not installed + from metlog.config import client_from_dict_config + + METLOG_CONF = { + 'logger': 'django-statsd', + 'sender': { + 'class': 'metlog.senders.DebugCaptureSender', + }, + } + + metlog = client_from_dict_config(METLOG_CONF) + with mock.patch.object(settings, 'METLOG', metlog): + with mock.patch.object(settings, 'STATSD_CLIENT', + 'django_statsd.clients.moz_metlog'): + + client = get_client() + eq_(len(client.metlog.sender.msgs), 0) + client.decr('testing') + eq_(len(client.metlog.sender.msgs), 1) + + msg = json.loads(client.metlog.sender.msgs[0]) + eq_(msg['severity'], 6) + eq_(msg['payload'], '-1') + eq_(msg['fields']['rate'], 1) + eq_(msg['fields']['name'], 'testing') + eq_(msg['type'], 'counter') + + def test_metlog_timing(self): + client_from_dict_config = self.check_metlog() + + # Need to load within the test in case metlog is not installed + from metlog.config import client_from_dict_config + + METLOG_CONF = { + 'logger': 'django-statsd', + 'sender': { + 'class': 'metlog.senders.DebugCaptureSender', + }, + } + + metlog = client_from_dict_config(METLOG_CONF) + with mock.patch.object(settings, 'METLOG', metlog): + with mock.patch.object(settings, 'STATSD_CLIENT', + 'django_statsd.clients.moz_metlog'): + + client = get_client() + eq_(len(client.metlog.sender.msgs), 0) + client.timing('testing', 512, rate=2) + eq_(len(client.metlog.sender.msgs), 1) + + msg = json.loads(client.metlog.sender.msgs[0]) + eq_(msg['severity'], 6) + eq_(msg['payload'], '512') + eq_(msg['fields']['rate'], 2) + eq_(msg['fields']['name'], 'testing') + eq_(msg['type'], 'timer') + + # This is primarily for Zamboni, which loads in the custom middleware # classes, one of which, breaks posts to our url. Let's stop that. @mock.patch.object(settings, 'MIDDLEWARE_CLASSES', []) diff --git a/docs/index.rst b/docs/index.rst index 77d764e..bb534d7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -73,6 +73,14 @@ First off, pick your client, one of: Just writes the values to a log file using Python's logging module. +- django_statsd.clients.moz_metlog + + Use this to route messages through + _metlog: http://github.com/mozilla-services/metlog-py. Note that + using metlog will require you to bind the metlog instance to bind + the metlog client instance as settings.METLOG. + + Usage ----- diff --git a/optional.txt b/optional.txt new file mode 100644 index 0000000..b0cc9da --- /dev/null +++ b/optional.txt @@ -0,0 +1 @@ +metlog-py