rshade 0.1.9.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/Gemfile +6 -2
  4. data/Gemfile.lock +10 -1
  5. data/README.md +19 -3
  6. data/Rakefile +5 -3
  7. data/bin/console +4 -3
  8. data/lib/rshade/config/registry.rb +47 -0
  9. data/lib/rshade/config/stack_store.rb +58 -0
  10. data/lib/rshade/config/store.rb +6 -5
  11. data/lib/rshade/config.rb +4 -3
  12. data/lib/rshade/core_extensions/object/reveal.rb +34 -5
  13. data/lib/rshade/event.rb +15 -5
  14. data/lib/rshade/event_observer.rb +3 -1
  15. data/lib/rshade/event_processor.rb +12 -11
  16. data/lib/rshade/event_tree.rb +13 -15
  17. data/lib/rshade/filter/abstract_filter.rb +3 -1
  18. data/lib/rshade/filter/default.rb +3 -1
  19. data/lib/rshade/filter/exclude_path_filter.rb +5 -3
  20. data/lib/rshade/filter/filter_builder.rb +14 -3
  21. data/lib/rshade/filter/filter_composition.rb +26 -24
  22. data/lib/rshade/filter/include_path_filter.rb +5 -1
  23. data/lib/rshade/filter/variable_filter.rb +4 -1
  24. data/lib/rshade/formatter/stack/json.rb +51 -0
  25. data/lib/rshade/formatter/stack/stdout.rb +13 -0
  26. data/lib/rshade/formatter/stack/string.rb +41 -0
  27. data/lib/rshade/formatter/trace/file.rb +33 -0
  28. data/lib/rshade/formatter/trace/html.rb +38 -0
  29. data/lib/rshade/formatter/trace/json.rb +61 -0
  30. data/lib/rshade/formatter/trace/stdout.rb +14 -0
  31. data/lib/rshade/formatter/trace/string.rb +48 -0
  32. data/lib/rshade/rspec/rspec.rb +7 -6
  33. data/lib/rshade/serializer/traversal.rb +16 -9
  34. data/lib/rshade/stack.rb +26 -0
  35. data/lib/rshade/stack_frame.rb +60 -0
  36. data/lib/rshade/trace.rb +7 -4
  37. data/lib/rshade/trace_observable.rb +5 -0
  38. data/lib/rshade/version.rb +3 -1
  39. data/lib/rshade.rb +25 -8
  40. data/rshade.gemspec +24 -21
  41. metadata +43 -8
  42. data/lib/rshade/formatter/file.rb +0 -28
  43. data/lib/rshade/formatter/html.rb +0 -33
  44. data/lib/rshade/formatter/json.rb +0 -59
  45. data/lib/rshade/formatter/stdout.rb +0 -10
  46. data/lib/rshade/formatter/string.rb +0 -36
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  module Filter
3
5
  class FilterComposition
@@ -5,53 +7,53 @@ module RShade
5
7
  AND_OP = :and
6
8
  OR_OP = :or
7
9
  UNARY_OP = :unary
8
- attr_reader :op, :left, :right, :parent
10
+ attr_reader :value, :left, :right, :parent
9
11
  attr_accessor :parent
10
12
 
11
13
  # @param [#call, Enumerable] left
12
14
  # @param [#call, Enumerable] right
13
- def initialize(op=UNARY_OP, left=nil, right=nil)
14
- @op = op
15
+ def initialize(value, left = nil, right = nil)
16
+ @value = value
15
17
  @left = left
16
18
  @right = right
17
19
  end
18
20
 
19
21
  def call(event)
20
- case op
22
+ case value
21
23
  when UNARY_OP
22
- return left&.call(event)
24
+ left&.call(event)
23
25
  when AND_OP
24
- return left&.call(event) && right&.call(event)
26
+ left&.call(event) && right&.call(event)
25
27
  when OR_OP
26
- l = left&.call(event)
27
- r = right&.call(event)
28
- # puts "#{left} => #{l} OR #{right} => #{r}"
29
- return l || r
28
+ left&.call(event) || right&.call(event)
30
29
  else
31
- raise 'undefined op'
30
+ value.call(event)
32
31
  end
33
32
  end
