tracia 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 363d76e2a793f137185051d30570d69c98051ddd1f1f10769eba90a2bcb28167
4
- data.tar.gz: 1268959b896e1b410ac7e9d0d0d46fd1c4e60d27ccf45655581e05288a9ba62c
3
+ metadata.gz: 6db85d92c978e016973febaede7a1365f0c6f1c56da05d5eeda4e4e72f7d4228
4
+ data.tar.gz: 25180162f25501cb45a9e72c549db166c9b3b493655a4cf1fee3fc459e13c684
5
5
  SHA512:
6
- metadata.gz: fb07182692dca1890de5248ecd55f904f9ca81512eee9f1f7d3a392c0d6d52c6727794b5963f4d6abda458bbcfb7c4c46a62c03db073dfaa18dcf8329168bd91
7
- data.tar.gz: 0540c570c7184eecff45c83ae58951250c6c31cfcfd3b1e304ee83b70a66b9ae2d72d14180c6f1f642a4f751ab8dd6c9ea2fd6fc2ac4ca5eebe3d381422e8447
6
+ metadata.gz: bf1293d617f0e8c94feaca3dccf0cd826a8cb745993cadb5d9281d25baaf5a1308666e75828c6e1ec25ef7d3c5c38ce7e8aeb17e8c0b2da1dafa973f306e984a
7
+ data.tar.gz: 1ba8344769fe6b565bfdd405b3f68ad5aec0c08584d13f12e06725c68020b68f3bc531c92dcb848e7767ae88868fbb4cb635eee7169042f0c195c47cbc707e27
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Tracia
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/tracia`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Construct call stack in tree-style
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,7 +20,50 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ Put `Tracia.add` in method call
24
+
25
+ ```ruby
26
+ def do_something
27
+ do_somthing_deep
28
+ end
29
+
30
+ def do_something_deep
31
+ Tracia.add("I am working on something deep")
32
+ end
33
+ ```
34
+
35
+ Wrap the call with `Tracia.start`
36
+
37
+ ```ruby
38
+ Tracia.start do
39
+ do_something()
40
+ end
41
+ ```
42
+
43
+ ### Custom Logger
44
+
45
+ A Template to make custom logger
46
+
47
+ ```ruby
48
+ class MyLogger
49
+ def initialize(database)
50
+ @database = database
51
+ end
52
+
53
+ def call(root)
54
+ # ...
55
+ @database.insert(root)
56
+ end
57
+ end
58
+ ```
59
+
60
+ Pass the custom logger to Tracia
61
+
62
+ ```ruby
63
+ Tracia.start(logger: MyLogger.new(db_connection)) do
64
+ # ..
65
+ end
66
+ ```
26
67
 
27
68
  ## Development
28
69
 
@@ -0,0 +1,25 @@
1
+ class Tracia
2
+ class DefaultLogger
3
+ NO_CHILD = []
4
+
5
+ class << self
6
+ def tree_graph_everything!
7
+ Object.define_method(:label_for_tree_graph) do
8
+ to_s
9
+ end
10
+
11
+ Object.define_method(:children_for_tree_graph) do
12
+ NO_CHILD
13
+ end
14
+ end
15
+ end
16
+
17
+ def initialize(out: STDOUT)
18
+ @out = out
19
+ end
20
+
21
+ def call(root)
22
+ @out.puts root.tree_graph
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ require "tree_graph"
2
+
3
+ class Tracia
4
+ class Frame
5
+ include TreeGraph
6
+
7
+ attr_reader :klass, :call_sym, :method_name, :children, :file
8
+
9
+ def initialize(klass, call_sym, method_name, file, lineno)
10
+ @file = file
11
+ @lineno = lineno
12
+ @klass = klass
13
+ @call_sym = call_sym
14
+ @method_name = method_name
15
+ @children = []
16
+ end
17
+
18
+ def same_klass_and_method?(other_frame)
19
+ klass == other_frame.klass &&
20
+ call_sym == other_frame.call_sym &&
21
+ method_name == other_frame.method_name
22
+ end
23
+
24
+ def label_for_tree_graph
25
+ "#{klass}#{call_sym}#{method_name} #{GemPaths.shorten(@file)}:#{@lineno}"
26
+ end
27
+
28
+ def children_for_tree_graph
29
+ children
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ require "yaml"
2
+
3
+ class Tracia
4
+ module GemPaths
5
+ ABSTRACTS = {}
6
+
7
+ ::YAML.load(`gem env`.gsub(/=>/, ':'))['RubyGems Environment']
8
+ .detect{ |hash| hash.has_key?('GEM PATHS') }['GEM PATHS']
9
+ .each_with_index { |path, i| ABSTRACTS["GemPath#{i}"] = path }
10
+
11
+ class << self
12
+ def shorten(location)
13
+ return '' if location.nil?
14
+ ABSTRACTS.each{ |name, path| location = location.gsub(path, "$#{name}") }
15
+ location
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Tracia
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/tracia.rb CHANGED
@@ -1,75 +1,164 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "tracia/version"
3
+ require "tracia/version"
4
+ require "tracia/gem_paths"
5
+ require "tracia/frame"
6
+ require "tracia/default_logger"
7
+
8
+ require "binding_of_callers"
4
9
 
5
10
  class Tracia
6
11
  class Error < StandardError; end
7
12
 
8
- attr_accessor :level, :error
13
+ INSTANCE_METHOD_SHARP = '#'
14
+
15
+ attr_accessor :level, :error, :depth
9
16
 
10
- # Your code goes here...
11
17
  class << self
12
- def start
13
- trc = (Thread.current[:_tracia_] ||= new)
18
+ def start(**opt)
19
+ trc = (Thread.current[:_tracia_] ||= new(**opt))
14
20
  trc.level += 1
21
+ trc.depth = binding.frame_count - 4
15
22
  yield
16
23
  rescue StandardError => e
17
24
  trc.error = e
18
25
  raise e
19
26
  ensure
20
27
  trc.level -= 1
21
- Thread.current[:_tracia_] = nil if trc.error || trc.level == 0
22
- trc.log if trc.level == 0 && trc.error.nil?
28
+ if trc.error || trc.level == 0
29
+ Thread.current[:_tracia_] = nil
30
+ trc.disable_trace_point
31
+ end
32
+ trc.log if trc.level == 0
23
33
  end
24
34
 
25
- def add(attrs)
35
+ def add(info)
26
36
  trc = Thread.current[:_tracia_]
27
- trc.add(caller, attrs) if trc
37
+ return unless trc
38
+
39
+ backtrace = binding.of_callers(binding.frame_count - trc.depth)
40
+ backtrace.reverse!
41
+ backtrace.pop
42
+ trc.add(backtrace, info)
28
43
  end
29
44
  end
30
45
 
31
- class Frame
32
- attr_reader :name, :children
46
+ def initialize(**opt)
47
+ @frames_to_reject = Array(opt[:reject])
48
+ @non_tail_recursion = opt[:non_tail_recursion]
49
+ @logger = opt[:logger] || DefaultLogger.new
33
50
 
34
- def initialize(name, level)
35
- @name = name
36
- @level = level
37
- @children = []
38
- @data = []
39
- end
51
+ @backtraces = []
52
+ @level = 0
53
+
54
+ enable_trace_point
55
+ end
40
56
 
41
- def inspect
42
- spaces = ' ' * @level
43
- @children.empty? ? "#{@name}\n" : "#{@name} ->\n#{spaces}#{@children}"
57
+ def enable_trace_point
58
+ current_thread = Thread.current
59
+ @trace_point = TracePoint.new(:raise) do |point|
60
+ bd = point.binding
61
+ next unless current_thread == bd.eval('Thread.current')
62
+ backtrace = bd.eval("binding.of_callers(binding.frame_count - #{depth})")
63
+ raiser = backtrace[0]
64
+ next if raiser.klass == Tracia && raiser.frame_env == 'rescue in start'
65
+ backtrace.reverse!
66
+ backtrace.pop
67
+ backtrace.pop
68
+ add(backtrace, point.raised_exception)
44
69
  end
70
+ @trace_point.enable
45
71
  end
46
72
 
47
- def initialize
48
- @stacks = []
49
- @level = 0
73
+ def disable_trace_point
74
+ @trace_point.disable
50
75
  end
51
76
 
52
- def add(stack, data)
53
- @stacks << [stack, data]
77
+ def add(backtrace, info)
78
+ backtrace = convert_to_frames(backtrace)
79
+ @backtraces << [backtrace, info]
54
80
  end
55
81
 
56
82
  def log
57
- frames = []
58
- @stacks.each do |stack, data|
59
- stack.reverse.each_with_index do |raw_frame, idx|
60
- frame = frames[idx]
61
- if frame == nil
62
- frame = Frame.new(raw_frame, idx)
63
- frames[idx - 1].children << frame if idx > 0
64
- frames[idx] = frame
65
- elsif frame.name != raw_frame
66
- frame = Frame.new(raw_frame, idx)
67
- frames[idx - 1].children << frame if idx > 0
68
- frames[idx] = frame
69
- frames = frames.slice(0, idx + 1)
70
- end
83
+ @stack = []
84
+
85
+ @backtraces.each do |backtrace, info|
86
+ build_road_from_root_to_leaf(backtrace)
87
+ @stack.last.children << info
88
+ end
89
+
90
+ root = @stack[0]
91
+ if root
92
+ non_tail_recursion!([root]) if @non_tail_recursion
93
+ @logger.call(root)
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def non_tail_recursion!(stack)
100
+ current_frame = stack.last
101
+ last_idx = current_frame.children.count - 1
102
+
103
+ current_frame.children.each_with_index do |child, idx|
104
+ next unless Frame === child
105
+ next non_tail_recursion!([child]) if last_idx != idx
106
+
107
+ recursion_idx = stack.index{ |frame| frame.same_klass_and_method?(child) }
108
+ if recursion_idx
109
+ parent = stack[recursion_idx - 1]
110
+ parent.children << child
111
+ current_frame.children.pop
112
+ non_tail_recursion!([parent, child])
113
+ else
114
+ stack.push(child)
115
+ non_tail_recursion!(stack)
116
+ end
117
+ end
118
+ end
119
+
120
+ def build_road_from_root_to_leaf(backtrace)
121
+ backtrace.reject!{ |raw_frame| reject?(raw_frame) }
122
+ backtrace.each_with_index do |raw_frame, idx|
123
+ frame = @stack[idx]
124
+ if frame == nil
125
+ push_frame(raw_frame, idx)
126
+ elsif !frame.same_klass_and_method?(raw_frame)
127
+ @stack = @stack.slice(0, idx + 1)
128
+ push_frame(raw_frame, idx)
71
129
  end
72
130
  end
73
- p frames
131
+
132
+ @stack = @stack.slice(0, backtrace.size) if @stack.size > backtrace.size
133
+ end
134
+
135
+ def push_frame(frame, idx)
136
+ @stack[idx - 1].children << frame if idx > 0
137
+ @stack[idx] = frame
138
+ end
139
+
140
+ def reject?(raw_frame)
141
+ @frames_to_reject.any?{ |rj| rj =~ raw_frame.file }
142
+ end
143
+
144
+ def convert_to_frames(callers)
145
+ callers.map! do |c|
146
+ _binding = c._binding
147
+ klass = c.klass
148
+ call_symbol = c.call_symbol
149
+ frame_env = c.frame_env
150
+
151
+ source_location =
152
+ if _binding.frame_type == :method
153
+ meth = call_symbol == INSTANCE_METHOD_SHARP ? klass.instance_method(frame_env) : klass.method(frame_env)
154
+ meth.source_location
155
+ else
156
+ _binding.source_location
157
+ end
158
+
159
+ Frame.new(klass, call_symbol, frame_env, source_location[0], source_location[1])
160
+ end
161
+
162
+ callers
74
163
  end
75
164
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tracia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ken
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-06 00:00:00.000000000 Z
11
+ date: 2022-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tree_graph
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: binding_of_callers
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.1
27
55
  description:
28
56
  email:
29
57
  - block24block@gmail.com
@@ -37,6 +65,9 @@ files:
37
65
  - README.md
38
66
  - Rakefile
39
67
  - lib/tracia.rb
68
+ - lib/tracia/default_logger.rb
69
+ - lib/tracia/frame.rb
70
+ - lib/tracia/gem_paths.rb
40
71
  - lib/tracia/version.rb
41
72
  - sig/tracia.rbs
42
73
  homepage: https://github.com/turnon/tracia