You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
4.3 KiB
124 lines
4.3 KiB
7 years ago
|
# -*- coding: utf-8 -
|
||
|
#
|
||
|
# This file is part of gunicorn released under the MIT license.
|
||
|
# See the NOTICE for more information.
|
||
|
|
||
|
"Bare-bones implementation of statsD's protocol, client-side"
|
||
|
|
||
|
import socket
|
||
|
import logging
|
||
|
from re import sub
|
||
|
|
||
|
from gunicorn.glogging import Logger
|
||
|
from gunicorn import six
|
||
|
|
||
|
# Instrumentation constants
|
||
|
METRIC_VAR = "metric"
|
||
|
VALUE_VAR = "value"
|
||
|
MTYPE_VAR = "mtype"
|
||
|
GAUGE_TYPE = "gauge"
|
||
|
COUNTER_TYPE = "counter"
|
||
|
HISTOGRAM_TYPE = "histogram"
|
||
|
|
||
|
class Statsd(Logger):
|
||
|
"""statsD-based instrumentation, that passes as a logger
|
||
|
"""
|
||
|
def __init__(self, cfg):
|
||
|
"""host, port: statsD server
|
||
|
"""
|
||
|
Logger.__init__(self, cfg)
|
||
|
self.prefix = sub(r"^(.+[^.]+)\.*$", "\\g<1>.", cfg.statsd_prefix)
|
||
|
try:
|
||
|
host, port = cfg.statsd_host
|
||
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
|
self.sock.connect((host, int(port)))
|
||
|
except Exception:
|
||
|
self.sock = None
|
||
|
|
||
|
# Log errors and warnings
|
||
|
def critical(self, msg, *args, **kwargs):
|
||
|
Logger.critical(self, msg, *args, **kwargs)
|
||
|
self.increment("gunicorn.log.critical", 1)
|
||
|
|
||
|
def error(self, msg, *args, **kwargs):
|
||
|
Logger.error(self, msg, *args, **kwargs)
|
||
|
self.increment("gunicorn.log.error", 1)
|
||
|
|
||
|
def warning(self, msg, *args, **kwargs):
|
||
|
Logger.warning(self, msg, *args, **kwargs)
|
||
|
self.increment("gunicorn.log.warning", 1)
|
||
|
|
||
|
def exception(self, msg, *args, **kwargs):
|
||
|
Logger.exception(self, msg, *args, **kwargs)
|
||
|
self.increment("gunicorn.log.exception", 1)
|
||
|
|
||
|
# Special treatement for info, the most common log level
|
||
|
def info(self, msg, *args, **kwargs):
|
||
|
self.log(logging.INFO, msg, *args, **kwargs)
|
||
|
|
||
|
# skip the run-of-the-mill logs
|
||
|
def debug(self, msg, *args, **kwargs):
|
||
|
self.log(logging.DEBUG, msg, *args, **kwargs)
|
||
|
|
||
|
def log(self, lvl, msg, *args, **kwargs):
|
||
|
"""Log a given statistic if metric, value and type are present
|
||
|
"""
|
||
|
try:
|
||
|
extra = kwargs.get("extra", None)
|
||
|
if extra is not None:
|
||
|
metric = extra.get(METRIC_VAR, None)
|
||
|
value = extra.get(VALUE_VAR, None)
|
||
|
typ = extra.get(MTYPE_VAR, None)
|
||
|
if metric and value and typ:
|
||
|
if typ == GAUGE_TYPE:
|
||
|
self.gauge(metric, value)
|
||
|
elif typ == COUNTER_TYPE:
|
||
|
self.increment(metric, value)
|
||
|
elif typ == HISTOGRAM_TYPE:
|
||
|
self.histogram(metric, value)
|
||
|
else:
|
||
|
pass
|
||
|
|
||
|
# Log to parent logger only if there is something to say
|
||
|
if msg:
|
||
|
Logger.log(self, lvl, msg, *args, **kwargs)
|
||
|
except Exception:
|
||
|
Logger.warning(self, "Failed to log to statsd", exc_info=True)
|
||
|
|
||
|
# access logging
|
||
|
def access(self, resp, req, environ, request_time):
|
||
|
"""Measure request duration
|
||
|
request_time is a datetime.timedelta
|
||
|
"""
|
||
|
Logger.access(self, resp, req, environ, request_time)
|
||
|
duration_in_ms = request_time.seconds * 1000 + float(request_time.microseconds) / 10 ** 3
|
||
|
status = resp.status
|
||
|
if isinstance(status, str):
|
||
|
status = int(status.split(None, 1)[0])
|
||
|
self.histogram("gunicorn.request.duration", duration_in_ms)
|
||
|
self.increment("gunicorn.requests", 1)
|
||
|
self.increment("gunicorn.request.status.%d" % status, 1)
|
||
|
|
||
|
# statsD methods
|
||
|
# you can use those directly if you want
|
||
|
def gauge(self, name, value):
|
||
|
self._sock_send("{0}{1}:{2}|g".format(self.prefix, name, value))
|
||
|
|
||
|
def increment(self, name, value, sampling_rate=1.0):
|
||
|
self._sock_send("{0}{1}:{2}|c|@{3}".format(self.prefix, name, value, sampling_rate))
|
||
|
|
||
|
def decrement(self, name, value, sampling_rate=1.0):
|
||
|
self._sock_send("{0}{1}:-{2}|c|@{3}".format(self.prefix, name, value, sampling_rate))
|
||
|
|
||
|
def histogram(self, name, value):
|
||
|
self._sock_send("{0}{1}:{2}|ms".format(self.prefix, name, value))
|
||
|
|
||
|
def _sock_send(self, msg):
|
||
|
try:
|
||
|
if isinstance(msg, six.text_type):
|
||
|
msg = msg.encode("ascii")
|
||
|
if self.sock:
|
||
|
self.sock.send(msg)
|
||
|
except Exception:
|
||
|
Logger.warning(self, "Error sending message to statsd", exc_info=True)
|