Exemplo n.º 1
0
class StatsdHandler(logging.Handler):

    DEFAULT_PUBLISH_TEMPLATES = {
        'default': ['%(logger)s;%(attr)s;%(metric_name)s']
    }

    DEFAULT_CONFIG = {
        'app_key': 'default_app_key',
        'host': 'localhost',
        'port': 8125,
        'sample_rate': 1,
        'disabled': False
    }

    def __init__(self, args=None, config_path=None):
        logging.Handler.__init__(self)
        if args is not None and args != '':
            config_path = args
        if not os.path.isfile(config_path):
            raise Exception('Invalid path to config file.')
        with open(config_path) as config_file_obj:
            self.config = load(config_file_obj.read())
        for key in self.DEFAULT_CONFIG:
            setattr(
                self, key,
                self.config.get('main', {}).get(key, None)
                or self.DEFAULT_CONFIG[key])

        # Initialize Statsd Client
        self.statsd = DogStatsd(host=self.host,
                                port=self.port,
                                namespace=self.app_key)

        self.publish_templates = self.DEFAULT_PUBLISH_TEMPLATES
        publish_templates = self.config.get('publish_templates', {})
        self.publish_templates.update(publish_templates)
        self.counters = self.config.get('counters', {})
        self.gauges = self.config.get('gauges', {})
        self.timers = self.config.get('timers', [])
        self.histograms = self.config.get('histograms', {})
        self.sets = self.config.get('sets', {})
        self.timers_start_keys = self._get_timers_keys_list('start')
        self.timers_end_keys = self._get_timers_keys_list('end')
        self.timers_value_keys = self._get_timers_keys_list('value')

    def _get_timers_keys_list(self, key_prefix):
        keys = []
        for t in self.timers:
            key = t.get('{}_attr_name'.format(key_prefix), None)
            if key is not None:
                keys.append(key)
        return keys

    def _get_timer_params(self,
                          key_prefix,
                          value_attr_name=None,
                          start_attr_name=None,
                          end_attr_name=None):
        for t in self.timers:
            if key_prefix == 'start':
                if (t.get('start_attr_name', None) == start_attr_name
                        and t.get('end_attr_name', None) == end_attr_name):
                    return [
                        t.get('name', start_attr_name),
                        t.get('publish_template', 'default')
                    ]
            elif key_prefix == 'value':
                if (t.get('value_attr_name', None) == value_attr_name):
                    return [
                        t.get('name', value_attr_name),
                        t.get('publish_template', 'default')
                    ]
            else:
                return None, None

    def _publish_count(self, subname, value):
        try:
            if float(value) > 0:
                self.statsd.increment(subname, value)
            else:
                self.statsd.decrement(subname, value)
        except:
            pass

    def _publish_timer(self, subname, value):
        try:
            self.statsd.timing(subname, value)
        except:
            pass

    def _process_counter_metrics(self, attr, record):
        lookup_value = self.counters[attr].get('lookup_value', 'None')
        value = getattr(record, attr, None)
        value_type = self.counters[attr].get('value_type', 'key')
        value_equals = self.counters[attr].get('value_equals', [])
        publish_template = self.counters[attr].get('publish_template',
                                                   'default')
        if publish_template not in self.publish_templates:
            publish_template = 'default'
        if value_type == 'value':
            counter_subname = attr
            counter_value = value if value is not None else 1
        elif value_type == 'key':
            counter_subname = value if value is not None else lookup_value
            if len(value_equals) > 0 and value not in value_equals:
                return
            counter_value = 1
        else:
            return
        for pt in self.publish_templates[publish_template]:
            subname = pt % dict(
                logger=record.name, attr=attr, metric_name=counter_subname)
            self._publish_count(subname, counter_value)

    def _process_timer_metrics(self, attr, record, key_prefix):
        if key_prefix == 'start':
            start_attr_value = getattr(record, attr, None)
            if start_attr_value is None:
                return
            start_attr_name = attr
            end_attr_value = None
            for end_attr_name in self.timers_end_keys:
                end_attr_value = getattr(record, end_attr_name, None)
                if end_attr_value is not None:
                    try:
                        timer_value = float(end_attr_value) -\
                            float(start_attr_value)
                        timer_name, publish_template =\
                            self._get_timer_params(
                                key_prefix, start_attr_name=start_attr_name,
                                end_attr_name=end_attr_name)
                        if publish_template not in self.publish_templates:
                            publish_template = 'default'
                        if timer_name is not None:
                            for pt in self.publish_templates[publish_template]:
                                subname = pt % dict(logger=record.name,
                                                    attr='',
                                                    metric_name=timer_name)
                                self._publish_timer(subname, timer_value)
                    except:
                        pass
        elif key_prefix == 'value':
            timer_value = getattr(record, attr, None)
            if timer_value is None:
                return
            timer_name, publish_template = self._get_timer_params(
                key_prefix, value_attr_name=attr)
            if publish_template not in self.publish_templates:
                publish_template = 'default'
            if timer_name is not None:
                for pt in self.publish_templates[publish_template]:
                    subname = pt % dict(
                        logger=record.name, attr='', metric_name=timer_name)
                    self._publish_timer(subname, timer_value)

    def _get_publish_template(self, metric_type, attr):
        metric_types_dict = getattr(self, metric_type, {})
        if metric_type == 'sets':
            publisher = self.statsd.set
        elif metric_type == 'gauges':
            publisher = self.statsd.gauge
        elif metric_type == 'histograms':
            publisher = self.statsd.histogram
        else:
            publisher = None
        publish_template =\
            metric_types_dict[attr].get('publish_template', 'default')
        if publish_template not in self.publish_templates:
            publish_template = 'default'
        return publish_template, publisher

    def _process_metrics(self, metric_type, attr, record):
        value = getattr(record, attr, None)
        if value is not None:
            publish_template, publisher =\
                self._get_publish_template(metric_type, attr)
            for pt in self.publish_templates[publish_template]:
                subname = pt % dict(
                    logger=record.name, attr=attr, metric_name=attr)
                publisher(subname, value)

    def emit(self, record):
        for attr in dir(record):
            if attr in self.counters:
                self._process_counter_metrics(attr, record)
            elif attr in self.gauges:
                self._process_metrics('gauges', attr, record)
            elif attr in self.timers_start_keys:
                self._process_timer_metrics(attr, record, 'start')
            elif attr in self.timers_value_keys:
                self._process_timer_metrics(attr, record, 'value')
            elif attr in self.histograms:
                self._process_metrics('histograms', attr, record)
            elif attr in self.sets:
                self._process_metrics('sets', attr, record)
            else:
                continue
