class Rack::Honeycomb::Middleware
Constants
- ENV_REGEX
- EVENT_TYPE
- RAILS_SPECIAL_PARAMS
- USER_AGENT_SUFFIX
Public Class Methods
new(app, options = {})
click to toggle source
@param [#call] app @param [Hash{Symbol => Object}] options @option options [String] :writekey (nil) @option options [String] :dataset (nil) @option options [String] :api_host (nil) @option options [Boolean] :is_sinatra (false) @option options [Boolean] :is_rails (false)
# File lib/rack/honeycomb/middleware.rb, line 33 def initialize(app, options = {}) @app, @options = app, options @logger = options.delete(:logger) @logger ||= ::Honeycomb.logger if defined?(::Honeycomb.logger) @is_sinatra = options.delete(:is_sinatra) debug 'Enabling Sinatra-specific fields' if @is_sinatra @is_rails = options.delete(:is_rails) debug 'Enabling Rails-specific fields' if @is_rails # report meta.package = rack only if we have no better information package = 'rack' package_version = RACK_VERSION if @is_rails package = 'rails' package_version = ::Rails::VERSION::STRING elsif @is_sinatra package = 'sinatra' package_version = ::Sinatra::VERSION end honeycomb = if client = options.delete(:client) debug "initialized with #{client.class.name} via :client option" client elsif defined?(::Honeycomb.client) debug "initialized with #{::Honeycomb.client.class.name} from honeycomb-beeline" ::Honeycomb.client else debug "initializing new Libhoney::Client" Libhoney::Client.new(options.merge(user_agent_addition: USER_AGENT_SUFFIX)) end @builder = honeycomb.builder. add( 'meta.package' => package, 'meta.package_version' => package_version, 'type' => EVENT_TYPE, 'meta.local_hostname' => Socket.gethostname, ) end
Public Instance Methods
call(env)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 74 def call(env) ev = @builder.event add_request_fields(ev, env) start = Time.now status, headers, body = with_tracing_if_available(ev, env) do @app.call(env) end add_response_fields(ev, status, headers, body) [status, headers, body] rescue Exception => e if ev ev.add_field('request.error', e.class.name) ev.add_field('request.error_detail', e.message) end raise ensure if ev && start finish = Time.now ev.add_field('duration_ms', (finish - start) * 1000) add_sinatra_fields(ev, env) if @is_sinatra add_rails_fields(ev, env) if @is_rails add_app_fields(ev, env) ev.send end end
Private Instance Methods
add_app_field(event, k, v)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 234 def add_app_field(event, k, v) event.add_field "#{APP_FIELD_NAMESPACE}.#{k}", v end
add_app_fields(event, env)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 182 def add_app_fields(event, env) # Pull arbitrary metadata off `env` if the caller attached # anything inside the Rack handler. env.each_pair do |k, v| if k.is_a?(String) && k.match(ENV_REGEX) field_name = k.sub(ENV_REGEX, '') add_app_field(event, field_name, v) env.delete(k) end end end
add_rails_fields(event, env)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 138 def add_rails_fields(event, env) rails_params = env['action_dispatch.request.parameters'] unless rails_params.kind_of? Hash debug "Got unexpected type #{rails_params.class} for env['action_dispatch.request.parameters']" return end rails_params.each do |param, value| if RAILS_SPECIAL_PARAMS.include?(param) event.add_field("request.#{param}", value) end end # overwrite 'name' (previously set in add_request_fields) event.add_field('name', "#{rails_params[:controller]}##{rails_params[:action]}") event.add_field('request.route', extract_rails_route(env)) end
add_request_fields(event, env)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 112 def add_request_fields(event, env) event.add_field('name', "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}") # N.B. 'name' may be overwritten later by add_sinatra_fields or # add_rails_fields event.add_field('request.method', env['REQUEST_METHOD']) event.add_field('request.path', env['PATH_INFO']) event.add_field('request.protocol', env['rack.url_scheme']) if env['QUERY_STRING'] && !env['QUERY_STRING'].empty? event.add_field('request.query_string', env['QUERY_STRING']) end event.add_field('request.http_version', env['HTTP_VERSION']) event.add_field('request.host', env['HTTP_HOST']) event.add_field('request.remote_addr', env['REMOTE_ADDR']) event.add_field('request.header.user_agent', env['HTTP_USER_AGENT']) end
add_response_fields(event, status, headers, body)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 194 def add_response_fields(event, status, headers, body) event.add_field('response.status_code', status) end
add_sinatra_fields(event, env)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 131 def add_sinatra_fields(event, env) route = env['sinatra.route'] event.add_field('request.route', route) # overwrite 'name' (previously set in add_request_fields) event.add_field('name', route) end
debug(msg)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 108 def debug(msg) @logger.debug("#{self.class.name}: #{msg}") if @logger end
extract_rails_route(env)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 157 def extract_rails_route(env) # egregious and probably slow hack to get the formatted route # TODO there must be a better way routes = env['action_dispatch.routes'] request = ::ActionDispatch::Request.new(env) formatted_route = nil routes.router.recognize(request) do |route, _| # make a hash where each param ("part") in the route is given its # own name as a value, e.g. {:id => ":id"} symbolic_params = {} route.required_parts.each do |part| symbolic_params[part] = ":#{part}" end # then ask the route to format itself using those param "values" formatted_route = route.format(symbolic_params) end "#{env['REQUEST_METHOD']} #{formatted_route}" rescue StandardError => e debug "couldn't extract named route for request: #{e.class}: #{e}" nil end
trace_context_from_header(env)
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 230 def trace_context_from_header(env) env['HTTP_X_HONEYCOMB_TRACE'] end
with_tracing_if_available(event, env) { || ... }
click to toggle source
# File lib/rack/honeycomb/middleware.rb, line 198 def with_tracing_if_available(event, env) # return if we are not using the ruby beeline return yield unless defined?(::Honeycomb) # beeline version <= 0.5.0 if ::Honeycomb.respond_to? :with_trace_id ::Honeycomb.with_trace_id do |trace_id| event.add_field "trace.trace_id", trace_id # so this shows up as a root span event.add_field "trace.span_id", trace_id ::Honeycomb.with_span_id(trace_id) do yield end end # beeline version > 0.5.0 elsif ::Honeycomb.respond_to? :trace_from_encoded_context encoded_context = trace_context_from_header(env) ::Honeycomb.trace_from_encoded_context(encoded_context) do ::Honeycomb.span_for_existing_event( event, name: nil, # this is only added if it is present, we set the name in #add_request_fields type: EVENT_TYPE, ) do yield end end # fallback if we don't detect any known beeline tracing methods else yield end end