Incorporate feedback from PR

Use extra keyword in logger, more pythonic
capture response code as a rate metric
This commit is contained in:
Alexis Le-Quoc 2014-05-31 00:10:51 -04:00
parent de9b05c774
commit 89a3c96700
3 changed files with 74 additions and 32 deletions

View File

@ -479,9 +479,9 @@ class Arbiter(object):
self.kill_worker(pid, signal.SIGQUIT) self.kill_worker(pid, signal.SIGQUIT)
self.log.info("%d workers", self.log.info("%d workers",
metric="gunicorn.workers", extra={"metric": "gunicorn.workers",
value=len(self.WORKERS), "value": len(self.WORKERS),
mtype="gauge") "mtype": "gauge"})
def spawn_worker(self): def spawn_worker(self):
self.worker_age += 1 self.worker_age += 1

View File

@ -3,8 +3,8 @@
# This file is part of gunicorn released under the MIT license. # This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information. # See the NOTICE for more information.
"""Bare-bones implementation of statsD's protocol, client-side "Bare-bones implementation of statsD's protocol, client-side"
"""
import socket import socket
from gunicorn.glogging import Logger from gunicorn.glogging import Logger
@ -33,22 +33,18 @@ class Statsd(Logger):
# Log errors and warnings # Log errors and warnings
def critical(self, msg, *args, **kwargs): def critical(self, msg, *args, **kwargs):
self._sanitize(kwargs)
Logger.critical(self, msg, *args, **kwargs) Logger.critical(self, msg, *args, **kwargs)
self.increment("gunicorn.log.critical", 1) self.increment("gunicorn.log.critical", 1)
def error(self, msg, *args, **kwargs): def error(self, msg, *args, **kwargs):
self._sanitize(kwargs)
Logger.error(self, msg, *args, **kwargs) Logger.error(self, msg, *args, **kwargs)
self.increment("gunicorn.log.error", 1) self.increment("gunicorn.log.error", 1)
def warning(self, msg, *args, **kwargs): def warning(self, msg, *args, **kwargs):
self._sanitize(kwargs)
Logger.warning(self, msg, *args, **kwargs) Logger.warning(self, msg, *args, **kwargs)
self.increment("gunicorn.log.warning", 1) self.increment("gunicorn.log.warning", 1)
def exception(self, msg, *args, **kwargs): def exception(self, msg, *args, **kwargs):
self._sanitize(kwargs)
Logger.exception(self, msg, *args, **kwargs) Logger.exception(self, msg, *args, **kwargs)
self.increment("gunicorn.log.exception", 1) self.increment("gunicorn.log.exception", 1)
@ -56,28 +52,27 @@ class Statsd(Logger):
def info(self, msg, *args, **kwargs): def info(self, msg, *args, **kwargs):
"""Log a given statistic if metric, value and type are present """Log a given statistic if metric, value and type are present
""" """
metric = kwargs.get(METRIC_VAR, None) extra = kwargs.get("extra", None)
value = kwargs.get(VALUE_VAR, None) if extra is not None:
typ = kwargs.get(MTYPE_VAR, None) metric = extra.get(METRIC_VAR, None)
if metric and value and typ: value = extra.get(VALUE_VAR, None)
self._sanitize(kwargs) typ = extra.get(MTYPE_VAR, None)
if typ == GAUGE_TYPE: if metric and value and typ:
self.gauge(metric, value) if typ == GAUGE_TYPE:
elif typ == COUNTER_TYPE: self.gauge(metric, value)
self.increment(metric, value) elif typ == COUNTER_TYPE:
elif typ == HISTOGRAM_TYPE: self.increment(metric, value)
self.histogram(metric, value) elif typ == HISTOGRAM_TYPE:
else: self.histogram(metric, value)
pass else:
pass
Logger.info(self, msg, *args, **kwargs) Logger.info(self, msg, *args, **kwargs)
# skip the run-of-the-mill logs # skip the run-of-the-mill logs
def debug(self, msg, *args, **kwargs): def debug(self, msg, *args, **kwargs):
self._sanitize(kwargs)
Logger.debug(self, msg, *args, **kwargs) Logger.debug(self, msg, *args, **kwargs)
def log(self, lvl, msg, *args, **kwargs): def log(self, lvl, msg, *args, **kwargs):
self._sanitize(kwargs)
Logger.log(self, lvl, msg, *args, **kwargs) Logger.log(self, lvl, msg, *args, **kwargs)
# access logging # access logging
@ -89,14 +84,7 @@ class Statsd(Logger):
duration_in_s = request_time.seconds + float(request_time.microseconds)/10**6 duration_in_s = request_time.seconds + float(request_time.microseconds)/10**6
self.histogram("gunicorn.request.duration", duration_in_s) self.histogram("gunicorn.request.duration", duration_in_s)
self.increment("gunicorn.requests", 1) self.increment("gunicorn.requests", 1)
self.increment("gunicorn.request.status.%d" % int(resp.status), 1)
def _sanitize(self, kwargs):
"""Drop stasd keywords from the logger"""
for k in (METRIC_VAR, VALUE_VAR, MTYPE_VAR):
try:
kwargs.pop(k)
except KeyError:
pass
# statsD methods # statsD methods
# you can use those directly if you want # you can use those directly if you want

54
tests/test_009-statsd.py Normal file
View File

@ -0,0 +1,54 @@
from datetime import timedelta
import t
from gunicorn.config import Config
from gunicorn.instrument.statsd import Statsd
class TestException(Exception): pass
class MockSocket(object):
"Pretend to be a UDP socket"
def __init__(self, failp):
self.failp = failp
self.msgs = [] # accumulate messages for later inspection
def send(self, msg):
if self.failp:
raise TestException("Should not interrupt the logger")
self.msgs.append(msg)
def reset(self):
self.msgs = []
class MockResponse(object):
def __init__(self, status):
self.status = status
def test_statsd_fail():
"UDP socket fails"
logger = Statsd(Config())
logger.sock = MockSocket(True)
logger.info("No impact on logging")
logger.debug("No impact on logging")
logger.critical("No impact on logging")
logger.error("No impact on logging")
logger.warning("No impact on logging")
logger.exception("No impact on logging")
def test_instrument():
logger = Statsd(Config())
logger.sock = MockSocket(False)
logger.info("Blah", extra={"mtype": "gauge", "metric": "gunicorn.test", "value": 666})
t.eq(logger.sock.msgs[0], "gunicorn.test:666|g")
logger.sock.reset()
logger.critical("Boom")
t.eq(logger.sock.msgs[0], "gunicorn.log.critical:1|c|@1.0")
logger.sock.reset()
logger.access(MockResponse(200), None, {}, timedelta(seconds=7))
t.eq(logger.sock.msgs[0], "gunicorn.request.duration:7.0|ms")
t.eq(logger.sock.msgs[1], "gunicorn.requests:1|c|@1.0")
t.eq(logger.sock.msgs[2], "gunicorn.request.status.200:1|c|@1.0")