tracia 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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