class DogStatsdAdapter(object):
    """
    A wrapper around DogStatsd that supports the full statsd.StatsClient interface
    Note that `tags` is available on all these methods, but this is not supported by statsd.StatsClient. It has been
    added, but should only be used when you are CERTAIN that you will be using this class (or something similar)
    """
    def __init__(self,
                 host='localhost',
                 port=8125,
                 prefix=None,
                 maxudpsize=512,
                 ipv6=False):
        if ipv6:
            _log.warning("DogStatsdAdapter() was 'ipv6'. This is ignored")

        self._dd_client = DogStatsd(host=host,
                                    port=port,
                                    namespace=prefix,
                                    max_buffer_size=maxudpsize)

    def timer(self, stat, rate=1, tags=None):
        self._dd_client.timed(metric=stat,
                              sample_rate=rate,
                              use_ms=True,
                              tags=tags)

    def timing(self, stat, delta, rate=1, tags=None):
        """Send new timing information. `delta` is in milliseconds."""
        self._dd_client.timing(metric=stat,
                               value=delta,
                               sample_rate=rate,
                               tags=tags)

    def incr(self, stat, count=1, rate=1, tags=None):
        """Increment a stat by `count`."""
        self._dd_client.increment(metric=stat,
                                  value=count,
                                  sample_rate=rate,
                                  tags=tags)

    def decr(self, stat, count=1, rate=1, tags=None):
        """Decrement a stat by `count`."""
        self._dd_client.decrement(metric=stat,
                                  value=count,
                                  sample_rate=rate,
                                  tags=tags)

    def gauge(self, stat, value, rate=1, delta=False, tags=None):
        """Set a gauge value."""
        self._dd_client.gauge(metric=stat,
                              value=value,
                              sample_rate=rate,
                              tags=tags)
        if delta:
            _log.warning(
                "DogStatsdAdapter was passed a gauge with 'delta' set. This is ignored in datadog"
            )

    def set(self, stat, value, rate=1, tags=None):
        """Set a set value."""
        self._dd_client.set(metric=stat,
                            value=value,
                            sample_rate=rate,
                            tags=tags)

    def histogram(self, stat, value, rate=1, tags=None):
        """
        Sample a histogram value, optionally setting tags and a sample rate.
        This is not supported by statsd.StatsClient. Use with caution!
        """
        self._dd_client.set(metric=stat,
                            value=value,
                            sample_rate=rate,
                            tags=tags)
