Skip to content

Commit

Permalink
refactor(helpers): refactor helpers module
Browse files Browse the repository at this point in the history
- Refactors helper module into separate files in "helpers" folder
- Existing "helpers" file left as facilitator to split helper module
- Report "generate_raw_exceptions" function split into helper file
  • Loading branch information
Cawllec committed Mar 19, 2018
1 parent 7b7f249 commit 5a6dd12
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 195 deletions.
159 changes: 3 additions & 156 deletions lib/bugsnag/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,156 +1,3 @@
require 'uri'
require 'set'
require 'json'


module Bugsnag
module Helpers
MAX_STRING_LENGTH = 3072
MAX_PAYLOAD_LENGTH = 256000
MAX_ARRAY_LENGTH = 40
RAW_DATA_TYPES = [Numeric, TrueClass, FalseClass]

##
# Trim the size of value if the serialized JSON value is longer than is
# accepted by Bugsnag
def self.trim_if_needed(value)
value = "" if value.nil?
sanitized_value = Bugsnag::Cleaner.clean_object_encoding(value)
return sanitized_value unless payload_too_long?(sanitized_value)
reduced_value = trim_strings_in_value(sanitized_value)
return reduced_value unless payload_too_long?(reduced_value)
reduced_value = truncate_arrays_in_value(reduced_value)
return reduced_value unless payload_too_long?(reduced_value)
remove_metadata_from_events(reduced_value)
end

##
# Merges r_hash into l_hash recursively, favouring the values in r_hash.
#
# Returns a new array consisting of the merged values
def self.deep_merge(l_hash, r_hash)
l_hash.merge(r_hash) do |key, l_val, r_val|
if l_val.is_a?(Hash) && r_val.is_a?(Hash)
deep_merge(l_val, r_val)
elsif l_val.is_a?(Array) && r_val.is_a?(Array)
l_val.concat(r_val)
else
r_val
end
end
end

##
# Merges r_hash into l_hash recursively, favouring the values in r_hash.
#
# Overwrites the values in the existing l_hash
def self.deep_merge!(l_hash, r_hash)
l_hash.merge!(r_hash) do |key, l_val, r_val|
if l_val.is_a?(Hash) && r_val.is_a?(Hash)
deep_merge(l_val, r_val)
elsif l_val.is_a?(Array) && r_val.is_a?(Array)
l_val.concat(r_val)
else
r_val
end
end
end

private

TRUNCATION_INFO = '[TRUNCATED]'

##
# Check if a value is a raw type which should not be trimmed, truncated
# or converted to a string
def self.is_json_raw_type?(value)
RAW_DATA_TYPES.detect {|klass| value.is_a?(klass)} != nil
end

# Shorten array until it fits within the payload size limit when serialized
def self.truncate_array(array)
return [] unless array.respond_to?(:slice)
array.slice(0, MAX_ARRAY_LENGTH).map do |item|
truncate_arrays_in_value(item)
end
end

# Trim all strings to be less than the maximum allowed string length
def self.trim_strings_in_value(value)
return value if is_json_raw_type?(value)
case value
when Hash
trim_strings_in_hash(value)
when Array, Set
trim_strings_in_array(value)
else
trim_as_string(value)
end
end

# Validate that the serialized JSON string value is below maximum payload
# length
def self.payload_too_long?(value)
if value.is_a?(String)
value.length >= MAX_PAYLOAD_LENGTH
else
::JSON.dump(value).length >= MAX_PAYLOAD_LENGTH
end
end

def self.trim_strings_in_hash(hash)
return {} unless hash.is_a?(Hash)
hash.each_with_object({}) do |(key, value), reduced_hash|
if reduced_value = trim_strings_in_value(value)
reduced_hash[key] = reduced_value
end
end
end

# If possible, convert the provided object to a string and trim to the
# maximum allowed string length
def self.trim_as_string(text)
return "" unless text.respond_to? :to_s
text = text.to_s
if text.length > MAX_STRING_LENGTH
length = MAX_STRING_LENGTH - TRUNCATION_INFO.length
text = text.slice(0, length) + TRUNCATION_INFO
end
text
end

def self.trim_strings_in_array(collection)
return [] unless collection.respond_to?(:map)
collection.map {|value| trim_strings_in_value(value)}
end

def self.truncate_arrays_in_value(value)
case value
when Hash
truncate_arrays_in_hash(value)
when Array, Set
truncate_array(value)
else
value
end
end

# Remove `metaData` from array of `events` within object
def self.remove_metadata_from_events(object)
return {} unless object.is_a?(Hash) and object[:events].respond_to?(:map)
object[:events].map do |event|
event.delete(:metaData) if object.is_a?(Hash)
end
object
end

def self.truncate_arrays_in_hash(hash)
return {} unless hash.is_a?(Hash)
hash.each_with_object({}) do |(key, value), reduced_hash|
if reduced_value = truncate_arrays_in_value(value)
reduced_hash[key] = reduced_value
end
end
end
end
end
require "bugsnag/helpers/exceptions"
require "bugsnag/helpers/merge"
require "bugsnag/helpers/trim"
43 changes: 43 additions & 0 deletions lib/bugsnag/helpers/exceptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Bugsnag
module Helpers
MAX_EXCEPTIONS_TO_UNWRAP = 5

class << self
##
# Generates a list of exceptions
def generate_raw_exceptions(exception)
exceptions = []

ex = exception
while !ex.nil? && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP

