module ExceptionHandling class ExceptionInfo ENVIRONMENT_WHITELIST = [ /^HTTP_/, /^QUERY_/, /^REQUEST_/, /^SERVER_/ ] ENVIRONMENT_OMIT =( < ex # can't call log_error here or we will blow the call stack traces = ex.backtrace.join("\n") ExceptionHandling.log_info("Unable to execute custom custom_data_hook callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n") end end def normalize_exception_data(data) if data[:location].nil? data[:location] = {} if data[:request] && data[:request].key?(:params) data[:location][:controller] = data[:request][:params]['controller'] data[:location][:action] = data[:request][:params]['action'] end end if data[:backtrace] && data[:backtrace].first first_line = data[:backtrace].first # template exceptions have the line number and filename as the first element in backtrace if matched = first_line.match( /on line #(\d*) of (.*)/i ) backtrace_hash = {} backtrace_hash[:line] = matched[1] backtrace_hash[:file] = matched[2] else backtrace_hash = Hash[* [:file, :line].zip( first_line.split( ':' )[0..1]).flatten ] end data[:location].merge!( backtrace_hash ) end end def clean_exception_data( data ) if (as_array = data[:backtrace].to_a).size == 1 data[:backtrace] = as_array.first.to_s.split(/\n\s*/) end if data[:request].is_a?(Hash) && data[:request][:params].is_a?(Hash) data[:request][:params] = deep_clean_hash(data[:request][:params]) end if data[:environment].is_a?(Hash) data[:environment] = clean_environment(data[:environment]) end end def clean_environment(env) Hash[ env.map do |k, v| [k, v] if !"#{k}: #{v}".in?(ENVIRONMENT_OMIT) && ENVIRONMENT_WHITELIST.any? { |regex| k =~ regex } end.compact ] end def deep_clean_hash(hash) hash.is_a?(Hash) or return hash hash.build_hash do |k, v| value = v.is_a?(Hash) ? deep_clean_hash(v) : filter_sensitive_value(k, v) [k, value] end end def filter_sensitive_value(key, value) if key =~ /(password|oauth_token)/ "[FILTERED]" elsif key == "rack.request.form_vars" && value.respond_to?(:match) && (captured_matches = value.match(/(.*)(password=)([^&]+)(.*)/)&.captures) [*captured_matches[0..1], "[FILTERED]", *captured_matches[3..-1]].join else value end end # # Pull certain fields out of the controller and add to the data hash. # def extract_and_merge_controller_data(data) if @controller data[:request] = { params: @controller.request.parameters.to_hash, rails_root: defined?(Rails) && defined?(Rails.root) ? Rails.root : "Rails.root not defined. Is this a test environment?", url: @controller.complete_request_uri } data[:environment].merge!(@controller.request.env.to_hash) @controller.session[:fault_in_session] data[:session] = { key: @controller.request.session_options[:id], data: @controller.session.to_hash } end end def customize_from_data_callback(data) if @data_callback # the expectation is that if the caller passed a block then they will be # doing their own merge of hash values into data begin @data_callback.call(data) rescue Exception => ex data.merge!(environment: "Exception in yield: #{ex.class}:#{ex}") end end end def stringify_sections(data) SECTIONS.each { |section| add_to_s(data[section]) if data[section].is_a?(Hash) } end def unstringify_sections(data) SECTIONS.each do |section| if data[section].is_a?(Hash) && data[section].key?(:to_s) data[section] = data[section].dup data[section].delete(:to_s) end end end def add_to_s( data_section ) data_section[:to_s] = dump_hash( data_section ) end def dump_hash( h, indent_level = 0 ) result = "" h.sort { |a, b| a.to_s <=> b.to_s }.each do |key, value| result << ' ' * (2 * indent_level) result << "#{key}:" case value when Hash result << "\n" << dump_hash( value, indent_level + 1 ) else result << " #{value}\n" end end unless h.nil? result end def enhanced_data_to_honeybadger_context data = enhanced_data.dup data[:server] = ExceptionHandling.server_name data[:exception_context] = deep_clean_hash(@exception_context) if @exception_context.present? unstringify_sections(data) context_data = HONEYBADGER_CONTEXT_SECTIONS.reduce({}) do |context, section| if data[section].present? context[section] = data[section] end context end context_data end end end