lib/middleware/metrics_middleware.rb



module Middleware
  require 'uri'

  # Returns the process type if any
  def self.get_process_type
    p_type = nil
    if ENV['HOSTNAME']
      temp = ENV['HOSTNAME'].split(ENV['DEIS_APP'])[1]
      temp = temp.split(/(-[0-9a-zA-Z]{5})$/)[0] # remove the 5 char hash
      p_type = temp[1, temp.rindex("-")-1]
    end
    return p_type
  end


  # Write to telegraf
  def self.write_to_telegraf(endpoint_name: nil, method_name: nil, status_code: nil, response_time: nil, db_runtime: nil, view_runtime: nil, content_type: nil)

    # Getting the process type
    p_type = Middleware::get_process_type

    # Separately handling 200 and non 200 as influx does not accept nil as a value
    if db_runtime && view_runtime
      # 200 requests
      ZuoraConnect.configuration.telegraf_client.write(ZuoraConnect.configuration.app_name_inbound,
          tags: {endpoint: endpoint_name, "content-type": content_type, method: method_name, status: status_code, process_type: p_type},
          values: {response_time: response_time, db_time: db_runtime, view_time: view_runtime})
    else
      # non 200 requests
      ZuoraConnect.configuration.telegraf_client.write(ZuoraConnect.configuration.app_name_inbound,
          tags: {endpoint: endpoint_name, "content-type": content_type, method: method_name, status: status_code, process_type: p_type},
          values: {response_time: response_time})
    end

  end


  # Object of this class is passed to the ActiveSupport::Notification hook
  class PageRequest

    # This method is triggered when a non error page is loaded (not 404, 500)
    def call(name, started, finished, unique_id, payload)

      # If the url contains any css or JavaScript files then do not collect metrics for them
      block_words = ["css", "assets", "jpg", "png", "jpeg", "ico"]
      if block_words.any? { |word| payload[:path].include?(word) }
        return nil
      end


      # Getting the endpoint and the content_type
      content_hash = {:html => "text/html", :js => "application/js", :json => "application/json"}
      content_type = content_hash[payload[:format]]

      if payload[:headers]
        request_path = URI(payload[:headers]['REQUEST_URI']).path.split(".")[0]
      else
        request_path = payload[:path]
      end

      # content_type = ((payload[:headers]["action_controller.instance"].instance_variable_get :@_response).instance_variable_get :@header)["Content-Type"]
      # content_type = content_type.split(";")[0]

      response_time = finished-started

      # Write to telegraf
      Middleware.write_to_telegraf("endpoint_name": request_path, "method_name": payload[:method], "status_code": payload[:status], "response_time": response_time, "db_runtime": payload[:db_runtime], "view_runtime": payload[:view_runtime], "content_type": content_type)

    end
  end


  class MetricsMiddleware

    require "zuora_connect/version"
    require "zuora_api/version"
    require "telegraf"

    def initialize(app)
      @app = app
    end

    def call(env)
      start_time = Time.now
      @status, @headers, @response = @app.call(env)

      # If the url contains any CSS or JavaScript files then do not collect metrics for them
      block_words = ["css", "assets", "jpg", "png", "jpeg", "ico"]
      if block_words.any? { |word| env['PATH_INFO'].include?(word) }
        return [@status, @headers, @response]
      end

      end_time = Time.now
      response_time = end_time - start_time

      #Prometheus Stuff
      if env['PATH_INFO'] == '/connect/internal/metrics'

        #Do something before each scrape
        if defined? Resque.redis

          app_name = ENV['DEIS_APP'].present? ? "#{ENV['DEIS_APP']}" : "#{Rails.application.class.parent_name}"
          begin

            Resque.redis.ping

            Prometheus::REDIS_CONNECTION.set({connection:'redis',name:app_name},1)
            Prometheus::FINISHED_JOBS.set({type:'resque',name:app_name},Resque.info[:processed])
            Prometheus::PENDING_JOBS.set({type:'resque',name:app_name},Resque.info[:pending])
            Prometheus::ACTIVE_WORKERS.set({type:'resque',name:app_name},Resque.info[:working])
            Prometheus::WORKERS.set({type:'resque',name:app_name},Resque.info[:workers])
            Prometheus::FAILED_JOBS.set({type:'resque',name:app_name},Resque.info[:failed])

          rescue Redis::CannotConnectError
              Prometheus::REDIS_CONNECTION.set({connection:'redis',name:app_name},0)
          end

          if ZuoraConnect.configuration.custom_prometheus_update_block != nil
            ZuoraConnect.configuration.custom_prometheus_update_block.call()
          end
        end

      end

      # Writing to telegraf: Handle 404 and 500 requests
      if @status != 200
        # Getting the endpoint and content_type
        request_path = URI(env['REQUEST_URI']).path.split(".")[0]
        content_type = @headers['Content-Type'].split(';')[0] if @headers['Content-Type']
        Middleware::write_to_telegraf("endpoint_name": request_path, "method_name": env['REQUEST_METHOD'], "status_code": @status, "response_time": response_time,  "content_type": content_type)
      end

      [@status, @headers, @response]

    end

  end

end