unless ex.is_a? Exception
if ex.respond_to?(:to_exception)
ex = ex.to_exception
elsif ex.respond_to?(:exception)
ex = ex.exception
end
end

unless ex.is_a?(Exception) || (defined?(Java::JavaLang::Throwable) && ex.is_a?(Java::JavaLang::Throwable))
Bugsnag.configuration.warn("Converting non-Exception to RuntimeError: #{ex.inspect}")
ex = RuntimeError.new(ex.to_s)
ex.set_backtrace caller
end

exceptions << ex

ex = if ex.respond_to?(:cause) && ex.cause
ex.cause
elsif ex.respond_to?(:continued_exception) && ex.continued_exception
ex.continued_exception
elsif ex.respond_to?(:original_exception) && ex.original_exception
ex.original_exception
end
end

exceptions
end
end
end
end
37 changes: 37 additions & 0 deletions lib/bugsnag/helpers/merge.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Bugsnag
module Helpers
class << self
##
# Merges r_hash into l_hash recursively, favouring the values in r_hash.
#
# Returns a new array consisting of the merged values
def deep_merge(l_hash, r_hash)
l_hash.merge(r_hash) do |_key, l_val, r_val|
if l_val.is_a?(Hash) && r_val.is_a?(Hash)
deep_merge(l_val, r_val)
elsif l_val.is_a?(Array) && r_val.is_a?(Array)
l_val.concat(r_val)
else
r_val
end
end
end

##
# Merges r_hash into l_hash recursively, favouring the values in r_hash.
#
# Overwrites the values in the existing l_hash
def deep_merge!(l_hash, r_hash)
l_hash.merge!(r_hash) do |_key, l_val, r_val|
if l_val.is_a?(Hash) && r_val.is_a?(Hash)
deep_merge(l_val, r_val)
elsif l_val.is_a?(Array) && r_val.is_a?(Array)
l_val.concat(r_val)
else
r_val
end
end
end
end
end
end
125 changes: 125 additions & 0 deletions lib/bugsnag/helpers/trim.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
require 'uri'
require 'set'
require 'json'

module Bugsnag
module Helpers
MAX_STRING_LENGTH = 3072
MAX_PAYLOAD_LENGTH = 256000
MAX_ARRAY_LENGTH = 40
RAW_DATA_TYPES = [Numeric, TrueClass, FalseClass]

class << self
##
# Trim the size of value if the serialized JSON value is longer than is
# accepted by Bugsnag
def trim_if_needed(value)
value = "" if value.nil?
sanitized_value = Bugsnag::Cleaner.clean_object_encoding(value)
return sanitized_value unless payload_too_long?(sanitized_value)
reduced_value = trim_strings_in_value(sanitized_value)
return reduced_value unless payload_too_long?(reduced_value)
reduced_value = truncate_arrays_in_value(reduced_value)
return reduced_value unless payload_too_long?(reduced_value)
remove_metadata_from_events(reduced_value)
end

private

TRUNCATION_INFO = '[TRUNCATED]'

##
# Check if a value is a raw type which should not be trimmed, truncated
# or converted to a string
def json_raw_type?(value)
RAW_DATA_TYPES.detect { |klass| value.is_a?(klass) } != nil
end

# Shorten array until it fits within the payload size limit when serialized
def truncate_array(array)
return [] unless array.respond_to?(:slice)
array.slice(0, MAX_ARRAY_LENGTH).map do |item|
truncate_arrays_in_value(item)
end
end

# Trim all strings to be less than the maximum allowed string length
def trim_strings_in_value(value)
return value if json_raw_type?(value)
case value
when Hash
trim_strings_in_hash(value)
when Array, Set
trim_strings_in_array(value)
else
trim_as_string(value)
end
end

# Validate that the serialized JSON string value is below maximum payload
# length
def payload_too_long?(value)
if value.is_a?(String)
value.length >= MAX_PAYLOAD_LENGTH
else
::JSON.dump(value).length >= MAX_PAYLOAD_LENGTH
end
end

def trim_strings_in_hash(hash)
return {} unless hash.is_a?(Hash)
hash.each_with_object({}) do |(key, value), reduced_hash|
if (reduced_value = trim_strings_in_value(value))
reduced_hash[key] = reduced_value
end
end
end

# If possible, convert the provided object to a string and trim to the
# maximum allowed string length
def trim_as_string(text)
return "" unless text.respond_to? :to_s
text = text.to_s
if text.length > MAX_STRING_LENGTH
length = MAX_STRING_LENGTH - TRUNCATION_INFO.length
text = text.slice(0, length) + TRUNCATION_INFO
end
text
end

def trim_strings_in_array(collection)
return [] unless collection.respond_to?(:map)
collection.map { |value| trim_strings_in_value(value) }
end

def truncate_arrays_in_value(value)
case value
when Hash
truncate_arrays_in_hash(value)
when Array, Set
truncate_array(value)
else
value
end
end

# Remove `metaData` from array of `events` within object
def remove_metadata_from_events(object)
return {} unless object.is_a?(Hash) && object[:events].respond_to?(:map)
object[:events].map do |event|
event.delete(:metaData) if object.is_a?(Hash)
end
object
end

def truncate_arrays_in_hash(hash)
return {} unless hash.is_a?(Hash)
hash.each_with_object({}) do |(key, value), reduced_hash|
if (reduced_value = truncate_arrays_in_value(value))
reduced_hash[key] = reduced_value
end
end
end
end
end
end
Loading

0 comments on commit 5a6dd12

Please sign in to comment.