Exemplo n.º 3
0
class DatadogStatsClient:
    """Statsd compliant datadog client."""
    def __init__(self,
                 host: str = 'localhost',
                 port: int = 8125,
                 prefix: str = 'faust-app',
                 rate: float = 1.0,
                 **kwargs: Any) -> None:
        self.client = DogStatsd(host=host,
                                port=port,
                                namespace=prefix,
                                **kwargs)
        self.rate = rate
        self.sanitize_re = re.compile(r'[^0-9a-zA-Z_]')
        self.re_substitution = '_'

    def gauge(self, metric: str, value: float, labels: Dict = None) -> None:
        self.client.gauge(
            metric,
            value=value,
            tags=self._encode_labels(labels),
            sample_rate=self.rate,
        )

    def increment(self,
                  metric: str,
                  value: float = 1.0,
                  labels: Dict = None) -> None:
        self.client.increment(
            metric,
            value=value,
            tags=self._encode_labels(labels),
            sample_rate=self.rate,
        )

    def incr(self, metric: str, count: int = 1) -> None:
        """Statsd compatibility."""
        self.increment(metric, value=count)

    def decrement(self,
                  metric: str,
                  value: float = 1.0,
                  labels: Dict = None) -> float:
        return self.client.decrement(
            metric,
            value=value,
            tags=self._encode_labels(labels),
            sample_rate=self.rate,
        )

    def decr(self, metric: str, count: float = 1.0) -> None:
        """Statsd compatibility."""
        self.decrement(metric, value=count)

    def timing(self, metric: str, value: float, labels: Dict = None) -> None:
        self.client.timing(
            metric,
            value=value,
            tags=self._encode_labels(labels),
            sample_rate=self.rate,
        )

    def timed(self,
              metric: str = None,
              labels: Dict = None,
              use_ms: bool = None) -> float:
        return self.client.timed(
            metric=metric,
            tags=self._encode_labels(labels),
            sample_rate=self.rate,
            use_ms=use_ms,
        )

    def histogram(self,
                  metric: str,
                  value: float,
                  labels: Dict = None) -> None:
        self.client.histogram(
            metric,
            value=value,
            tags=self._encode_labels(labels),
            sample_rate=self.rate,
        )

    def _encode_labels(self, labels: Optional[Dict]) -> Optional[List[str]]:
        def sanitize(s: str) -> str:
            return self.sanitize_re.sub(self.re_substitution, str(s))

        return [f'{sanitize(k)}:{sanitize(v)}'
                for k, v in labels.items()] if labels else None
Exemplo n.º 4
0
class DogStatsdMetrics(Metrics):
    def __init__(self,
                 id,
                 prefix=None,
                 tags=None,
                 host="127.0.0.1",
                 port=8125):
        self.id = id
        self.prefix = prefix
        self.tags = tags or {}
        self.host = host
        self.port = port
        self.tags["instance"] = id

    def setup(self):
        from datadog.dogstatsd import DogStatsd

        self.client = DogStatsd(host=self.host, port=self.port)

    def gauge(self, metric, value, tags=None, sample_rate=1):
        self.client.gauge(
            self._get_key(metric),
            value,
            sample_rate=sample_rate,
            tags=self._get_tags(tags),
        )

    def increment(self, metric, value=1, tags=None, sample_rate=1):
        self.client.increment(
            self._get_key(metric),
            value,
            sample_rate=sample_rate,
            tags=self._get_tags(tags),
        )

    def decrement(self, metric, value=1, tags=None, sample_rate=1):
        self.client.decrement(
            self._get_key(metric),
            value,
            sample_rate=sample_rate,
            tags=self._get_tags(tags),
        )

    def histogram(self, metric, value, tags=None, sample_rate=1):
        self.client.histogram(
            self._get_key(metric),
            value,
            sample_rate=sample_rate,
            tags=self._get_tags(tags),
        )

    def timing(self, metric, value, tags=None, sample_rate=1):
        self.client.timing(
            self._get_key(metric),
            value,
            sample_rate=sample_rate,
            tags=self._get_tags(tags),
        )

    def timed(self, metric, tags=None, sample_rate=1, use_ms=None):
        self.client.timed(
            self._get_key(metric),
            sample_rate=sample_rate,
            tags=self._get_tags(tags),
            use_ms=use_ms,
        )