summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen Burry <bburry@etsy.com>2015-08-31 19:31:49 +0100
committerBen Burry <bburry@etsy.com>2015-11-13 17:33:02 +0000
commita65f919bebe19677a8eac208ddedd12fca296e68 (patch)
tree5bade5490594cde3b27c38ba278019176f6f6f37
parent1b5560db7db5c6c7d3745e49fb369eadd705d8f0 (diff)
downloadlogster-a65f919bebe19677a8eac208ddedd12fca296e68.zip
logster-a65f919bebe19677a8eac208ddedd12fca296e68.tar.gz
logster-a65f919bebe19677a8eac208ddedd12fca296e68.tar.bz2
Provide pluggable output classes
-rwxr-xr-xbin/logster239
-rw-r--r--logster/logster_helper.py96
-rw-r--r--logster/outputs/CloudwatchOutput.py134
-rw-r--r--logster/outputs/GangliaOutput.py32
-rw-r--r--logster/outputs/GraphiteOutput.py56
-rw-r--r--logster/outputs/NSCAOutput.py46
-rw-r--r--logster/outputs/StatsdOutput.py33
-rw-r--r--logster/outputs/StdoutOutput.py23
-rw-r--r--logster/outputs/__init__.py0
-rw-r--r--logster/outputs/builtin.py6
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