diff options
author | Ben Burry <bburry@etsy.com> | 2015-08-31 19:31:49 +0100 |
---|---|---|
committer | Ben Burry <bburry@etsy.com> | 2015-11-13 17:33:02 +0000 |
commit | a65f919bebe19677a8eac208ddedd12fca296e68 (patch) | |
tree | 5bade5490594cde3b27c38ba278019176f6f6f37 | |
parent | 1b5560db7db5c6c7d3745e49fb369eadd705d8f0 (diff) | |
download | logster-a65f919bebe19677a8eac208ddedd12fca296e68.zip logster-a65f919bebe19677a8eac208ddedd12fca296e68.tar.gz logster-a65f919bebe19677a8eac208ddedd12fca296e68.tar.bz2 |
Provide pluggable output classes
-rwxr-xr-x | bin/logster | 239 | ||||
-rw-r--r-- | logster/logster_helper.py | 96 | ||||
-rw-r--r-- | logster/outputs/CloudwatchOutput.py | 134 | ||||
-rw-r--r-- | logster/outputs/GangliaOutput.py | 32 | ||||
-rw-r--r-- | logster/outputs/GraphiteOutput.py | 56 | ||||
-rw-r--r-- | logster/outputs/NSCAOutput.py | 46 | ||||
-rw-r--r-- | logster/outputs/StatsdOutput.py | 33 | ||||
-rw-r--r-- | logster/outputs/StdoutOutput.py | 23 | ||||
-rw-r--r-- | logster/outputs/__init__.py | 0 | ||||
-rw-r--r-- | logster/outputs/builtin.py | 6 |
10 files changed, 394 insertions, 271 deletions
diff --git a/bin/logster b/bin/logster index 2046d86..9e3c120 100755 --- a/bin/logster +++ b/bin/logster @@ -43,27 +43,54 @@ import os import sys -import re import optparse import stat import logging.handlers import logging.config -import socket import traceback import platform +import inspect -from time import time, strftime, gmtime +from time import time from math import floor # Local dependencies -from logster.logster_helper import LogsterParsingException, LockingError, CloudWatch, CloudWatchException +from logster.logster_helper import LogsterParsingException, LockingError, LogsterOutput from logster.tailers.logtailtailer import LogtailTailer +import logster.outputs.builtin + # Globals -gmetric = "/usr/bin/gmetric" log_dir = "/var/log/logster" state_dir = "/var/run" -send_nsca = "/usr/sbin/send_nsca" + + +builtin_output_classes = dict([ + (cls.shortname, cls) for name, cls in inspect.getmembers(logster.outputs.builtin) + if inspect.isclass(cls) and issubclass(cls, LogsterOutput) and not cls is LogsterOutput +]) + + +def load_output_klass(option, opt, value, parser): + outputs = parser.values.outputs if parser.values.outputs else [] + + output_klass = builtin_output_classes.get(value) + if not output_klass: + try: + module_name, output_name = value.rsplit('.', 1) + module = __import__(module_name, globals(), locals(), [output_name]) + output_klass = getattr(module, output_name) + except: + parser.error("Unable to load output class %s" % value) + + if output_klass not in outputs: + add_options = getattr(output_klass, "add_options", None) + if callable(add_options): + add_options(parser) + + outputs.append(output_klass) + setattr(parser.values, option.dest, outputs) + script_start_time = time() @@ -86,42 +113,22 @@ cmdline.add_option('--parser-help', action='store_true', help='Print usage and options for the selected parser') cmdline.add_option('--parser-options', action='store', help='Options to pass to the logster parser such as "-o VALUE --option2 VALUE". These are parser-specific and passed directly to the parser.') -cmdline.add_option('--gmetric-options', action='store', - help='Options to pass to gmetric such as "-d 180 -c /etc/ganglia/gmond.conf" (default). These are passed directly to gmetric.', - default='-d 180 -c /etc/ganglia/gmond.conf') -cmdline.add_option('--graphite-host', action='store', - help='Hostname and port for Graphite collector, e.g. graphite.example.com:2003') -cmdline.add_option('--graphite-protocol', action='store', default='tcp', - choices=('tcp', 'udp'), - help='Specify graphite socket protocol. Options are tcp and udp. Defaults to tcp.') -cmdline.add_option('--statsd-host', action='store', - help='Hostname and port for statsd collector, e.g. statsd.example.com:8125') -cmdline.add_option('--aws-key', action='store', default=os.getenv('AWS_ACCESS_KEY_ID'), - help='Amazon credential key') -cmdline.add_option('--aws-secret-key', action='store', default=os.getenv('AWS_SECRET_ACCESS_KEY_ID'), - help='Amazon credential secret key') -cmdline.add_option('--nsca-host', action='store', - help='Hostname and port for NSCA daemon, e.g. nsca.example.com:5667') -cmdline.add_option('--nsca-service-hostname', action='store', - help='<host_name> value to use in nsca passive service check. Default is \"%default\"', - default=socket.gethostname()) cmdline.add_option('--state-dir', '-s', action='store', default=state_dir, help='Where to store the tailer state file. Default location %s' % state_dir) cmdline.add_option('--log-dir', '-l', action='store', default=log_dir, help='Where to store the logster logfile. Default location %s' % log_dir) cmdline.add_option('--log-conf', action='store', default=None, help='Logging configuration file. None by default') -cmdline.add_option('--output', '-o', action='append', - choices=('graphite', 'ganglia', 'stdout', 'cloudwatch', 'nsca', 'statsd'), - help="Where to send metrics (can specify multiple times). Choices are 'graphite', 'ganglia', 'cloudwatch', 'nsca' , 'statsd', or 'stdout'.") -cmdline.add_option('--stdout-separator', action='store', default="_", dest="stdout_separator", - help='Seperator between prefix/suffix and name for stdout. Default is \"%default\".') +cmdline.add_option('--output', '-o', action='callback', callback=load_output_klass, type="string", dest="outputs", metavar="OUTPUT", + help="Where to send metrics (can specify multiple times).\ + Choices are %s or a fully qualified Python class name" % ', '.join(builtin_output_classes.keys())) cmdline.add_option('--dry-run', '-d', action='store_true', default=False, help='Parse the log file but send stats to standard output.') cmdline.add_option('--debug', '-D', action='store_true', default=False, help='Provide more verbose logging for debugging.') options, arguments = cmdline.parse_args() + if options.parser_help: options.parser_options = '-h' @@ -145,20 +152,11 @@ else: if not options.output: cmdline.print_help() cmdline.error("Supply where the data should be sent with -o (or --output).") -if 'graphite' in options.output and not options.graphite_host: - cmdline.print_help() - cmdline.error("You must supply --graphite-host when using 'graphite' as an output type.") -if 'cloudwatch' in options.output and not options.aws_key and not options.aws_secret_key: - cmdline.print_help() - cmdline.error("You must supply --aws-key and --aws-secret-key or Set environment variables. AWS_ACCESS_KEY_ID for --aws-key, AWS_SECRET_ACCESS_KEY_ID for --aws-secret-key") -if 'nsca' in options.output and not options.nsca_host: - cmdline.print_help() - cmdline.error("You must supply --nsca-host when using 'nsca' as an output type.") -class_name = arguments[0] -if class_name.find('.') == -1: +parser_klass_name = arguments[0] +if parser_klass_name.find('.') == -1: # If it's a single name, find it in the base logster package - class_name = 'logster.parsers.%s.%s' % (class_name, class_name) + parser_klass_name = 'logster.parsers.%s.%s' % (parser_klass_name, parser_klass_name) log_file = arguments[1] state_dir = options.state_dir log_dir = options.log_dir @@ -197,152 +195,9 @@ def lineno(): def submit_stats(parser, duration, options): metrics = parser.get_state(duration) - - if 'ganglia' in options.output: - submit_ganglia(metrics, options) - if 'graphite' in options.output: - submit_graphite(metrics, options) - if 'stdout' in options.output: - submit_stdout(metrics, options) - if 'cloudwatch' in options.output: - submit_cloudwatch(metrics, options) - if 'nsca' in options.output: - submit_nsca(metrics, options) - if 'statsd' in options.output: - submit_statsd(metrics, options) - - -def submit_stdout(metrics, options): - for metric in metrics: - metric_name = metric.name - if (options.metric_prefix != ""): - metric_name = options.metric_prefix + options.stdout_separator + metric_name - if (options.metric_suffix is not None): - metric_name = metric_name + options.stdout_separator + options.metric_suffix - print("%s %s %s" % (metric.timestamp, metric_name, metric.value)) - -def submit_ganglia(metrics, options): - for metric in metrics: - metric_name = metric.name - if (options.metric_prefix != ""): - metric_name = options.metric_prefix + "_" + metric_name - if (options.metric_suffix is not None): - metric_name = metric_name + "_" + options.metric_suffix - - gmetric_cmd = "%s %s --name %s --value %s --type %s --units \"%s\"" % ( - gmetric, options.gmetric_options, metric_name, metric.value, metric.type, metric.units) - logger.debug("Submitting Ganglia metric: %s" % gmetric_cmd) - - if (not options.dry_run): - os.system("%s" % gmetric_cmd) - else: - print("%s" % gmetric_cmd) - - -def submit_graphite(metrics, options): - if (re.match("^[\w\.\-]+\:\d+$", options.graphite_host) == None): - raise Exception("Invalid host:port found for Graphite: '%s'" % options.graphite_host) - - if (not options.dry_run): - host = options.graphite_host.split(':') - - if options.graphite_protocol == 'udp': - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - else: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - s.connect((host[0], int(host[1]))) - - try: - for metric in metrics: - metric_name = metric.name - if (options.metric_prefix != ""): - metric_name = options.metric_prefix + "." + metric_name - if (options.metric_suffix is not None): - metric_name = metric_name + "." + options.metric_suffix - - metric_string = "%s %s %s" % (metric_name, metric.value, metric.timestamp) - logger.debug("Submitting Graphite metric: %s" % metric_string) - - if (not options.dry_run): - s.sendall(bytes("%s\n" % metric_string)) - else: - print("%s %s" % (options.graphite_host, metric_string)) - finally: - if (not options.dry_run): - s.close() - -def submit_cloudwatch(metrics, options): - for metric in metrics: - metric_name = metric.name - if (options.metric_prefix != ""): - metric_name = options.metric_prefix + "." + metric_name - if (options.metric_suffix is not None): - metric_name = metric_name + "." + options.metric_suffix - - metric.timestamp = strftime("%Y%m%dT%H:%M:00Z", gmtime(metric.timestamp)) - metric.units = "None" - metric_string = "%s %s %s" % (metric_name, metric.value, metric.timestamp) - logger.debug("Submitting CloudWatch metric: %s" % metric_string) - - if (not options.dry_run): - try: - cw = CloudWatch(options.aws_key, options.aws_secret_key, metric).get_instance_id() - except CloudWatchException: - logger.debug("Is this machine really amazon EC2?") - sys.exit(1) - - try: - cw.put_data() - except CloudWatchException as e: - logger.debug(e.message) - sys.exit(1) - else: - print(metric_string) - - -def submit_nsca(metrics, options): - if (re.match("^[\w\.\-]+\:\d+$", options.nsca_host) is None): - raise Exception("Invalid host:port found for NSCA: '%s'" % options.nsca_host) - - host = options.nsca_host.split(':') - - for metric in metrics: - metric_name = metric.name - if (options.metric_prefix != ""): - metric_name = options.metric_prefix + "_" + metric_name - if (options.metric_suffix is not None): - metric_name = metric_name + "_" + options.metric_suffix - - metric_string = "\t".join((options.nsca_service_hostname, metric_name, str(metric.value), metric.units,)) - logger.debug("Submitting NSCA status: %s" % metric_string) - - nsca_cmd = "echo '%s' | %s -H %s -p %s" % (metric_string, send_nsca, host[0], host[1],) - - if (not options.dry_run): - os.system(nsca_cmd) - else: - print("%s" % nsca_cmd) - - -def submit_statsd(metrics, addr): - if (not options.dry_run): - host = options.statsd_host.split(':') - - for metric in metrics: - metric_name = metric.name - if (options.metric_prefix != ""): - metric_name = options.metric_prefix + '.' + metric_name - if (options.metric_suffix is not None): - metric_name = metric_name + '.' + options.metric_suffix - metric_string = "%s:%s|%s" % (metric_name, metric.value, metric.metric_type) - logger.debug("Submitting statsd metric: %s" % metric_string) - - if (not options.dry_run): - udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp_sock.sendto(metric_string, (host[0], int(host[1]))) - else: - print("%s %s" % (options.statsd_host, metric_string)) + for output_klass in options.output: + output = output_klass(options, logger) + output.submit(metrics) def start_locking(lockfile_name): @@ -392,15 +247,15 @@ def end_locking(lockfile_fd, lockfile_name): def main(): dirsafe_logfile = log_file.replace('/','-') - state_file = '%s/%s-%s%s.state' % (state_dir, tailer_klass.short_name, class_name, dirsafe_logfile) - lock_file = '%s/%s-%s%s.lock' % (state_dir, tailer_klass.short_name, class_name, dirsafe_logfile) + state_file = '%s/%s-%s%s.state' % (state_dir, tailer_klass.short_name, parser_klass_name, dirsafe_logfile) + lock_file = '%s/%s-%s%s.lock' % (state_dir, tailer_klass.short_name, parser_klass_name, dirsafe_logfile) tailer = tailer_klass(log_file, state_file, options, logger) - logger.info("Executing parser %s on logfile %s" % (class_name, log_file)) + logger.info("Executing parser %s on logfile %s" % (parser_klass_name, log_file)) logger.debug("Using state file %s" % state_file) # Import and instantiate the class from the module passed in. - module_name, parser_name = class_name.rsplit('.', 1) + module_name, parser_name = parser_klass_name.rsplit('.', 1) module = __import__(module_name, globals(), locals(), [parser_name]) parser = getattr(module, parser_name)(option_string=options.parser_options) diff --git a/logster/logster_helper.py b/logster/logster_helper.py index a37f1e9..63657fa 100644 --- a/logster/logster_helper.py +++ b/logster/logster_helper.py @@ -67,85 +67,23 @@ class LockingError(Exception): """ Exception raised for errors creating or destroying lockfiles. """ pass -class CloudWatchException(Exception): - """ Raise thie exception if the connection can't be established - with Amazon server """ - pass -class CloudWatch: - """ Base class for Amazon CloudWatch """ - def __init__(self, key, secret_key, metric): - """ Specify Amazon CloudWatch params """ - - self.base_url = "monitoring.ap-northeast-1.amazonaws.com" - self.key = key - self.secret_key = secret_key - self.metric = metric - - def get_instance_id(self, instance_id = None): - """ get instance id from amazon meta data server """ - - self.instance_id = instance_id - - if self.instance_id is None: - try: - conn = HTTPConnection("169.254.169.254") - conn.request("GET", "/latest/meta-data/instance-id") - except Exception: - raise CloudWatchException("Can't connect Amazon meta data server to get InstanceID : (%s)") - - self.instance_id = conn.getresponse().read() - - return self - - def set_params(self): - - params = {'Namespace': 'logster', - 'MetricData.member.1.MetricName': self.metric.name, - 'MetricData.member.1.Value': self.metric.value, - 'MetricData.member.1.Unit': self.metric.units, - 'MetricData.member.1.Dimensions.member.1.Name': 'InstanceID', - 'MetricData.member.1.Dimensions.member.1.Value': self.instance_id} - - self.url_params = params - self.url_params['AWSAccessKeyId'] = self.key - self.url_params['Action'] = 'PutMetricData' - self.url_params['SignatureMethod'] = 'HmacSHA256' - self.url_params['SignatureVersion'] = '2' - self.url_params['Version'] = '2010-08-01' - self.url_params['Timestamp'] = self.metric.timestamp - - return self - - def get_signed_url(self): - """ build signed parameters following - http://docs.amazonwebservices.com/AmazonCloudWatch/latest/APIReference/API_PutMetricData.html """ - keys = sorted(self.url_params) - values = map(self.url_params.get, keys) - url_string = urlencode(list(zip(keys,values))) - - string_to_sign = "GET\n%s\n/\n%s" % (self.base_url, url_string) - try: - if sys.version_info[:2] == (2, 5): - signature = hmac.new( key=self.secret_key, msg=string_to_sign, digestmod=hashlib.sha256).digest() - else: - signature = hmac.new( key=bytes(self.secret_key), msg=bytes(string_to_sign), digestmod=hashlib.sha256).digest() - except TypeError: - signature = hmac.new( key=bytes(self.secret_key, "utf-8"), msg=bytes(string_to_sign, "utf-8"), digestmod=hashlib.sha256).digest() - - signature = base64.encodestring(signature).strip() - urlencoded_signature = quote_plus(signature) - url_string += "&Signature=%s" % urlencoded_signature - - return "/?" + url_string - - def put_data(self): - signedURL = self.set_params().get_signed_url() - try: - conn = HTTPConnection(self.base_url) - conn.request("GET", signedURL) - except Exception: - raise CloudWatchException("Can't connect Amazon CloudWatch server") - res = conn.getresponse() +def build_metric_name(metric, prefix, suffix, separator): + metric_name = metric.name + if prefix: + metric_name = prefix + separator + metric_name + if suffix: + metric_name = metric_name + separator + suffix + return metric_name + + +class LogsterOutput(object): + def __init__(self, parser, options, logger): + self.options = options + self.logger = logger + self.dry_run = options.dry_run + def get_metric_name(self, metric, separator="."): + build_metric_name(metric, self.options.metric_prefix, + self.options.metric_suffix, separator) diff --git a/logster/outputs/CloudwatchOutput.py b/logster/outputs/CloudwatchOutput.py new file mode 100644 index 0000000..466862f --- /dev/null +++ b/logster/outputs/CloudwatchOutput.py @@ -0,0 +1,134 @@ +from logster.logster_helper import LogsterOutput +from time import strftime, gmtime +import os +import sys + + +class CloudWatchException(Exception): + """ Raise thie exception if the connection can't be established + with Amazon server """ + pass + + +class CloudWatch(object): + """ Base class for Amazon CloudWatch """ + def __init__(self, key, secret_key, metric): + """ Specify Amazon CloudWatch params """ + + self.base_url = "monitoring.ap-northeast-1.amazonaws.com" + self.key = key + self.secret_key = secret_key + self.metric = metric + + def get_instance_id(self, instance_id = None): + """ get instance id from amazon meta data server """ + + self.instance_id = instance_id + + if self.instance_id is None: + try: + conn = HTTPConnection("169.254.169.254") + conn.request("GET", "/latest/meta-data/instance-id") + except Exception: + raise CloudWatchException("Can't connect Amazon meta data server to get InstanceID : (%s)") + + self.instance_id = conn.getresponse().read() + + return self + + def set_params(self): + + params = {'Namespace': 'logster', + 'MetricData.member.1.MetricName': self.metric.name, + 'MetricData.member.1.Value': self.metric.value, + 'MetricData.member.1.Unit': self.metric.units, + 'MetricData.member.1.Dimensions.member.1.Name': 'InstanceID', + 'MetricData.member.1.Dimensions.member.1.Value': self.instance_id} + + self.url_params = params + self.url_params['AWSAccessKeyId'] = self.key + self.url_params['Action'] = 'PutMetricData' + self.url_params['SignatureMethod'] = 'HmacSHA256' + self.url_params['SignatureVersion'] = '2' + self.url_params['Version'] = '2010-08-01' + self.url_params['Timestamp'] = self.metric.timestamp + + return self + + def get_signed_url(self): + """ build signed parameters following + http://docs.amazonwebservices.com/AmazonCloudWatch/latest/APIReference/API_PutMetricData.html """ + keys = sorted(self.url_params) + values = map(self.url_params.get, keys) + url_string = urlencode(list(zip(keys,values))) + + string_to_sign = "GET\n%s\n/\n%s" % (self.base_url, url_string) + try: + if sys.version_info[:2] == (2, 5): + signature = hmac.new( key=self.secret_key, msg=string_to_sign, digestmod=hashlib.sha256).digest() + else: + signature = hmac.new( key=bytes(self.secret_key), msg=bytes(string_to_sign), digestmod=hashlib.sha256).digest() + except TypeError: + signature = hmac.new( key=bytes(self.secret_key, "utf-8"), msg=bytes(string_to_sign, "utf-8"), digestmod=hashlib.sha256).digest() + + signature = base64.encodestring(signature).strip() + urlencoded_signature = quote_plus(signature) + url_string += "&Signature=%s" % urlencoded_signature + + return "/?" + url_string + + def put_data(self): + signedURL = self.set_params().get_signed_url() + try: + conn = HTTPConnection(self.base_url) + conn.request("GET", signedURL) + except Exception: + raise CloudWatchException("Can't connect Amazon CloudWatch server") + res = conn.getresponse() + + +class CloudwatchOutput(LogsterOutput): + shortname = 'cloudwatch' + + + @classmethod + def add_options(cls, parser): + parser.add_option('--aws-key', action='store', + default=os.getenv('AWS_ACCESS_KEY_ID'), help='Amazon credential key') + parser.add_option('--aws-secret-key', action='store', + default=os.getenv('AWS_SECRET_ACCESS_KEY_ID'), help='Amazon credential secret key') + + + def __init__(self, parser, options, logger): + super(CloudwatchOutput, self).__init__(parser, options, logger) + if not options.aws_key or not options.aws_secret_key: + parser.print_help() + parser.error("You must supply --aws-key and --aws-secret-key or Set environment variables. AWS_ACCESS_KEY_ID for --aws-key, AWS_SECRET_ACCESS_KEY_ID for --aws-secret-key") + self.aws_key = options.aws_key + self.aws_secret_key = options.aws_secret_key + + + def submit(self, metrics): + for metric in metrics: + metric_name = self.get_metric_name(metric) + + metric.timestamp = strftime("%Y%m%dT%H:%M:00Z", gmtime(metric.timestamp)) + metric.units = "None" + metric_string = "%s %s %s" % (metric_name, metric.value, metric.timestamp) + self.logger.debug("Submitting CloudWatch metric: %s" % metric_string) + + if (not self.dry_run): + try: + cw = CloudWatch(self.aws_key, self.aws_secret_key, metric).get_instance_id() + except CloudWatchException: + self.logger.debug("Is this machine really amazon EC2?") + sys.exit(1) + + try: + cw.put_data() + except CloudWatchException as e: + self.logger.debug(e.message) + sys.exit(1) + else: + print(metric_string) + diff --git a/logster/outputs/GangliaOutput.py b/logster/outputs/GangliaOutput.py new file mode 100644 index 0000000..058e872 --- /dev/null +++ b/logster/outputs/GangliaOutput.py @@ -0,0 +1,32 @@ +from logster.logster_helper import LogsterOutput +import os + + +class GangliaOutput(LogsterOutput): + shortname = 'ganglia' + gmetric = "/usr/bin/gmetric" + + @classmethod + def add_options(cls, parser): + parser.add_option('--gmetric-options', action='store', + help='Options to pass to gmetric such as "-d 180 -c /etc/ganglia/gmond.conf" (default). These are passed directly to gmetric.', + default='-d 180 -c /etc/ganglia/gmond.conf') + + + def __init__(self, parser, options, logger): + super(GangliaOutput, self).__init__(parser, options, logger) + self.gmetric_options = options.gmetric_options + + + def submit(self, metrics): + for metric in metrics: + metric_name = self.get_metric_name(metric, "_") + + gmetric_cmd = "%s %s --name %s --value %s --type %s --units \"%s\"" % ( GangliaOutput.gmetric, self.gmetric_options, metric_name, metric.value, metric.type, metric.units) + self.logger.debug("Submitting Ganglia metric: %s" % gmetric_cmd) + + if (not self.dry_run): + os.system("%s" % gmetric_cmd) + else: + print("%s" % gmetric_cmd) + diff --git a/logster/outputs/GraphiteOutput.py b/logster/outputs/GraphiteOutput.py new file mode 100644 index 0000000..fb7c7f5 --- /dev/null +++ b/logster/outputs/GraphiteOutput.py @@ -0,0 +1,56 @@ +from logster.logster_helper import LogsterOutput +import socket + + +class GraphiteOutput(LogsterOutput): + shortname = 'graphite' + + + @classmethod + def add_options(cls, parser): + parser.add_option('--graphite-host', action='store', + help='Hostname and port for Graphite collector, e.g. graphite.example.com:2003') + parser.add_option('--graphite-protocol', action='store', default='tcp', + choices=('tcp', 'udp'), + help='Specify graphite socket protocol. Options are tcp and udp. Defaults to tcp.') + + + def __init__(self, parser, options, logger): + super(GraphiteOutput, self).__init__(parser, options, logger) + if not options.graphite_host: + parser.print_help() + parser.error("You must supply --graphite-host when using 'graphite' as an output type.") + if (re.match("^[\w\.\-]+\:\d+$", options.graphite_host) == None): + parser.print_help() + parser.error("Invalid host:port found for Graphite: '%s'" % options.graphite_host) + + self.graphite_host = options.graphite_host + self.graphite_protocol = options.graphite_protocol + + + def submit(self, metrics): + + if (not self.dry_run): + host = self.graphite_host.split(':') + + if self.graphite_protocol == 'udp': + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + else: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + s.connect((host[0], int(host[1]))) + + try: + for metric in metrics: + metric_name = self.get_metric_name(metric) + + metric_string = "%s %s %s" % (metric_name, metric.value, metric.timestamp) + self.logger.debug("Submitting Graphite metric: %s" % metric_string) + + if (not self.dry_run): + s.sendall(bytes("%s\n" % metric_string)) + else: + print("%s %s" % (self.graphite_host, metric_string)) + finally: + if (not self.dry_run): + s.close() diff --git a/logster/outputs/NSCAOutput.py b/logster/outputs/NSCAOutput.py new file mode 100644 index 0000000..8e1e4c6 --- /dev/null +++ b/logster/outputs/NSCAOutput.py @@ -0,0 +1,46 @@ +from logster.logster_helper import LogsterOutput +import socket +import re +import os + + +class NSCAOutput(LogsterOutput): + shortname = 'nsca' + send_nsca = "/usr/sbin/send_nsca" + + + @classmethod + def add_options(cls, parser): + parser.add_option('--nsca-host', action='store', + help='Hostname and port for NSCA daemon, e.g. nsca.example.com:5667') + parser.add_option('--nsca-service-hostname', action='store', + help='<host_name> value to use in nsca passive service check. Default is \"%default\"', + default=socket.gethostname()) + + + def __init__(self, parser, options, logger): + super(NSCAOutput, self).__init__(parser, options, logger) + if not options.nsca_host: + parser.print_help() + parser.error("You must supply --nsca-host when using 'nsca' as an output type.") + if (re.match("^[\w\.\-]+\:\d+$", options.nsca_host) is None): + parser.print_help() + parser.error("Invalid host:port found for NSCA: '%s'" % options.nsca_host) + self.host = options.nsca_host.split(':') + self.nsca_service_hostname = options.nsca_service_hostname + + def submit(self, metrics): + + for metric in metrics: + metric_name = self.get_metric_name(metric, "_") + + metric_string = "\t".join((self.nsca_service_hostname, metric_name, str(metric.value), metric.units,)) + self.logger.debug("Submitting NSCA status: %s" % metric_string) + + nsca_cmd = "echo '%s' | %s -H %s -p %s" % (metric_string, NSCAOutput.send_nsca, self.host[0], self.host[1],) + + if (not self.dry_run): + os.system(nsca_cmd) + else: + print("%s" % nsca_cmd) + diff --git a/logster/outputs/StatsdOutput.py b/logster/outputs/StatsdOutput.py new file mode 100644 index 0000000..d4c7453 --- /dev/null +++ b/logster/outputs/StatsdOutput.py @@ -0,0 +1,33 @@ +from logster.logster_helper import LogsterOutput +import socket + + +class StatsdOutput(LogsterOutput): + shortname = 'statsd' + + + @classmethod + def add_options(cls, parser): + parser.add_option('--statsd-host', action='store', + help='Hostname and port for statsd collector, e.g. statsd.example.com:8125') + + + def __init__(self, parser, options, logger): + super(StatsdOutput, self).__init__(parser, options, logger) + self.statsd_host = options.statsd_host + + + def submit(self, metrics): + if (not self.dry_run): + host = self.statsd_host.split(':') + + for metric in metrics: + metric_name = self.get_metric_name(metric) + metric_string = "%s:%s|%s" % (metric_name, metric.value, metric.metric_type) + self.logger.debug("Submitting statsd metric: %s" % metric_string) + + if (not self.dry_run): + udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_sock.sendto(metric_string, (host[0], int(host[1]))) + else: + print("%s %s" % (self.statsd_host, metric_string)) diff --git a/logster/outputs/StdoutOutput.py b/logster/outputs/StdoutOutput.py new file mode 100644 index 0000000..b3bbc8b --- /dev/null +++ b/logster/outputs/StdoutOutput.py @@ -0,0 +1,23 @@ +from logster.logster_helper import LogsterOutput + + +class StdoutOutput(LogsterOutput): + shortname = 'stdout' + + + @classmethod + def add_options(cls, parser): + parser.add_option('--stdout-separator', action='store', default="_", dest="stdout_separator", + help='Seperator between prefix/suffix and name for stdout. Default is \"%default\".') + + + def __init__(self, parser, options, logger): + super(StdoutOutput, self).__init__(parser, options, logger) + self.separator = options.stdout_separator + + + def submit(self, metrics): + for metric in metrics: + metric_name = self.get_metric_name(metric, self.separator) + print("%s %s %s" % (metric.timestamp, metric_name, metric.value)) + diff --git a/logster/outputs/__init__.py b/logster/outputs/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/logster/outputs/__init__.py diff --git a/logster/outputs/builtin.py b/logster/outputs/builtin.py new file mode 100644 index 0000000..cf355de --- /dev/null +++ b/logster/outputs/builtin.py @@ -0,0 +1,6 @@ +from logster.outputs.StdoutOutput import StdoutOutput +from logster.outputs.GraphiteOutput import GraphiteOutput +from logster.outputs.GangliaOutput import GangliaOutput +from logster.outputs.StatsdOutput import StatsdOutput +from logster.outputs.CloudwatchOutput import CloudwatchOutput +from logster.outputs.NSCAOutput import NSCAOutput |