lib/raygun/client.rb in raygun4ruby-3.2.3 vs lib/raygun/client.rb in raygun4ruby-3.2.4

- old
+ new

@@ -1,305 +1,305 @@ -module Raygun - # client for the Raygun REST APIv1 - # as per http://raygun.io/raygun-providers/rest-json-api?v=1 - class Client - - ENV_IP_ADDRESS_KEYS = %w(action_dispatch.remote_ip raygun.remote_ip REMOTE_ADDR) - NO_API_KEY_MESSAGE = "[RAYGUN] Just a note, you've got no API Key configured, which means we can't report exceptions. Specify your Raygun API key using Raygun#setup (find yours at https://app.raygun.io)" - MAX_BREADCRUMBS_SIZE = 100_000 - - include HTTParty - - def initialize - @api_key = require_api_key - @headers = { - "X-ApiKey" => @api_key - } - - enable_http_proxy if Raygun.configuration.proxy_settings[:address] - self.class.base_uri Raygun.configuration.api_url - self.class.default_timeout(Raygun.configuration.error_report_send_timeout) - end - - def require_api_key - Raygun.configuration.api_key || print_api_key_warning - end - - def track_exception(exception_instance, env = {}, user = nil) - create_entry(build_payload_hash(exception_instance, env, user)) - end - - private - - def enable_http_proxy - self.class.http_proxy(Raygun.configuration.proxy_settings[:address], - Raygun.configuration.proxy_settings[:port] || "80", - Raygun.configuration.proxy_settings[:username], - Raygun.configuration.proxy_settings[:password]) - end - - def client_details - { - name: Raygun::CLIENT_NAME, - version: Raygun::VERSION, - clientUrl: Raygun::CLIENT_URL - } - end - - def error_details(exception) - details = { - className: exception.class.to_s, - message: exception.message.to_s.encode('UTF-16', :undef => :replace, :invalid => :replace).encode('UTF-8'), - stackTrace: (exception.backtrace || []).map { |line| stack_trace_for(line) }, - } - - details.update(innerError: error_details(exception.cause)) if exception.respond_to?(:cause) && exception.cause - - details - end - - def stack_trace_for(line) - # see http://www.ruby-doc.org/core-2.0/Exception.html#method-i-backtrace - file_name, line_number, method = line.split(":") - { - lineNumber: line_number, - fileName: file_name, - methodName: method ? method.gsub(/^in `(.*?)'$/, "\\1") : "(none)" - } - end - - def hostname - Socket.gethostname - end - - def version - Raygun.configuration.version - end - - def user_information(env) - env["raygun.affected_user"] - end - - def affected_user_present?(env) - !!env["raygun.affected_user"] - end - - def rack_env - ENV["RACK_ENV"] - end - - def rails_env - ENV["RAILS_ENV"] - end - - def request_information(env) - Raygun.log('retrieving request information') - - return {} if env.nil? || env.empty? - { - hostName: env["SERVER_NAME"], - url: env["PATH_INFO"], - httpMethod: env["REQUEST_METHOD"], - iPAddress: "#{ip_address_from(env)}", - queryString: Rack::Utils.parse_nested_query(env["QUERY_STRING"]), - headers: headers(env), - form: form_params(env), - rawData: raw_data(env) - } - end - - def headers(rack_env) - rack_env.select { |k, v| k.to_s.start_with?("HTTP_") }.inject({}) do |hsh, (k, v)| - hsh[normalize_raygun_header_key(k)] = v - hsh - end - end - - def normalize_raygun_header_key(key) - key.sub(/^HTTP_/, '') - .sub(/_/, ' ') - .split.map(&:capitalize).join(' ') - .sub(/ /, '-') - end - - def form_params(env) - Raygun.log('retrieving form params') - - params = action_dispatch_params(env) || rack_params(env) || {} - filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"]) - end - - def action_dispatch_params(env) - env["action_dispatch.request.parameters"] - end - - def rack_params(env) - request = Rack::Request.new(env) - request.params if env["rack.input"] - end - - def raw_data(rack_env) - Raygun.log('retrieving raw data') - request = Rack::Request.new(rack_env) - - return unless Raygun.configuration.record_raw_data - return if request.get? - Raygun.log('passed raw_data checks') - - input = rack_env['rack.input'] - - if input && !request.form_data? - input.rewind - - body = input.read(4096) || '' - input.rewind - - body - else - {} - end - end - - def filter_custom_data(env) - params = env.delete(:custom_data) || {} - filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"]) - end - - # see http://raygun.io/raygun-providers/rest-json-api?v=1 - def build_payload_hash(exception_instance, env = {}, user = nil) - Raygun.log('building payload hash') - custom_data = filter_custom_data(env) || {} - exception_custom_data = if exception_instance.respond_to?(:raygun_custom_data) - exception_instance.raygun_custom_data - else - {} - end - - tags = env.delete(:tags) || [] - - if rails_env - tags << rails_env - else - tags << rack_env - end - - configuration_tags = [] - if Raygun.configuration.tags.is_a?(Proc) - configuration_tags = Raygun.configuration.tags.call(exception_instance, env) - else - configuration_tags = Raygun.configuration.tags - end - - Raygun.log('set tags') - - grouping_key = env.delete(:grouping_key) - correlation_id = env.delete(:correlation_id) - - configuration_custom_data = Raygun.configuration.custom_data - configured_custom_data = if configuration_custom_data.is_a?(Proc) - configuration_custom_data.call(exception_instance, env) - else - configuration_custom_data - end - - Raygun.log('set custom data') - - error_details = { - machineName: hostname, - version: version, - client: client_details, - error: error_details(exception_instance), - userCustomData: exception_custom_data.merge(custom_data).merge(configured_custom_data), - tags: configuration_tags.concat(tags).compact.uniq, - request: request_information(env), - environment: { - utcOffset: Time.now.utc_offset / 3600 - } - } - store = ::Raygun::Breadcrumbs::Store - error_details[:breadcrumbs] = store.take_until_size(MAX_BREADCRUMBS_SIZE).map(&:build_payload) if store.any? - - Raygun.log('set details and breadcrumbs') - - error_details.merge!(groupingKey: grouping_key) if grouping_key - error_details.merge!(correlationId: correlation_id) if correlation_id - - user_details = if affected_user_present?(env) - user_information(env) - elsif user != nil - AffectedUser.information_hash(user) - end - error_details.merge!(user: user_details) unless user_details == nil - - Raygun.log('set user details') - - if Raygun.configuration.filter_payload_with_whitelist - Raygun.log('filtering payload with whitelist') - error_details = filter_payload_with_whitelist(error_details) - end - - { - occurredOn: Time.now.utc.iso8601, - details: error_details - } - end - - def create_entry(payload_hash) - Raygun.log('sending payload to api') - - self.class.post( - "/entries", - verify_peer: true, - verify: true, - headers: @headers, - body: JSON.generate(payload_hash), - ) - end - - def filter_params_with_blacklist(params_hash = {}, extra_filter_keys = nil) - filter_parameters = Raygun.configuration.filter_parameters - - if filter_parameters.is_a? Proc - filter_parameters.call(params_hash) - else - filter_keys = (Array(extra_filter_keys) + filter_parameters).map(&:to_s) - - filter_params_with_array(params_hash, filter_keys) - end - end - - def filter_payload_with_whitelist(payload_hash) - shape = Raygun.configuration.whitelist_payload_shape - - if shape.is_a? Proc - shape.call(payload_hash) - else - # Always keep the client hash, so force it to true here - Services::ApplyWhitelistFilterToPayload.new.call(shape.merge(client: true), payload_hash) - end - end - - def filter_params_with_array(params_hash, filter_keys) - # Recursive filtering of (nested) hashes - (params_hash || {}).inject({}) do |result, (k, v)| - result[k] = case v - when Hash - filter_params_with_array(v, filter_keys) - else - filter_keys.any? { |fk| /#{fk}/i === k.to_s } ? "[FILTERED]" : v - end - result - end - end - - def ip_address_from(env_hash) - ENV_IP_ADDRESS_KEYS.each do |key_to_try| - return env_hash[key_to_try] unless env_hash[key_to_try].nil? || env_hash[key_to_try] == "" - end - "(Not Available)" - end - - def print_api_key_warning - $stderr.puts(NO_API_KEY_MESSAGE) - end - end -end +module Raygun + # client for the Raygun REST APIv1 + # as per https://raygun.com/documentation/product-guides/crash-reporting/api/ + class Client + + ENV_IP_ADDRESS_KEYS = %w(action_dispatch.remote_ip raygun.remote_ip REMOTE_ADDR) + NO_API_KEY_MESSAGE = "[RAYGUN] Just a note, you've got no API Key configured, which means we can't report exceptions. Specify your Raygun API key using Raygun#setup (find yours at https://app.raygun.com)" + MAX_BREADCRUMBS_SIZE = 100_000 + + include HTTParty + + def initialize + @api_key = require_api_key + @headers = { + "X-ApiKey" => @api_key + } + + enable_http_proxy if Raygun.configuration.proxy_settings[:address] + self.class.base_uri Raygun.configuration.api_url + self.class.default_timeout(Raygun.configuration.error_report_send_timeout) + end + + def require_api_key + Raygun.configuration.api_key || print_api_key_warning + end + + def track_exception(exception_instance, env = {}, user = nil) + create_entry(build_payload_hash(exception_instance, env, user)) + end + + private + + def enable_http_proxy + self.class.http_proxy(Raygun.configuration.proxy_settings[:address], + Raygun.configuration.proxy_settings[:port] || "80", + Raygun.configuration.proxy_settings[:username], + Raygun.configuration.proxy_settings[:password]) + end + + def client_details + { + name: Raygun::CLIENT_NAME, + version: Raygun::VERSION, + clientUrl: Raygun::CLIENT_URL + } + end + + def error_details(exception) + details = { + className: exception.class.to_s, + message: exception.message.to_s.encode('UTF-16', :undef => :replace, :invalid => :replace).encode('UTF-8'), + stackTrace: (exception.backtrace || []).map { |line| stack_trace_for(line) }, + } + + details.update(innerError: error_details(exception.cause)) if exception.respond_to?(:cause) && exception.cause + + details + end + + def stack_trace_for(line) + # see http://www.ruby-doc.org/core-2.0/Exception.html#method-i-backtrace + file_name, line_number, method = line.split(":") + { + lineNumber: line_number, + fileName: file_name, + methodName: method ? method.gsub(/^in `(.*?)'$/, "\\1") : "(none)" + } + end + + def hostname + Socket.gethostname + end + + def version + Raygun.configuration.version + end + + def user_information(env) + env["raygun.affected_user"] + end + + def affected_user_present?(env) + !!env["raygun.affected_user"] + end + + def rack_env + ENV["RACK_ENV"] + end + + def rails_env + ENV["RAILS_ENV"] + end + + def request_information(env) + Raygun.log('retrieving request information') + + return {} if env.nil? || env.empty? + { + hostName: env["SERVER_NAME"], + url: env["PATH_INFO"], + httpMethod: env["REQUEST_METHOD"], + iPAddress: "#{ip_address_from(env)}", + queryString: Rack::Utils.parse_nested_query(env["QUERY_STRING"]), + headers: headers(env), + form: form_params(env), + rawData: raw_data(env) + } + end + + def headers(rack_env) + rack_env.select { |k, v| k.to_s.start_with?("HTTP_") }.inject({}) do |hsh, (k, v)| + hsh[normalize_raygun_header_key(k)] = v + hsh + end + end + + def normalize_raygun_header_key(key) + key.sub(/^HTTP_/, '') + .sub(/_/, ' ') + .split.map(&:capitalize).join(' ') + .sub(/ /, '-') + end + + def form_params(env) + Raygun.log('retrieving form params') + + params = action_dispatch_params(env) || rack_params(env) || {} + filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"]) + end + + def action_dispatch_params(env) + env["action_dispatch.request.parameters"] + end + + def rack_params(env) + request = Rack::Request.new(env) + request.params if env["rack.input"] + end + + def raw_data(rack_env) + Raygun.log('retrieving raw data') + request = Rack::Request.new(rack_env) + + return unless Raygun.configuration.record_raw_data + return if request.get? + Raygun.log('passed raw_data checks') + + input = rack_env['rack.input'] + + if input && !request.form_data? + input.rewind + + body = input.read(4096) || '' + input.rewind + + body + else + {} + end + end + + def filter_custom_data(env) + params = env.delete(:custom_data) || {} + filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"]) + end + + # see https://raygun.com/documentation/product-guides/crash-reporting/api/ + def build_payload_hash(exception_instance, env = {}, user = nil) + Raygun.log('building payload hash') + custom_data = filter_custom_data(env) || {} + exception_custom_data = if exception_instance.respond_to?(:raygun_custom_data) + exception_instance.raygun_custom_data + else + {} + end + + tags = env.delete(:tags) || [] + + if rails_env + tags << rails_env + else + tags << rack_env + end + + configuration_tags = [] + if Raygun.configuration.tags.is_a?(Proc) + configuration_tags = Raygun.configuration.tags.call(exception_instance, env) + else + configuration_tags = Raygun.configuration.tags + end + + Raygun.log('set tags') + + grouping_key = env.delete(:grouping_key) + correlation_id = env.delete(:correlation_id) + + configuration_custom_data = Raygun.configuration.custom_data + configured_custom_data = if configuration_custom_data.is_a?(Proc) + configuration_custom_data.call(exception_instance, env) + else + configuration_custom_data + end + + Raygun.log('set custom data') + + error_details = { + machineName: hostname, + version: version, + client: client_details, + error: error_details(exception_instance), + userCustomData: exception_custom_data.merge(custom_data).merge(configured_custom_data), + tags: configuration_tags.concat(tags).compact.uniq, + request: request_information(env), + environment: { + utcOffset: Time.now.utc_offset / 3600 + } + } + store = ::Raygun::Breadcrumbs::Store + error_details[:breadcrumbs] = store.take_until_size(MAX_BREADCRUMBS_SIZE).map(&:build_payload) if store.any? + + Raygun.log('set details and breadcrumbs') + + error_details.merge!(groupingKey: grouping_key) if grouping_key + error_details.merge!(correlationId: correlation_id) if correlation_id + + user_details = if affected_user_present?(env) + user_information(env) + elsif user != nil + AffectedUser.information_hash(user) + end + error_details.merge!(user: user_details) unless user_details == nil + + Raygun.log('set user details') + + if Raygun.configuration.filter_payload_with_whitelist + Raygun.log('filtering payload with whitelist') + error_details = filter_payload_with_whitelist(error_details) + end + + { + occurredOn: Time.now.utc.iso8601, + details: error_details + } + end + + def create_entry(payload_hash) + Raygun.log('sending payload to api') + + self.class.post( + "/entries", + verify_peer: true, + verify: true, + headers: @headers, + body: JSON.generate(payload_hash), + ) + end + + def filter_params_with_blacklist(params_hash = {}, extra_filter_keys = nil) + filter_parameters = Raygun.configuration.filter_parameters + + if filter_parameters.is_a? Proc + filter_parameters.call(params_hash) + else + filter_keys = (Array(extra_filter_keys) + filter_parameters).map(&:to_s) + + filter_params_with_array(params_hash, filter_keys) + end + end + + def filter_payload_with_whitelist(payload_hash) + shape = Raygun.configuration.whitelist_payload_shape + + if shape.is_a? Proc + shape.call(payload_hash) + else + # Always keep the client hash, so force it to true here + Services::ApplyWhitelistFilterToPayload.new.call(shape.merge(client: true), payload_hash) + end + end + + def filter_params_with_array(params_hash, filter_keys) + # Recursive filtering of (nested) hashes + (params_hash || {}).inject({}) do |result, (k, v)| + result[k] = case v + when Hash + filter_params_with_array(v, filter_keys) + else + filter_keys.any? { |fk| /#{fk}/i === k.to_s } ? "[FILTERED]" : v + end + result + end + end + + def ip_address_from(env_hash) + ENV_IP_ADDRESS_KEYS.each do |key_to_try| + return env_hash[key_to_try] unless env_hash[key_to_try].nil? || env_hash[key_to_try] == "" + end + "(Not Available)" + end + + def print_api_key_warning + $stderr.puts(NO_API_KEY_MESSAGE) + end + end +end