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 +4 -4
- data/README.md +56 -4
- data/lib/tracia/default_logger.rb +25 -0
- data/lib/tracia/frame.rb +32 -0
- data/lib/tracia/gem_paths.rb +19 -0
- data/lib/tracia/version.rb +1 -1
- data/lib/tracia.rb +129 -40
- metadata +33 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3cb9b65fa0d3c36e530bdf201281a35c6f58d38273e2287b12d80688b2cc0a8a
|
4
|
+
data.tar.gz: b61d048ac0eb2f09cc432e2ca4aaa0035e4a1ab2d7e6d18b848cdce1d97b67a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5432ae3e05b76ca7fe8c90ef02843669e627e6aae92e42c257285f37cdcaa03215badbf9b2ad86cac9dc0462cef6a8e60190434de79612028fade99edb6c894b
|
7
|
+
data.tar.gz: c41e5de84fc6aba18dcecbadd856c4715f2ffd8ffdcfbc1cc93ffcd35956003f0d4948e313eb93621101395f50e54b26c7ebf41c394bf632a47ba63d77922491
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Tracia
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
data/lib/tracia/frame.rb
ADDED
@@ -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
|
data/lib/tracia/version.rb
CHANGED
data/lib/tracia.rb
CHANGED
@@ -1,75 +1,164 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
22
|
-
|
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(
|
35
|
+
def add(info)
|
26
36
|
trc = Thread.current[:_tracia_]
|
27
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
51
|
+
@backtraces = []
|
52
|
+
@level = 0
|
53
|
+
|
54
|
+
enable_trace_point
|
55
|
+
end
|
40
56
|
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
48
|
-
@
|
49
|
-
@level = 0
|
73
|
+
def disable_trace_point
|
74
|
+
@trace_point.disable
|
50
75
|
end
|
51
76
|
|
52
|
-
def add(
|
53
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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.
|
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-
|
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
|