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 +4 -4
- data/README.md +45 -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: 6db85d92c978e016973febaede7a1365f0c6f1c56da05d5eeda4e4e72f7d4228
|
4
|
+
data.tar.gz: 25180162f25501cb45a9e72c549db166c9b3b493655a4cf1fee3fc459e13c684
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf1293d617f0e8c94feaca3dccf0cd826a8cb745993cadb5d9281d25baaf5a1308666e75828c6e1ec25ef7d3c5c38ce7e8aeb17e8c0b2da1dafa973f306e984a
|
7
|
+
data.tar.gz: 1ba8344769fe6b565bfdd405b3f68ad5aec0c08584d13f12e06725c68020b68f3bc531c92dcb848e7767ae88868fbb4cb635eee7169042f0c195c47cbc707e27
|
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,50 @@ 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
|
+
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
|
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
|
+
@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
|
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 - 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
|
-
|
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.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
|
-
|
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.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
|
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.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-
|
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
|