diff --git a/lib/observable/instrumenter.rb b/lib/observable/instrumenter.rb index 6ac0a75..034753b 100644 --- a/lib/observable/instrumenter.rb +++ b/lib/observable/instrumenter.rb @@ -1,5 +1,6 @@ require "opentelemetry/sdk" require_relative "configuration" +require_relative "serialization_depth" module Observable class Instrumenter @@ -8,6 +9,7 @@ class Instrumenter def initialize(tracer: nil, config: nil) @config = config || Configuration.config @tracer = tracer || OpenTelemetry.tracer_provider.tracer(@config.tracer_name) + @serialization_depth = SerializationDepth.new(@config.serialization_depth) end def instrument(caller_binding = nil, &block) @@ -52,7 +54,7 @@ def extract_namespace_from_path(file_path) return "UnknownClass" if file_name.empty? file_name.split("_").map(&:capitalize).join - rescue + rescue StandardError "UnknownClass" end @@ -65,14 +67,14 @@ def extract_actual_class_name else caller_self.class.name end - rescue + rescue StandardError extract_namespace_from_path(@caller_binding.eval("__FILE__")) end else caller_location = find_caller_location extract_namespace_from_path(caller_location.path) end - rescue + rescue StandardError "UnknownClass" end @@ -82,28 +84,20 @@ def determine_if_class_method begin caller_self = @caller_binding.eval("self") caller_self.is_a?(Class) - rescue + rescue StandardError false end end def create_instrumented_span(caller_info, &block) separator = caller_info.is_class_method ? "." : "#" - - if caller_info.method_name.include?("#") || caller_info.method_name.include?(".") - span_name = "#{caller_info.namespace}#{separator}#{caller_info.method_name.split(/[#.]/).last}" - function_name = "#{caller_info.namespace}#{separator}#{caller_info.method_name.split(/[#.]/).last}" - method_name_only = caller_info.method_name.split(/[#.]/).last - else - span_name = "#{caller_info.namespace}#{separator}#{caller_info.method_name}" - function_name = "#{caller_info.namespace}#{separator}#{caller_info.method_name}" - method_name_only = caller_info.method_name - end + method_name_only = caller_info.method_name.split(/[#.]/).last + qualified_name = "#{caller_info.namespace}#{separator}#{method_name_only}" @last_captured_method_name = method_name_only @last_captured_namespace = caller_info.namespace - @tracer.in_span(span_name) do |span| - set_span_attributes(span, caller_info, function_name) + @tracer.in_span(qualified_name) do |span| + set_span_attributes(span, caller_info, qualified_name) begin result = block.call @@ -143,7 +137,7 @@ def set_span_attributes(span, caller_info, function_name) end end - def serialize_argument(span, attribute_prefix, value, param_name = nil, depth = 0) + def serialize_argument(span, attribute_prefix, value, param_name = nil, depth = 0, max_depth = nil) if param_name && should_filter_pii?(param_name) span.set_attribute(attribute_prefix, "[FILTERED]") return @@ -155,29 +149,11 @@ def serialize_argument(span, attribute_prefix, value, param_name = nil, depth = when NilClass span.set_attribute(attribute_prefix, "nil") when Hash - serialize_hash(span, attribute_prefix, value, depth) + serialize_hash(span, attribute_prefix, value, depth, max_depth) when Array - serialize_array(span, attribute_prefix, value, depth) + serialize_array(span, attribute_prefix, value, depth, max_depth) else - serialize_object(span, attribute_prefix, value, depth) - end - end - - def serialize_argument_with_custom_depth(span, attribute_prefix, value, custom_max_depth, param_name = nil, depth = 0) - if param_name && should_filter_pii?(param_name) - span.set_attribute(attribute_prefix, "[FILTERED]") - return - end - - case value - when String, Numeric, TrueClass, FalseClass, NilClass - span.set_attribute(attribute_prefix, value) - when Hash - serialize_hash_with_custom_depth(span, attribute_prefix, value, custom_max_depth, depth) - when Array - serialize_array_with_custom_depth(span, attribute_prefix, value, custom_max_depth, depth) - else - serialize_object_with_custom_depth(span, attribute_prefix, value, custom_max_depth, depth) + serialize_object(span, attribute_prefix, value, depth, max_depth) end end @@ -190,89 +166,38 @@ def serialize_return_value(span, value) when String, Numeric, TrueClass, FalseClass span.set_attribute("code.return", value) when Hash - serialize_hash(span, "code.return", value, 2) + serialize_hash(span, "code.return", value, DEFAULT_SERIALIZATION_DEPTH) when Array - serialize_array(span, "code.return", value, 2) + serialize_array(span, "code.return", value, DEFAULT_SERIALIZATION_DEPTH) else - serialize_object(span, "code.return", value, 2) + serialize_object(span, "code.return", value, DEFAULT_SERIALIZATION_DEPTH) end end - def serialize_hash(span, prefix, hash, depth = 0) - max_depth = get_serialization_depth_for_class("Hash") + def serialize_hash(span, prefix, hash, depth = 0, max_depth = nil) + max_depth ||= get_serialization_depth_for_class("Hash") return if depth > max_depth hash.each do |key, value| key_str = key.to_s next if should_filter_pii?(key_str) - serialize_argument(span, "#{prefix}.#{key_str}", value, nil, depth + 1) - end - end - - def serialize_hash_with_custom_depth(span, prefix, hash, custom_max_depth, depth = 0) - return if depth > custom_max_depth - - hash.each do |key, value| - key_str = key.to_s - next if should_filter_pii?(key_str) - serialize_argument_with_custom_depth(span, "#{prefix}.#{key_str}", value, custom_max_depth, nil, depth + 1) + serialize_argument(span, "#{prefix}.#{key_str}", value, nil, depth + 1, max_depth) end end - def serialize_array_with_custom_depth(span, prefix, array, custom_max_depth, depth = 0) - return if depth > custom_max_depth - - items_to_process = [array.length, 10].min - array.first(items_to_process).each_with_index do |item, index| - serialize_argument_with_custom_depth(span, "#{prefix}.#{index}", item, custom_max_depth, nil, depth + 1) - end - end - - def serialize_object_with_custom_depth(span, prefix, obj, custom_max_depth, depth = 0) - if depth >= custom_max_depth - span.set_attribute("#{prefix}.class", obj.class.name) - span.set_attribute(prefix, obj.to_s) - return - end - - span.set_attribute("#{prefix}.class", obj.class.name) - - formatter = find_formatter_for_class(obj.class.name) - - if formatter && obj.respond_to?(formatter) - result = obj.send(formatter) - if result.is_a?(Hash) - serialize_hash_with_custom_depth(span, prefix, result, custom_max_depth, depth) - return - end - end - - default_formatter = @config.formatters[:default] - if default_formatter && obj.respond_to?(default_formatter) - result = obj.send(default_formatter) - if result.is_a?(Hash) - serialize_hash_with_custom_depth(span, prefix, result, custom_max_depth, depth) - return - end - end - - span.set_attribute(prefix, obj.to_s) - end - - def serialize_array(span, prefix, array, depth = 0) - max_depth = get_serialization_depth_for_class("Array") + def serialize_array(span, prefix, array, depth = 0, max_depth = nil) + max_depth ||= get_serialization_depth_for_class("Array") return if depth > max_depth - items_to_process = [array.length, 10].min + items_to_process = [array.length, MAX_ARRAY_ITEMS].min array.first(items_to_process).each_with_index do |item, index| - serialize_argument(span, "#{prefix}.#{index}", item, nil, depth + 1) + serialize_argument(span, "#{prefix}.#{index}", item, nil, depth + 1, max_depth) end end - def serialize_object(span, prefix, obj, depth = 0) - max_depth = get_serialization_depth_for_class(obj.class.name) + def serialize_object(span, prefix, obj, depth = 0, max_depth = nil) + max_depth ||= get_serialization_depth_for_class(obj.class.name) - # If we've reached the depth limit for this class, just set the class name if depth >= max_depth span.set_attribute("#{prefix}.class", obj.class.name) span.set_attribute(prefix, obj.to_s) @@ -286,9 +211,7 @@ def serialize_object(span, prefix, obj, depth = 0) if formatter && obj.respond_to?(formatter) result = obj.send(formatter) if result.is_a?(Hash) - # When object converts to hash, the hash starts fresh at depth 0 - # but we need to enforce the object's depth limit - serialize_hash_with_custom_depth(span, prefix, result, max_depth, 0) + serialize_hash(span, prefix, result, 0, max_depth) return end end @@ -297,9 +220,7 @@ def serialize_object(span, prefix, obj, depth = 0) if default_formatter && obj.respond_to?(default_formatter) result = obj.send(default_formatter) if result.is_a?(Hash) - # When object converts to hash, the hash starts fresh at depth 0 - # but we need to enforce the object's depth limit - serialize_hash_with_custom_depth(span, prefix, result, max_depth, 0) + serialize_hash(span, prefix, result, 0, max_depth) return end end @@ -312,11 +233,7 @@ def find_formatter_for_class(class_name) end def get_serialization_depth_for_class(class_name) - if @config.serialization_depth.is_a?(Integer) - return @config.serialization_depth - end - - @config.serialization_depth[class_name] || @config.serialization_depth[:default] || @config.serialization_depth["default"] || 2 + @serialization_depth.for_class(class_name) end def should_filter_pii?(param_name) @@ -353,7 +270,7 @@ def extract_from_binding extract_local_variables_with_numeric_indexing(args) args - rescue + rescue StandardError {} end diff --git a/lib/observable/persistence/span_repo.rb b/lib/observable/persistence/span_repo.rb index 2d1a7db..6b05be3 100644 --- a/lib/observable/persistence/span_repo.rb +++ b/lib/observable/persistence/span_repo.rb @@ -130,12 +130,7 @@ def get_value(hash, key) end def colorize_trace_header(trace_id) - # Use the same color constants from Span - cyan = "\e[36m" - bold = "\e[1m" - reset = "\e[0m" - - "#{cyan}#{bold}#{trace_id}#{reset}" + "#{Span::CYAN}#{Span::BOLD}#{trace_id}#{Span::RESET}" end end end diff --git a/lib/observable/serialization_depth.rb b/lib/observable/serialization_depth.rb new file mode 100644 index 0000000..a7669a5 --- /dev/null +++ b/lib/observable/serialization_depth.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Observable + class SerializationDepth + DEFAULT_DEPTH = 2 + + def initialize(config) + @config = config + end + + def for_class(class_name) + if @config.is_a?(Integer) + @config + else + @config[class_name] || @config[:default] || @config["default"] || DEFAULT_DEPTH + end + end + end +end diff --git a/lib/observable/structured_error.rb b/lib/observable/structured_error.rb index b3484da..247e5b7 100644 --- a/lib/observable/structured_error.rb +++ b/lib/observable/structured_error.rb @@ -45,7 +45,7 @@ def self.safe_message(error) else "" end - rescue + rescue StandardError "" end @@ -63,7 +63,7 @@ def self.safe_type(error) end error.class.name - rescue + rescue StandardError error.class.name end @@ -77,7 +77,7 @@ def self.safe_context(error) else {} end - rescue + rescue StandardError {} end @@ -92,7 +92,7 @@ def self.try_custom_converter(error) result = begin converter.call(error) - rescue + rescue StandardError nil end