module Fluent::Plugin::Prometheus

Public Class Methods

parse_initlabels_elements(conf, base_labels) click to toggle source
# File lib/fluent/plugin/prometheus.rb, line 59
def self.parse_initlabels_elements(conf, base_labels)
  base_initlabels = []

  # We first treat the special case of RecordAccessors and Placeholders labels if any declared
  conf.elements.select { |e| e.name == 'initlabels' }.each { |block|
    initlabels = {}

    block.each do |key, value|
      if not base_labels.has_key? key.to_sym
        raise ConfigError, "Key #{key} in <initlabels> is non existent in <labels> for metric #{conf['name']}"
      end

      if value.start_with?('$.') || value.start_with?('$[') || value.start_with?('${')
        raise ConfigError, "Cannot use RecordAccessor or placeholder #{value} (key #{key}) in a <initlabels> in metric #{conf['name']}"
      end

      base_label_value = base_labels[key.to_sym]

      if !(base_label_value.class == Fluent::PluginHelper::RecordAccessor::Accessor) && ! (base_label_value.start_with?('${') )
        raise ConfigError, "Cannot set <initlabels> on non RecordAccessor/Placeholder key #{key} (value #{value}) in metric #{conf['name']}"
      end

      if base_label_value == '${worker_id}' || base_label_value == '${hostname}'
        raise ConfigError, "Cannot set <initlabels> on reserved placeholder #{base_label_value} for key #{key} in metric #{conf['name']}"
      end
      
      initlabels[key.to_sym] = value
    end

    # Now adding all labels that are not RecordAccessor nor Placeholder labels as is
    base_labels.each do |key, value|
      if base_labels[key.to_sym].class != Fluent::PluginHelper::RecordAccessor::Accessor
        if value == '${worker_id}'
          # We retrieve fluentd_worker_id this way to not overcomplicate the code
          initlabels[key.to_sym] = (ENV['SERVERENGINE_WORKER_ID'] || 0).to_i
        elsif value == '${hostname}'
          initlabels[key.to_sym] = Socket.gethostname
        elsif !(value.start_with?('${'))
          initlabels[key.to_sym] = value
        end
      end
    end

    base_initlabels << initlabels
  }

  # Testing for RecordAccessor/Placeholder labels missing a declaration in <initlabels> blocks
  base_labels.each do |key, value|
    if value.class == Fluent::PluginHelper::RecordAccessor::Accessor || value.start_with?('${')
      if not base_initlabels.map(&:keys).flatten.include? (key.to_sym)
          raise ConfigError, "RecordAccessor/Placeholder key #{key} with value #{value} has not been set in a <initlabels> for initialized metric #{conf['name']}"
      end
    end
  end

  if base_initlabels.length == 0
    # There were no RecordAccessor nor Placeholder labels, we blunty retrieve the static base_labels
    base_initlabels << base_labels
  end

  base_initlabels
end
parse_labels_elements(conf) click to toggle source
# File lib/fluent/plugin/prometheus.rb, line 35
def self.parse_labels_elements(conf)
  labels = conf.elements.select { |e| e.name == 'labels' }
  if labels.size > 1
    raise ConfigError, "labels section must have at most 1"
  end

  base_labels = {}
  unless labels.empty?
    labels.first.each do |key, value|
      labels.first.has_key?(key)

      # use RecordAccessor only for $. and $[ syntax
      # otherwise use the value as is or expand the value by RecordTransformer for ${} syntax
      if value.start_with?('$.') || value.start_with?('$[')
        base_labels[key.to_sym] = PluginHelper::RecordAccessor::Accessor.new(value)
      else
        base_labels[key.to_sym] = value
      end
    end
  end

  base_labels
end
parse_metrics_elements(conf, registry, labels = {}) click to toggle source
# File lib/fluent/plugin/prometheus.rb, line 122
def self.parse_metrics_elements(conf, registry, labels = {})
  metrics = []
  conf.elements.select { |element|
    element.name == 'metric'
  }.each { |element|
    if element.has_key?('key') && (element['key'].start_with?('$.') || element['key'].start_with?('$['))
      value = element['key']
      element['key'] = PluginHelper::RecordAccessor::Accessor.new(value)
    end
    case element['type']
    when 'summary'
      metrics << Fluent::Plugin::Prometheus::Summary.new(element, registry, labels)
    when 'gauge'
      metrics << Fluent::Plugin::Prometheus::Gauge.new(element, registry, labels)
    when 'counter'
      metrics << Fluent::Plugin::Prometheus::Counter.new(element, registry, labels)
    when 'histogram'
      metrics << Fluent::Plugin::Prometheus::Histogram.new(element, registry, labels)
    else
      raise ConfigError, "type option must be 'counter', 'gauge', 'summary' or 'histogram'"
    end
  }
  metrics
end
placeholder_expander(log) click to toggle source
# File lib/fluent/plugin/prometheus.rb, line 147
def self.placeholder_expander(log)
  Fluent::Plugin::Prometheus::ExpandBuilder.new(log: log)
end

Public Instance Methods

configure(conf) click to toggle source
Calls superclass method
# File lib/fluent/plugin/prometheus.rb, line 163
def configure(conf)
  super
  @placeholder_values = {}
  @placeholder_expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
  @hostname = Socket.gethostname
end
instrument(tag, es, metrics) click to toggle source
# File lib/fluent/plugin/prometheus.rb, line 190
def instrument(tag, es, metrics)
  placeholder_values = {
    'tag' => tag,
    'hostname' => @hostname,
    'worker_id' => fluentd_worker_id,
  }

  es.each do |time, record|
    record = stringify_keys(record)
    placeholders = record.merge(placeholder_values)
    expander = @placeholder_expander_builder.build(placeholders)
    metrics.each do |metric|
      begin
        metric.instrument(record, expander)
      rescue => e
        log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
        router.emit_error_event(tag, time, record, e)
      end
    end
  end
end
instrument_single(tag, time, record, metrics) click to toggle source
# File lib/fluent/plugin/prometheus.rb, line 170
def instrument_single(tag, time, record, metrics)
  @placeholder_values[tag] ||= {
    'tag' => tag,
    'hostname' => @hostname,
    'worker_id' => fluentd_worker_id,
  }

  record = stringify_keys(record)
  placeholders = record.merge(@placeholder_values[tag])
  expander = @placeholder_expander_builder.build(placeholders)
  metrics.each do |metric|
    begin
      metric.instrument(record, expander)
    rescue => e
      log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
      router.emit_error_event(tag, time, record, e)
    end
  end
end
stringify_keys(hash_to_stringify) click to toggle source
# File lib/fluent/plugin/prometheus.rb, line 151
def stringify_keys(hash_to_stringify)
  # Adapted from: https://www.jvt.me/posts/2019/09/07/ruby-hash-keys-string-symbol/
  hash_to_stringify.map do |k,v|
    value_or_hash = if v.instance_of? Hash
                      stringify_keys(v)
                    else
                      v
                    end
    [k.to_s, value_or_hash]
  end.to_h
end