require 'sidekiq' require 'sidekiq/datadog/version' require 'datadog/statsd' require 'socket' module Sidekiq module Middleware module Server class Datadog # Configure and install datadog instrumentation. Example: # # Sidekiq.configure_server do |config| # config.server_middleware do |chain| # chain.add Sidekiq::Middleware::Server::Datadog # end # end # # Options: # * :hostname - the hostname used for instrumentation, defaults to system hostname, respects +INSTRUMENTATION_HOSTNAME+ env variable # * :metric_name - the metric name (prefix) to use, defaults to "sidekiq.job" # * :tags - array of custom tags, these can be plain strings or lambda blocks accepting a rack env instance # * :statsd_host - the statsD host, defaults to "localhost", respects +STATSD_HOST+ env variable # * :statsd_port - the statsD port, defaults to 8125, respects +STATSD_PORT+ env variable # * :statsd - custom statsd instance def initialize(opts = {}) hostname = opts[:hostname] || ENV['INSTRUMENTATION_HOSTNAME'] || Socket.gethostname statsd_host = opts[:statsd_host] || ENV['STATSD_HOST'] || "localhost" statsd_port = (opts[:statsd_port] || ENV['STATSD_PORT'] || 8125).to_i @metric_name = opts[:metric_name] || "sidekiq.job" @statsd = opts[:statsd] || ::Datadog::Statsd.new(statsd_host, statsd_port) @tags = opts[:tags] || [] if @tags.none? {|t| t =~ /^host\:/ } @tags.push("host:#{hostname}") end env = Sidekiq.options[:environment] || ENV['RACK_ENV'] if env && @tags.none? {|t| t =~ /^env\:/ } @tags.push("env:#{ENV['RACK_ENV']}") end end def call(worker, job, queue, *) start = Time.now begin yield record(worker, job, queue, start) rescue => e record(worker, job, queue, start, e) raise end end private def record(worker, job, queue, start, error = nil) ms = ((Time.now - start) * 1000).round if job["enqueued_at"] queued_ms = ((start - Time.at(job["enqueued_at"])) * 1000).round end name = underscore(job['wrapped'] || worker.class.to_s) tags = @tags.map do |tag| case tag when String then tag when Proc then tag.call(worker, job, queue, error) end end tags.push "name:#{name}" tags.push "queue:#{queue}" if queue if error kind = underscore(error.class.name.sub(/Error$/, '')) tags.push "status:error", "error:#{kind}" else tags.push "status:ok" end tags.compact! @statsd.increment @metric_name, :tags => tags @statsd.timing "#{@metric_name}.time", ms, :tags => tags if queued_ms @statsd.timing "#{@metric_name}.queued_time", queued_ms, :tags => tags end end def underscore(word) word = word.to_s.gsub(/::/, '/') word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") word.downcase end end end end end