stack_trace 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/Gemfile.lock +41 -0
- data/README.md +180 -5
- data/lib/stack_trace.rb +32 -27
- data/lib/stack_trace/configuration.rb +62 -0
- data/lib/stack_trace/integration/rspec.rb +79 -0
- data/lib/stack_trace/module_extensions.rb +13 -0
- data/lib/stack_trace/setup.rb +62 -0
- data/lib/stack_trace/span.rb +56 -23
- data/lib/stack_trace/trace.rb +45 -17
- data/lib/stack_trace/version.rb +1 -1
- data/stack_trace.gemspec +4 -1
- metadata +25 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c29060d98a3ca570339220779da0a26b5d5d8be19994b3ce6a3b808caf51d8c
|
4
|
+
data.tar.gz: ecc897ccf6ea4257cc950a2f349450f14e3026a6f32fb2bb45342d3367af63d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93084a9322b703052f22124360a76bec238ab218dcd38004167db5d5d71b6b2f191323bd1f233aff153ddfed2944ad204be1471f5ac477efe51e869203950d57
|
7
|
+
data.tar.gz: fc08282fc1f32aa5983ebe68213f22a87d5f9a1f34388b1b80ef7e04629237a71570141b005d93291506020e93aa775a56f4edf91573e38fb3b255924d961d69
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
stack_trace (0.2.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.2)
|
10
|
+
diff-lcs (1.3)
|
11
|
+
method_source (0.9.2)
|
12
|
+
pry (0.12.2)
|
13
|
+
coderay (~> 1.1.0)
|
14
|
+
method_source (~> 0.9.0)
|
15
|
+
rake (13.0.1)
|
16
|
+
rspec (3.8.0)
|
17
|
+
rspec-core (~> 3.8.0)
|
18
|
+
rspec-expectations (~> 3.8.0)
|
19
|
+
rspec-mocks (~> 3.8.0)
|
20
|
+
rspec-core (3.8.2)
|
21
|
+
rspec-support (~> 3.8.0)
|
22
|
+
rspec-expectations (3.8.4)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.8.0)
|
25
|
+
rspec-mocks (3.8.1)
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
+
rspec-support (~> 3.8.0)
|
28
|
+
rspec-support (3.8.2)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
bundler (~> 2.0)
|
35
|
+
pry
|
36
|
+
rake (~> 13.0)
|
37
|
+
rspec (~> 3.0)
|
38
|
+
stack_trace!
|
39
|
+
|
40
|
+
BUNDLED WITH
|
41
|
+
2.0.1
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# StackTrace
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
StackTrace traces method calls in your application which can give you an overview about how your application works, which objects depends on which ones and what are the bottlenecks.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -20,9 +18,186 @@ Or install it yourself as:
|
|
20
18
|
|
21
19
|
$ gem install stack_trace
|
22
20
|
|
21
|
+
## Terminology
|
22
|
+
|
23
|
+
Before we jump into details we should talk about the two important terms that you should grasp first to understand the tracing output, which are `trace` and `span`.
|
24
|
+
|
25
|
+
#### Trace
|
26
|
+
|
27
|
+
Trace is an object which encapsulates the whole process. Whenever you start tracing, there will be a `trace` object associated which has a unique identifier(uuid v4) and holds all the `spans`.
|
28
|
+
|
29
|
+
#### Span
|
30
|
+
|
31
|
+
Span holds the information about the actual **unit of work**. StackTrace will create a new span for each method call if it's been told to do so by the configuration. Spans hold all the information that you need about the method calls like the time taken, arguments etc. as well as the child spans if the method calls othre methods.
|
32
|
+
You will see detailed information about the spans in the `Getting tracing information` chapter.
|
33
|
+
|
23
34
|
## Usage
|
24
35
|
|
25
|
-
|
36
|
+
Using StackTrace gem is pretty straight forward. First you should configure it to set which modules/classes and which methods should be traced and then you can start tracing the execution of your code with `StackTrace::trace` method.
|
37
|
+
|
38
|
+
#### Configuration
|
39
|
+
|
40
|
+
With the belove configuration, StackTrace will trace all the methods of **Foo** `module/class` and only the `zoo` method of **Bar** `module/class`.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
StackTrace.configure do |config|
|
44
|
+
config.enabled = true
|
45
|
+
config.modules = {
|
46
|
+
Foo => {
|
47
|
+
instance_methods: :all,
|
48
|
+
class_methods: :all
|
49
|
+
},
|
50
|
+
Bar => { instance_methods: [:zoo] }
|
51
|
+
}
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
`instance_methods` and `class_methods` can be configured with the following values;
|
56
|
+
|
57
|
+
- `:all` to trace all methods
|
58
|
+
- `:skip_inherited` to trace only the methods defined in module/class
|
59
|
+
- `:path` to trace all the classes/modules defined in a specific path regex(Make sure that StackTrace gem is loaded into memory before any files to use this configuration)
|
60
|
+
- Array of symbols to specify method names one by one
|
61
|
+
- Regular expression to trace all methods with matching method names
|
62
|
+
|
63
|
+
Also the keys for `modules` hash can have the following values;
|
64
|
+
|
65
|
+
- `Class/Module` to trace methods of given value
|
66
|
+
- An array of `Class/Module` to trace methods of all given values
|
67
|
+
- Regular expression to trace methods of all matching modules or classes.
|
68
|
+
- { inherits: Class } to trace methods of all classes inherited from base class.
|
69
|
+
|
70
|
+
Here are the example usages;
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
StackTrace.configure do |config|
|
74
|
+
config.enabled = true
|
75
|
+
config.modules = {
|
76
|
+
Foo => { instance_methods: :skip_inherited },
|
77
|
+
[Too, Joo] => { class_methods: :all }
|
78
|
+
/Ba.*/ => { instance_methods: :all },
|
79
|
+
{ inherits: Zoo } => { instance_methods: [:foo, :bar, :zoo] }
|
80
|
+
}
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
#### Tracing
|
85
|
+
|
86
|
+
After configuring the StackTrace, you can call `StackTrace::trace` method to create a tracing information of the code execution as shown below;
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
StackTrace.trace do
|
90
|
+
foo = Foo.new
|
91
|
+
foo.do_something
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
#### Getting tracing information
|
96
|
+
|
97
|
+
Currently StackTrace gem provides tracing information as a Ruby `Hash` object. You can use `StackTrace::Trace::as_json` method to receive the `Hash` for the current trace like so;
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
StackTrace.trace do
|
101
|
+
# Do something usefull
|
102
|
+
StackTrace::Trace.as_json
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
#### What does StackTrace collect?
|
107
|
+
|
108
|
+
The `Hash` object returned by `StackTrace::Trace::as_json` method has the following structure;
|
109
|
+
|
110
|
+
* **uuid**: This is a UUID V4 value to identify the trace.
|
111
|
+
* **spans**: This is an array of spans which has the following structure;
|
112
|
+
* **receiver**: The identifier for the receiver object.
|
113
|
+
* **method_name**: The name of the method which this span is created for.
|
114
|
+
* **arguments**: Arguments received by the method.
|
115
|
+
* **value**: The return value of the method.
|
116
|
+
* **exception**: The exception information if an exception is raised in this method. This attribute has the following child attributes:
|
117
|
+
* **message**: The error message(`error.message`).
|
118
|
+
* **backtrace**: The backtrace information of the excption as an array of strings.
|
119
|
+
* **time**: How long the execution of this unit of work took.
|
120
|
+
* **spans**: Child spans of the span.
|
121
|
+
|
122
|
+
Imagine you have the following configuration and class;
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class Greeting
|
126
|
+
def hello(first_name, last_name)
|
127
|
+
"Hello, #{capitalize(first_name)} #{capitalize(last_name)}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def capitalize(string)
|
131
|
+
string.capitalize
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
StackTrace.configure do |config|
|
136
|
+
config.enabled = true
|
137
|
+
config.modules = {
|
138
|
+
Greeting => { instance_methods: :all }
|
139
|
+
}
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
The the execution of the following code leads to below return object from `StackTrace::Trace.as_json` method;
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
StackTrace.trace do
|
147
|
+
Greeting.new.hello("john", "doe")
|
148
|
+
result = StackTrace::Trace.as_json
|
149
|
+
end
|
150
|
+
|
151
|
+
result == {
|
152
|
+
uuid: "12e2a347-8d5a-4d1d-a5ad-efe012ffcdf9",
|
153
|
+
spans: [
|
154
|
+
{
|
155
|
+
receiver: "Greeting#123124312",
|
156
|
+
method_name: "initialize",
|
157
|
+
arguments: {},
|
158
|
+
value: nil,
|
159
|
+
exception: nil,
|
160
|
+
time: "10.927719116210938 µs",
|
161
|
+
spans: []
|
162
|
+
},
|
163
|
+
{
|
164
|
+
receiver: "Greeting#123124312",
|
165
|
+
method_name: "hello",
|
166
|
+
arguments: {
|
167
|
+
first_name: "john",
|
168
|
+
last_name: "doe"
|
169
|
+
},
|
170
|
+
value: "Hello, John Doe",
|
171
|
+
exception: nil,
|
172
|
+
time: "20.831909134113330 µs",
|
173
|
+
spans: [
|
174
|
+
{
|
175
|
+
receiver: "Greeting#123124312",
|
176
|
+
method_name: "capitalize",
|
177
|
+
arguments: {
|
178
|
+
string: "john"
|
179
|
+
},
|
180
|
+
value: "John",
|
181
|
+
exception: nil,
|
182
|
+
time: "6.198883056640625 µs",
|
183
|
+
spans: []
|
184
|
+
},
|
185
|
+
{
|
186
|
+
receiver: "Greeting#123124312",
|
187
|
+
method_name: "capitalize",
|
188
|
+
arguments: {
|
189
|
+
string: "doe"
|
190
|
+
},
|
191
|
+
value: "Doe",
|
192
|
+
exception: nil,
|
193
|
+
time: "4.291534423828125 µs",
|
194
|
+
spans: []
|
195
|
+
}
|
196
|
+
]
|
197
|
+
}
|
198
|
+
]
|
199
|
+
}
|
200
|
+
```
|
26
201
|
|
27
202
|
## Development
|
28
203
|
|
@@ -32,7 +207,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
207
|
|
33
208
|
## Contributing
|
34
209
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
210
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/meinac/stack_trace.
|
36
211
|
|
37
212
|
## License
|
38
213
|
|
data/lib/stack_trace.rb
CHANGED
@@ -1,43 +1,48 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
require "stack_trace/
|
3
|
+
require "stack_trace/configuration"
|
4
|
+
require "stack_trace/module_extensions"
|
5
|
+
require "stack_trace/setup"
|
4
6
|
require "stack_trace/span"
|
5
7
|
require "stack_trace/trace"
|
8
|
+
require "stack_trace/version"
|
6
9
|
|
7
10
|
module StackTrace
|
8
|
-
|
9
|
-
self.traced_methods = method_names
|
10
|
-
end
|
11
|
-
|
12
|
-
def method_added(method_name)
|
13
|
-
return unless should_override?(method_name)
|
11
|
+
TRACED_EVENTS = %i(call c_call return c_return raise).freeze
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
class << self
|
14
|
+
def configure
|
15
|
+
yield configuration
|
16
|
+
trace_point.enable
|
17
|
+
end
|
18
18
|
|
19
|
-
|
19
|
+
def configuration
|
20
|
+
@configuration ||= Configuration.new
|
21
|
+
end
|
20
22
|
|
21
|
-
|
23
|
+
def trace
|
24
|
+
return unless block_given?
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def overridden_methods
|
28
|
-
@overridden_methods ||= []
|
29
|
-
end
|
26
|
+
Trace.start
|
27
|
+
yield
|
28
|
+
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
def current
|
31
|
+
Trace.current
|
32
|
+
end
|
34
33
|
|
35
|
-
|
36
|
-
Trace.
|
34
|
+
def as_json
|
35
|
+
Trace.as_json
|
37
36
|
end
|
38
|
-
end
|
39
37
|
|
40
|
-
|
41
|
-
|
38
|
+
def trace_point
|
39
|
+
@trace_point ||= TracePoint.new(*TRACED_EVENTS) { |tp| Trace.track(tp) }
|
40
|
+
end
|
42
41
|
end
|
43
42
|
end
|
43
|
+
|
44
|
+
TracePoint.new(:class) do |tp|
|
45
|
+
tp.binding.eval <<~RUBY
|
46
|
+
self.stack_trace_source_location = __FILE__
|
47
|
+
RUBY
|
48
|
+
end.enable
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require "objspace"
|
4
|
+
|
5
|
+
module StackTrace
|
6
|
+
class Configuration
|
7
|
+
CONFIG_ATTRIBUTES = {
|
8
|
+
enabled: false,
|
9
|
+
modules: {},
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_writer *CONFIG_ATTRIBUTES.keys
|
13
|
+
|
14
|
+
CONFIG_ATTRIBUTES.each do |attr_name, default_value|
|
15
|
+
define_method(attr_name) do
|
16
|
+
instance_variable_get("@#{attr_name}") || default_value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def for(klass)
|
21
|
+
config_holder = config_holder_for(klass)
|
22
|
+
modules.find { |module_name_conf, _| config_for_class?(module_name_conf, config_holder) }
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Configuration for StackTrace is done by specifying the class/module itself
|
28
|
+
# so if the klass we receive here is a singleton_class, we should get the
|
29
|
+
# class/module of that singleton_class first.
|
30
|
+
def config_holder_for(klass)
|
31
|
+
klass.singleton_class? ? ObjectSpace.each_object(klass).first : klass
|
32
|
+
end
|
33
|
+
|
34
|
+
def config_for_class?(config, klass)
|
35
|
+
case config
|
36
|
+
when Regexp
|
37
|
+
klass.name =~ config
|
38
|
+
when Hash
|
39
|
+
match_hash_config(config, klass)
|
40
|
+
else
|
41
|
+
[config].flatten.include?(klass)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def match_hash_config(config, klass)
|
46
|
+
inherits_config?(klass, config) || path_config?(klass, config)
|
47
|
+
end
|
48
|
+
|
49
|
+
def inherits_config?(klass, inherits: nil, **)
|
50
|
+
inherits &&
|
51
|
+
klass.ancestors.include?(inherits) &&
|
52
|
+
klass != inherits
|
53
|
+
end
|
54
|
+
|
55
|
+
def path_config?(klass, path: nil, **)
|
56
|
+
path &&
|
57
|
+
klass.respond_to?(:stack_trace_source_location) &&
|
58
|
+
klass.stack_trace_source_location &&
|
59
|
+
klass.stack_trace_source_location.match(path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
RSpec.configuration.before(:suite) do
|
6
|
+
StackTrace::Integration::Rspec.create_tracing_directory
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec.configuration.after(:suite) do
|
10
|
+
StackTrace::Integration::Rspec.finish_tracing
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec.configuration.around(:each) do |example|
|
14
|
+
StackTrace.trace do
|
15
|
+
example.run
|
16
|
+
StackTrace::Integration::Rspec.save_trace_for(example)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module StackTrace
|
21
|
+
module Integration
|
22
|
+
class Rspec
|
23
|
+
EXAMPLE_META_KEYS = %i(file_path line_number scoped_id description full_description)
|
24
|
+
FINAL_MESSAGE = <<~TEXT
|
25
|
+
\e[1m
|
26
|
+
StackTrace:
|
27
|
+
|
28
|
+
Trace information is saved into \e[32m%{file_path}\e[0m
|
29
|
+
\e[22m
|
30
|
+
TEXT
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def create_tracing_directory
|
34
|
+
Dir.mkdir(tracing_dir_path) unless Dir.exist?(tracing_dir_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def finish_tracing
|
38
|
+
save_examples
|
39
|
+
print_message
|
40
|
+
end
|
41
|
+
|
42
|
+
def save_trace_for(example)
|
43
|
+
examples << example_data(example)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def examples
|
49
|
+
@examples ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
def save_examples
|
53
|
+
File.write(tracing_file_path, examples.to_json)
|
54
|
+
end
|
55
|
+
|
56
|
+
def tracing_file_path
|
57
|
+
File.join(tracing_dir_path, trace_file_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def tracing_dir_path
|
61
|
+
File.expand_path("stack_trace")
|
62
|
+
end
|
63
|
+
|
64
|
+
def trace_file_name
|
65
|
+
@trace_file_name ||= Time.now.strftime('%d_%m_%Y %H_%M_%S.json')
|
66
|
+
end
|
67
|
+
|
68
|
+
def example_data(example)
|
69
|
+
example.metadata.slice(*EXAMPLE_META_KEYS)
|
70
|
+
.merge!(trace: Trace.current.as_json)
|
71
|
+
end
|
72
|
+
|
73
|
+
def print_message
|
74
|
+
puts format(FINAL_MESSAGE, file_path: tracing_file_path)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module StackTrace
|
4
|
+
module ModuleExtensions
|
5
|
+
attr_accessor :stack_trace_source_location
|
6
|
+
|
7
|
+
def trace_method?(method_id)
|
8
|
+
Setup.trackable?(self, method_id)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Module.include(StackTrace::ModuleExtensions)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module StackTrace
|
4
|
+
class Setup
|
5
|
+
class << self
|
6
|
+
def trackable?(mod, method_id)
|
7
|
+
store[mod].trace?(method_id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def store
|
11
|
+
@store ||= Hash.new do |h, k|
|
12
|
+
h[k.singleton_class] = new(k, :class_methods)
|
13
|
+
h[k] = new(k, :instance_methods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(klass, context)
|
19
|
+
self.klass = klass
|
20
|
+
self.context = context
|
21
|
+
end
|
22
|
+
|
23
|
+
def trace?(method_id)
|
24
|
+
enabled? && traced_method?(method_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_accessor :klass, :context
|
30
|
+
|
31
|
+
def enabled?
|
32
|
+
defined?(@enabled) ? @enabled : (@enabled = !config.nil?)
|
33
|
+
end
|
34
|
+
|
35
|
+
def config
|
36
|
+
@config ||= StackTrace.configuration.for(klass)
|
37
|
+
end
|
38
|
+
|
39
|
+
def traced_method?(method_id)
|
40
|
+
method_lookup[method_id]
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_lookup
|
44
|
+
@method_lookup ||= Hash.new { |lookup, method_id| lookup[method_id] = method_enabled?(method_id) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_enabled?(method_id)
|
48
|
+
case method_config
|
49
|
+
when Array
|
50
|
+
method_config.include?(method_id)
|
51
|
+
when Symbol
|
52
|
+
method_config != :skip_inherited || instance_methods(false).include?(method_id)
|
53
|
+
when Regexp
|
54
|
+
method_id =~ method_config
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_config
|
59
|
+
@method_config ||= config[1].fetch(context, [])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/stack_trace/span.rb
CHANGED
@@ -2,29 +2,69 @@
|
|
2
2
|
|
3
3
|
module StackTrace
|
4
4
|
class Span
|
5
|
-
|
6
|
-
|
5
|
+
class << self
|
6
|
+
def start_from(trace_point, parent)
|
7
|
+
new(
|
8
|
+
receiver(trace_point),
|
9
|
+
trace_point.method_id,
|
10
|
+
extract_arguments(trace_point),
|
11
|
+
parent
|
12
|
+
)
|
13
|
+
end
|
7
14
|
|
8
|
-
|
9
|
-
|
15
|
+
private
|
16
|
+
|
17
|
+
def receiver(trace_point)
|
18
|
+
trace_point.binding.eval("self").to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_arguments(trace_point)
|
22
|
+
trace_point.parameters
|
23
|
+
.map(&:last)
|
24
|
+
.each_with_object({}) do |parameter, memo|
|
25
|
+
memo[parameter] = trace_point.binding.eval(parameter.to_s).inspect
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_writer :exception
|
31
|
+
|
32
|
+
def initialize(receiver, method_name, args, parent)
|
33
|
+
self.receiver = receiver
|
10
34
|
self.method_name = method_name
|
11
35
|
self.args = args
|
36
|
+
self.parent = parent
|
37
|
+
self.started_at = Time.now.to_f
|
12
38
|
self.spans = []
|
13
39
|
end
|
14
40
|
|
15
|
-
def
|
16
|
-
(spans << span
|
41
|
+
def <<(span)
|
42
|
+
(spans << span) && span
|
17
43
|
end
|
18
44
|
|
19
|
-
def close
|
20
|
-
|
21
|
-
|
45
|
+
def close(trace_point)
|
46
|
+
self.value = trace_point.return_value.inspect
|
47
|
+
self.finished_at = Time.now.to_f
|
48
|
+
parent
|
22
49
|
end
|
23
50
|
|
24
|
-
def
|
25
|
-
|
51
|
+
def as_json
|
52
|
+
{
|
53
|
+
receiver: receiver,
|
54
|
+
method_name: method_name,
|
55
|
+
arguments: args,
|
56
|
+
value: value,
|
57
|
+
exception: exception_as_json,
|
58
|
+
time: time,
|
59
|
+
spans: spans.map(&:as_json)
|
60
|
+
}
|
26
61
|
end
|
27
62
|
|
63
|
+
private
|
64
|
+
|
65
|
+
attr_accessor :receiver, :method_name, :args, :value, :parent, :spans, :started_at, :finished_at
|
66
|
+
attr_reader :exception
|
67
|
+
|
28
68
|
def time
|
29
69
|
case time_ns
|
30
70
|
when 0..1_000
|
@@ -38,22 +78,15 @@ module StackTrace
|
|
38
78
|
end
|
39
79
|
end
|
40
80
|
|
41
|
-
def
|
81
|
+
def exception_as_json
|
82
|
+
return unless exception
|
83
|
+
|
42
84
|
{
|
43
|
-
|
44
|
-
|
45
|
-
value: value,
|
46
|
-
exception: exception,
|
47
|
-
time: time,
|
48
|
-
spans: spans.map(&:as_json)
|
85
|
+
message: exception.message,
|
86
|
+
backtrace: exception.backtrace
|
49
87
|
}
|
50
88
|
end
|
51
89
|
|
52
|
-
private
|
53
|
-
|
54
|
-
attr_accessor :started_at, :finished_at
|
55
|
-
attr_writer :method_name, :args, :spans
|
56
|
-
|
57
90
|
def time_ns
|
58
91
|
(finished_at - started_at) * 1_000_000_000
|
59
92
|
end
|
data/lib/stack_trace/trace.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
+
require "securerandom"
|
4
|
+
|
3
5
|
module StackTrace
|
4
6
|
class Trace
|
7
|
+
TRACE_START_EVENTS = %i(call c_call).freeze
|
8
|
+
TRACE_END_EVENTS = %i(return c_return).freeze
|
9
|
+
TRACE_RAISE_EVENT = :raise
|
10
|
+
|
5
11
|
class << self
|
6
|
-
def track(
|
7
|
-
|
8
|
-
span.value = yield
|
9
|
-
rescue StandardError => e
|
10
|
-
span.exception = e
|
11
|
-
raise e
|
12
|
-
ensure
|
13
|
-
span.close
|
12
|
+
def track(trace_point)
|
13
|
+
current.add(trace_point) if trackable?(trace_point)
|
14
14
|
end
|
15
15
|
|
16
16
|
def start
|
@@ -24,32 +24,60 @@ module StackTrace
|
|
24
24
|
def as_json
|
25
25
|
current.as_json
|
26
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def trackable?(trace_point)
|
31
|
+
trace_point.defined_class&.trace_method?(trace_point.method_id)
|
32
|
+
end
|
27
33
|
end
|
28
34
|
|
29
|
-
attr_reader :spans
|
35
|
+
attr_reader :uuid, :spans
|
30
36
|
|
31
37
|
def initialize
|
38
|
+
@uuid = SecureRandom.uuid
|
32
39
|
@spans = []
|
33
40
|
end
|
34
41
|
|
35
|
-
def add(
|
36
|
-
|
42
|
+
def add(trace_point)
|
43
|
+
case trace_point.event
|
44
|
+
when *TRACE_START_EVENTS
|
45
|
+
create_new_span(trace_point)
|
46
|
+
when *TRACE_END_EVENTS
|
47
|
+
close_current_span(trace_point)
|
48
|
+
else
|
49
|
+
apply_exception_to_current_span(trace_point)
|
50
|
+
end
|
37
51
|
end
|
38
52
|
|
39
53
|
def as_json
|
40
|
-
{
|
54
|
+
{
|
55
|
+
uuid: uuid,
|
56
|
+
spans: spans.map(&:as_json)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def <<(span)
|
61
|
+
spans << span
|
41
62
|
end
|
42
63
|
|
43
64
|
private
|
44
65
|
|
45
|
-
def
|
46
|
-
|
66
|
+
def create_new_span(trace_point)
|
67
|
+
span = Span.start_from(trace_point, container)
|
68
|
+
container << (@active_span = span)
|
69
|
+
end
|
70
|
+
|
71
|
+
def close_current_span(trace_point)
|
72
|
+
@active_span = @active_span&.close(trace_point)
|
73
|
+
end
|
47
74
|
|
48
|
-
|
75
|
+
def apply_exception_to_current_span(trace_point)
|
76
|
+
@active_span.exception = trace_point.raised_exception
|
49
77
|
end
|
50
78
|
|
51
|
-
def
|
52
|
-
|
79
|
+
def container
|
80
|
+
@active_span || self
|
53
81
|
end
|
54
82
|
end
|
55
83
|
end
|
data/lib/stack_trace/version.rb
CHANGED
data/stack_trace.gemspec
CHANGED
@@ -23,7 +23,10 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ["lib"]
|
25
25
|
|
26
|
+
spec.required_ruby_version = '>= 1.9.2'
|
27
|
+
|
26
28
|
spec.add_development_dependency "bundler", "~> 2.0"
|
27
|
-
spec.add_development_dependency "rake", "~>
|
29
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
28
30
|
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
+
spec.add_development_dependency "pry"
|
29
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stack_trace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mehmet Emin INAC
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: This library tracks the execution of methods, their arguments, return
|
56
70
|
values etc.
|
57
71
|
email:
|
@@ -64,12 +78,17 @@ files:
|
|
64
78
|
- ".rspec"
|
65
79
|
- ".travis.yml"
|
66
80
|
- Gemfile
|
81
|
+
- Gemfile.lock
|
67
82
|
- LICENSE.txt
|
68
83
|
- README.md
|
69
84
|
- Rakefile
|
70
85
|
- bin/console
|
71
86
|
- bin/setup
|
72
87
|
- lib/stack_trace.rb
|
88
|
+
- lib/stack_trace/configuration.rb
|
89
|
+
- lib/stack_trace/integration/rspec.rb
|
90
|
+
- lib/stack_trace/module_extensions.rb
|
91
|
+
- lib/stack_trace/setup.rb
|
73
92
|
- lib/stack_trace/span.rb
|
74
93
|
- lib/stack_trace/trace.rb
|
75
94
|
- lib/stack_trace/version.rb
|
@@ -86,14 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
105
|
requirements:
|
87
106
|
- - ">="
|
88
107
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
108
|
+
version: 1.9.2
|
90
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
110
|
requirements:
|
92
111
|
- - ">="
|
93
112
|
- !ruby/object:Gem::Version
|
94
113
|
version: '0'
|
95
114
|
requirements: []
|
96
|
-
rubygems_version: 3.0.
|
115
|
+
rubygems_version: 3.0.8
|
97
116
|
signing_key:
|
98
117
|
specification_version: 4
|
99
118
|
summary: Tracks the execution of methods configured
|