tracia 0.1.0 → 0.2.2

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: 3cb9b65fa0d3c36e530bdf201281a35c6f58d38273e2287b12d80688b2cc0a8a
4
+ data.tar.gz: b61d048ac0eb2f09cc432e2ca4aaa0035e4a1ab2d7e6d18b848cdce1d97b67a4
5
5
  SHA512:
6
- metadata.gz: fb07182692dca1890de5248ecd55f904f9ca81512eee9f1f7d3a392c0d6d52c6727794b5963f4d6abda458bbcfb7c4c46a62c03db073dfaa18dcf8329168bd91
7
- data.tar.gz: 0540c570c7184eecff45c83ae58951250c6c31cfcfd3b1e304ee83b70a66b9ae2d72d14180c6f1f642a4f751ab8dd6c9ea2fd6fc2ac4ca5eebe3d381422e8447
6
+ metadata.gz: 5432ae3e05b76ca7fe8c90ef02843669e627e6aae92e42c257285f37cdcaa03215badbf9b2ad86cac9dc0462cef6a8e60190434de79612028fade99edb6c894b
7
+ data.tar.gz: c41e5de84fc6aba18dcecbadd856c4715f2ffd8ffdcfbc1cc93ffcd35956003f0d4948e313eb93621101395f50e54b26c7ebf41c394bf632a47ba63d77922491
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,61 @@ 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
+ By default, Tracia writes result to STDOUT, you can set it somewhere else
46
+
47
+ ```ruby
48
+ file = File.new('/path/to/log.txt', 'w+')
49
+
50
+ Tracia.start(logger: Tracia::DefaultLogger.new(out: file)) do
51
+ # ...
52
+ end
53
+ ```
54
+
55
+ Or you can make a logger class which responds to `call`
56
+
57
+ ```ruby
58
+ class MyLogger
59
+ def initialize(database)
60
+ @database = database
61
+ end
62
+
63
+ # callback method for Tracia
64
+ def call(root)
65
+ # ...
66
+ @database.insert(root)
67
+ end
68
+ end
69
+ ```
70
+
71
+ Then pass that logger to Tracia
72
+
73
+ ```ruby
74
+ Tracia.start(logger: MyLogger.new(db_connection)) do
75
+ # ..
76
+ end
77
+ ```
26
78
 
27
79
  ## Development
28
80
 
@@ -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
+ @klass = klass
11
+ @call_sym = call_sym
12
+ @method_name = method_name
13
+ @file = file
14
+ @lineno = lineno
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.2"
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 + 1
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.partial_callers(-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.partial_callers(-#{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.2
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-15 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.3
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.3
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