# encoding: utf-8
require "logstash/outputs/base"
require "logstash/namespace"

# statsd is a network daemon for aggregating statistics, such as counters and timers,
# and shipping over UDP to backend services, such as Graphite or Datadog. The general
# idea is that you send metrics to statsd and every few seconds it will emit the
# aggregated values to the backend. Example aggregates are sums, average and maximum
# values, their standard deviation, etc. This plugin makes it easy to send such
# metrics based on data in Logstash events.
#
# You can learn about statsd here:
#
# * https://codeascraft.com/2011/02/15/measure-anything-measure-everything/[Etsy blog post announcing statsd]
# * https://github.com/etsy/statsd[statsd on github]
#
# Typical examples of how this can be used with Logstash include counting HTTP hits
# by response code, summing the total number of bytes of traffic served, and tracking
# the 50th and 95th percentile of the processing time of requests.
#
# Each metric emitted to statsd has a dot-separated path, a type, and a value. The
# metric path is built from the `namespace` and `sender` options together with the
# metric name that's picked up depending on the type of metric. All in all, the
# metric path will follow this pattern:
#
#     namespace.sender.metric
#
# With regards to this plugin, the default namespace is "logstash", the default
# sender is the `host` field, and the metric name depends on what is set as the
# metric name in the `increment`, `decrement`, `timing`, `count`, `set` or `gauge`
# options. In metric paths, colons (":"), pipes ("|") and at signs ("@") are reserved
# and will be replaced by underscores ("_").
#
# Example:
# [source,ruby]
# output {
#   statsd {
#     host => "statsd.example.org"
#     count => {
#       "http.bytes" => "%{bytes}"
#     }
#   }
# }
#
# If run on a host named hal9000 the configuration above will send the following
# metric to statsd if the current event has 123 in its `bytes` field:
#
#     logstash.hal9000.http.bytes:123|c
class LogStash::Outputs::Statsd < LogStash::Outputs::Base
  ## Regex stolen from statsd code
  RESERVED_CHARACTERS_REGEX = /[\:\|\@]/
  config_name "statsd"

  # The hostname or IP address of the statsd server.
  config :host, :validate => :string, :default => "localhost"

  # The port to connect to on your statsd server.
  config :port, :validate => :number, :default => 8125

  # The statsd namespace to use for this metric. `%{fieldname}` substitutions are
  # allowed.
  config :namespace, :validate => :string, :default => "logstash"

  # The name of the sender. Dots will be replaced with underscores. `%{fieldname}`
  # substitutions are allowed.
  config :sender, :validate => :string, :default => "%{host}"

  # An increment metric. Metric names as array. `%{fieldname}` substitutions are
  # allowed in the metric names.
  config :increment, :validate => :array, :default => []

  # A decrement metric. Metric names as array. `%{fieldname}` substitutions are
  # allowed in the metric names.
  config :decrement, :validate => :array, :default => []

  # A timing metric. `metric_name => duration` as hash. `%{fieldname}` substitutions
  # are allowed in the metric names.
  config :timing, :validate => :hash, :default => {}

  # A count metric. `metric_name => count` as hash. `%{fieldname}` substitutions are
  # allowed in the metric names.
  config :count, :validate => :hash, :default => {}

  # A set metric. `metric_name => "string"` to append as hash. `%{fieldname}`
  # substitutions are allowed in the metric names.
  config :set, :validate => :hash, :default => {}

  # A gauge metric. `metric_name => gauge` as hash. `%{fieldname}` substitutions are
  # allowed in the metric names.
  config :gauge, :validate => :hash, :default => {}
  
  # The sample rate for the metric.
  config :sample_rate, :validate => :number, :default => 1

  # Enable debugging.
  config :debug, :validate => :boolean, :default => false, :deprecated => "This setting was never used by this plugin. It will be removed soon."

  public
  def register
    require "statsd"
    @client = Statsd.new(@host, @port)
  end # def register

  public
  def receive(event)
    
    @client.namespace = event.sprintf(@namespace) if not @namespace.empty?
    @logger.debug? and @logger.debug("Original sender: #{@sender}")
    sender = event.sprintf(@sender)
    @logger.debug? and @logger.debug("Munged sender: #{sender}")
    @logger.debug? and @logger.debug("Event: #{event}")
    @increment.each do |metric|
      @client.increment(build_stat(event.sprintf(metric), sender), @sample_rate)
    end
    @decrement.each do |metric|
      @client.decrement(build_stat(event.sprintf(metric), sender), @sample_rate)
    end
    @count.each do |metric, val|
      @client.count(build_stat(event.sprintf(metric), sender),
                    event.sprintf(val), @sample_rate)
    end
    @timing.each do |metric, val|
      @client.timing(build_stat(event.sprintf(metric), sender),
                     event.sprintf(val), @sample_rate)
    end
    @set.each do |metric, val|
      @client.set(build_stat(event.sprintf(metric), sender),
                    event.sprintf(val), @sample_rate)
    end
    @gauge.each do |metric, val|
      @client.gauge(build_stat(event.sprintf(metric), sender),
                    event.sprintf(val), @sample_rate)
    end
  end # def receive

  def build_stat(metric, sender=@sender)
    sender = sender.gsub('::','.').gsub(RESERVED_CHARACTERS_REGEX, '_').gsub(".", "_")
    metric = metric.gsub('::','.').gsub(RESERVED_CHARACTERS_REGEX, '_')
    @logger.debug? and @logger.debug("Formatted value", :sender => sender, :metric => metric)
    return "#{sender}.#{metric}"
  end
end # class LogStash::Outputs::Statsd