34
33
 
35
34
  def each(&block)
36
- if left&.respond_to?(:each)
37
- left&.each(&block)
38
- else
39
- yield left
40
- end
41
-
42
- if right&.respond_to?(:each)
43
- right&.each(&block)
44
- else
45
- yield right
46
- end
35
+ yield value unless left && right
36
+ left&.each(&block)
37
+ right&.each(&block)
47
38
  end
48
39
 
49
40
  def config_filter(type, &block)
50
41
  filter = find do |filter|
51
42
  filter.is_a? type
52
43
  end
53
- filter.config(&block) if filter
44
+ filter&.config(&block)
45
+ end
46
+
47
+ # for debug purposes, show each filter and result of evaluation
48
+ def filter_results(event)
49
+ each_with_object([]) do |filter, arr|
50
+ arr << [filter, filter.call(event)]
51
+ end
52
+ end
53
+
54
+ def self.build(arr)
55
+ ::RShade::Filter::FilterBuilder.build(arr)
54
56
  end
55
57
  end
56
58
  end
57
- end
59
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  module Filter
3
5
  class IncludePathFilter < AbstractFilter
@@ -22,6 +24,7 @@ module RShade
22
24
  paths.any? do |path|
23
25
  next str?(path, event_path) if path.is_a? String
24
26
  next regexp?(path, event_path) if path.is_a? Regexp
27
+
25
28
  false
26
29
  end
27
30
  end
@@ -31,6 +34,7 @@ module RShade
31
34
  end
32
35
 
33
36
  private
37
+
34
38
  def str?(str, event_path)
35
39
  event_path.include?(str)
36
40
  end
@@ -40,4 +44,4 @@ module RShade
40
44
  end
41
45
  end
42
46
  end
43
- end
47
+ end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  module Filter
3
5
  class VariableFilter < AbstractFilter
4
6
  attr_reader :matchers
7
+
5
8
  NAME = :variable_filter
6
9
 
7
10
  def initialize
@@ -30,4 +33,4 @@ module RShade
30
33
  end
31
34
  end
32
35
  end
33
- end
36
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Stack
6
+ class Json
7
+ attr_reader :filepath, :pretty
8
+
9
+ def initialize(filepath:, pretty: false)
10
+ @filepath = filepath
11
+ @pretty = pretty
12
+ end
13
+
14
+ # @param [Array<RShade::StackFrame>] stack_frames
15
+ def call(stack_frames)
16
+ payload = stack_frames.map.with_index do |frame, idx|
17
+ serialize(idx, frame)
18
+ end
19
+
20
+ File.open(filepath, 'a+') do |file|
21
+ record = {
22
+ time: Time.now.getutc,
23
+ thread_id: Thread.current,
24
+ thread_list: Thread.list,
25
+ frames: payload
26
+ }
27
+ file.puts(convert_to_json(record, pretty))
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def convert_to_json(object, pretty)
34
+ return JSON.pretty_generate(object) if pretty
35
+
36
+ JSON.generate(object)
37
+ end
38
+
39
+ # @param [RShade::StackFrame] frame
40
+ def serialize(idx, frame)
41
+ {
42
+ frame: idx,
43
+ source_location: "#{frame.source_location[:path]}:#{frame.source_location[:line]}",
44
+ local_variables: frame.local_vars,
45
+ receiver_variables: frame.receiver_variables
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Stack
6
+ class Stdout < String
7
+ def call(stack)
8
+ puts super(stack)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Stack
6
+ class String
7
+ ROOT_SEP = "---\n"
8
+
9
+ def initialize(opts = {}); end
10
+
11
+ # @param [RShade::EventProcessor] stack
12
+ def call(stack)
13
+ buffer = StringIO.new
14
+ stack.each_with_index do |frame, idx|
15
+ if idx.zero?
16
+ buffer << ROOT_SEP
17
+ next
18
+ end
19
+ next unless frame
20
+
21
+ buffer.write line(idx, frame, idx)
22
+ end
23
+ buffer.string
24
+ end
25
+
26
+ # @param [RShade::StackFrame] frame
27
+ def line(line_idx, frame, depth)
28
+ source_location = ColorizedString["(#{frame.source_location[:path]}:#{frame.source_location[:line]})"].colorize(:green)
29
+ var_str = frame.local_vars.map do |_, val|
30
+ var_name = val[:name]
31
+ var_value = val[:value]
32
+ var_type = val[:type]
33
+ "#{var_type} #{var_name} => (#{var_value})"
34
+ end.join(', ')
35
+ colorized_var_str = ColorizedString[var_str].colorize(:blue)
36
+ "#{' ' * depth}[frame: #{line_idx}]#{source_location} => local vars: (#{colorized_var_str})\n"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Trace
6
+ class File
7
+ attr_reader :formatter
8
+
9
+ FILE_NAME = 'stacktrace.json'
10
+
11
+ def initialize(args = {})
12
+ @formatter = args.fetch(:format, Json)
13
+ end
14
+
15
+ # @param [RShade::EventProcessor] event_store
16
+ def call(event_store)
17
+ data = formatter.call(event_store)
18
+ if formatter == Json
19
+ write_to_file(JSON.pretty_generate(data))
20
+ else
21
+ write_to_file(data.to_s)
22
+ end
23
+ end
24
+
25
+ def write_to_file(data)
26
+ ::File.open(::File.join(RShade::Config.store_dir, FILE_NAME), 'w+') do |f|
27
+ f.write data
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module RShade
6
+ module Formatter
7
+ module Trace
8
+ class Html
9
+ attr_reader :formatter
10
+
11
+ FILE_NAME = 'stacktrace.html'
12
+ TEMPLATE = 'html/template.html.erb'
13
+
14
+ def initialize(args = {})
15
+ @formatter = args.fetch(:formatter, Json)
16
+ end
17
+
18
+ # @param [RShade::EventProcessor] event_store
19
+ def call(event_store)
20
+ data = formatter.call(event_store)
21
+ erb_template = ERB.new(template)
22
+ content = erb_template.result_with_hash({ json: data.to_json })
23
+ write_to_file(content)
24
+ end
25
+
26
+ def write_to_file(data)
27
+ ::File.open(::File.join(RShade::Config.store_dir, FILE_NAME), 'w+') do |f|
28
+ f.write data
29
+ end
30
+ end
31
+
32
+ def template
33
+ @template ||= ::File.read(::File.join(::RShade::Config.root_dir, TEMPLATE))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Trace
6
+ class Json
7
+ attr_reader :event_processor
8
+
9
+ # @param [RShade::EventProcessor] event_store
10
+ def call(event_store)
11
+ @event_store = event_store
12
+ flat
13
+ end
14
+
15
+ def flat
16
+ arr = []
17
+ event_store.each do |node|
18
+ arr << item(node.event)
19
+ end
20
+ arr.sort_by { |item| item[:level] }
21
+ end
22
+
23
+ def hierarchical
24
+ hash = {}
25
+ event_store.each do |node|
26
+ depth = node.level
27
+ ref = hash_iterate(hash, depth)
28
+ ref[:data] = item(node)
29
+ end
30
+ sort_hash(hash)
31
+ end
32
+
33
+ def sort_hash(h)
34
+ {}.tap do |h2|
35
+ h.sort.each do |k, v|
36
+ h2[k] = v.is_a?(Hash) ? sort_hash(v) : v
37
+ end
38
+ end
39
+ end
40
+
41
+ def hash_iterate(hash, depth)
42
+ (0..depth).each do |_lvl|
43
+ hash[:inner] = {} unless hash[:inner]
44
+ hash = hash[:inner]
45
+ end
46
+ hash
47
+ end
48
+
49
+ def item(value)
50
+ {
51
+ class: value.klass.to_s,
52
+ method_name: value.method_name,
53
+ full_path: "#{value.path}:#{value.lineno}",
54
+ level: value.depth,
55
+ vars: value.vars
56
+ }
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Trace
6
+ class Stdout < String
7
+ # @param [RShade::EventProcessor] event_store
8
+ def call(event_store)
9
+ puts super(event_store)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Trace
6
+ class String
7
+ ROOT_SEP = "---\n"
8
+
9
+ def initialize(opts = {}); end
10
+
11
+ # @param [RShade::EventProcessor] event_store
12
+ def call(event_store)
13
+ buffer = StringIO.new
14
+ event_store.each_with_index do |node, idx|
15
+ depth = node.level
16
+ event = node.value
17
+ if depth == 1
18
+ buffer << ROOT_SEP
19
+ next
20
+ end
21
+ next unless event
22
+
23
+ buffer.write line(idx, event, node.vlevel)
24
+ end
25
+ buffer.string
26
+ end
27
+
28
+ def line(line_idx, value, depth)
29
+ vars = value.vars
30
+ returned_value = value.return_value || {}
31
+ returned_str = "#{returned_value[:type]} #{returned_value[:value]}"
32
+ returned = ColorizedString["=> |#{returned_str}|"].colorize(:magenta)
33
+
34
+ class_method = ColorizedString["#{value.klass}##{value.method_name}"].colorize(:green)
35
+ full_path = ColorizedString["#{value.path}:#{value.lineno}"].colorize(:blue)
36
+ line_idx = ColorizedString["[#{line_idx}] "].colorize(:red)
37
+ var_str = vars.map do |_, val|
38
+ var_name = val[:name]
39
+ var_value = val[:value]
40
+ var_type = val[:type]
41
+ "#{var_type} #{var_name} => (#{var_value})"
42
+ end.join(', ')
43
+ "#{' ' * depth}#{line_idx}#{class_method}(#{var_str}) #{returned} -> #{full_path}\n"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,13 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
- REPORTS = []
4
+ REPORTS = [].freeze
3
5
 
4
6
  module RSpecHelper
5
- def rshade_reveal(options = {})
7
+ def rshade_reveal(options = {}, &block)
6
8
  raise 'No block given' unless block_given?
7
- options.merge!(formatter: Formatter::String) { |_key,v1, _v2| v1 }
8
- result = Trace.reveal(options) do
9
- yield
10
- end
9
+
10
+ options.merge!(formatter: Formatter::String) { |_key, v1, _v2| v1 }
11
+ result = Trace.reveal(options, &block)
11
12
  REPORTS.push result.show
12
13
  end
13
14
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  module Serializer
3
5
  class Traversal
4
6
  attr_reader :types
5
7
 
6
- def initialize(custom_types={})
8
+ def initialize(custom_types = {})
7
9
  @types = default_types.merge(custom_types)
8
10
  end
9
11
 
@@ -17,31 +19,36 @@ module RShade
17
19
 
18
20
  def default_types
19
21
  {
20
- default: ->(value) { value.to_s },
22
+ default: lambda(&:to_s),
21
23
  Integer => ->(value) { value },
22
24
  Float => ->(value) { value },
23
25
  Numeric => ->(value) { value },
24
26
  String => ->(value) { value },
25
- Hash => ->(value) do
27
+ Hash => lambda do |value|
26
28
  hash = {}
27
- value.each do |k,v|
28
- hash[k] = traverse(v)
29
+ begin
30
+ value.each do |k, v|
31
+ hash[k] = traverse(v)
32
+ end
33
+ rescue Exception => e
34
+ binding.pry
29
35
  end
36
+
30
37
  hash
31
38
  end,
32
- Array => ->(value) do
39
+ Array => lambda do |value|
33
40
  value.map { |item| traverse(item) }
34
41
  end
35
42
  }
36
43
  end
37
44
 
38
45
  def traverse(value)
39
- klass = value
46
+ klass = Class
40
47
  klass = value.class unless value.is_a?(Class)
41
48
  serializer = types[klass]
42
- serializer = types[:default] unless serializer
49
+ serializer ||= types[:default]
43
50
  serializer.call(value)
44
51
  end
45
52
  end
46
53
  end
47
- end
54
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ class Stack
5
+ attr_reader :config
6
+
7
+ def initialize(config: nil, registry: ::RShade::Config::Registry.instance)
8
+ @config = config || registry.default_stack_config
9
+ end
10
+
11
+ def self.trace(config: nil)
12
+ new(config:).trace
13
+ end
14
+
15
+ def trace
16
+ config.exclude_gems!
17
+ result = binding.callers.drop(2).map do |bind|
18
+ frame = StackFrame.from_binding(bind)
19
+ next nil unless config.filter.call(frame)
20
+
21
+ frame
22
+ end.compact.reverse
23
+ config.formatter.call(result)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ # nodoc
5
+ class StackFrame
6
+ attr_reader :hash
7
+
8
+ def initialize(hash)
9
+ @hash = hash
10
+ end
11
+
12
+ %i[source_location source local_vars receiver_variables].each do |method_name|
13
+ define_method method_name do
14
+ fetch method_name
15
+ end
16
+ end
17
+
18
+ def path
19
+ source_location[:path]
20
+ end
21
+
22
+ # @param [Binding] binding_frame
23
+ def self.from_binding(binding_frame)
24
+ source_location = {
25
+ path: binding_frame.source_location[0],
26
+ line: binding_frame.source_location[1]
27
+ }
28
+ local_vars = binding_frame.local_variables.each_with_object({}) do |var_name, memo|
29
+ value = binding_frame.local_variable_get(var_name)
30
+ type = value.is_a?(Class) ? value : value.class
31
+ memo[var_name] = {
32
+ name: var_name,
33
+ value:,
34
+ type: type.to_s
35
+ }
36
+ end
37
+ receiver_variables = binding_frame.receiver.instance_variables.each_with_object({}) do |var_name, memo|
38
+ value = binding_frame.receiver.instance_variable_get(var_name)
39
+ type = value.is_a?(Class) ? value : value.class
40
+ memo[var_name] = {
41
+ name: var_name,
42
+ value:,
43
+ type: type.to_s
44
+ }
45
+ end
46
+ hash = { source_location:, local_vars:, source: {}, receiver_variables: }
47
+ new(hash)
48
+ end
49
+
50
+ def to_s
51
+ "#{source_location} - #{local_vars} - #{source}"
52
+ end
53
+
54
+ private
55
+
56
+ def fetch(key)
57
+ @hash[key]
58
+ end
59
+ end
60
+ end
data/lib/rshade/trace.rb CHANGED
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  class Trace
3
5
  attr_reader :config, :event_store
4
-
6
+
5
7
  # @param [RShade::Config,RShade::Config::Store] config
6
8
  def initialize(config)
7
9
  @config = fetch_config(config)
8
10
  @event_store = EventTree.new
9
11
  end
10
12
 
11
- def self.reveal(config=nil, &block)
13
+ def self.reveal(config = nil, &block)
12
14
  new(config).reveal(&block)
13
15
  end
14
16
 
@@ -16,7 +18,7 @@ module RShade
16
18
  processor = EventProcessor.new(event_store, config)
17
19
  observer = EventObserver.new(config, processor)
18
20
  observable = RShade::TraceObservable.new([observer], config)
19
- observable.reveal &block
21
+ observable.reveal(&block)
20
22
  self
21
23
  end
22
24
 
@@ -25,8 +27,9 @@ module RShade
25
27
  end
26
28
 
27
29
  private
30
+
28
31
  def fetch_config(config)
29
- config = config || ::RShade::Config.default
32
+ config ||= ::RShade::Config.default
30
33
  config = config.value if config.is_a?(::RShade::Config)
31
34
  config
32
35
  end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  class TraceObservable
3
5
  include Observable
4
6
  attr_reader :trace_p
7
+
5
8
  CALL_EVENTS = Set[:call, :c_call, :b_call]
6
9
  RETURN_EVENTS = Set[:return, :c_return, :b_return]
7
10
 
@@ -27,6 +30,7 @@ module RShade
27
30
  end
28
31
 
29
32
  private
33
+
30
34
  # more info https://rubyapi.org/3.1/o/tracepoint
31
35
  # @param [TracePoint] tp
32
36
  def process(tp)
@@ -34,6 +38,7 @@ module RShade
34
38
  event = Event.from_trace_point(tp)
35
39
  return notify_observers(event, :enter) if CALL_EVENTS.include?(tp.event)
36
40
  return notify_observers(event, :leave) if RETURN_EVENTS.include?(tp.event)
41
+
37
42
  notify_observers(event, :other)
38
43
  end
39
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
- VERSION = "0.1.9.1"
4
+ VERSION = '0.2.0'
3
5
  end