tracee 0.1.0 → 1.0.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 +263 -13
- data/lib/tracee/benchmarkable.rb +13 -1
- data/lib/tracee/logger.rb +4 -2
- data/lib/tracee/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5cf4bf19f3573beff7a7b2bbd48dea36af50e3a
|
4
|
+
data.tar.gz: b653e49210ff047f41231be4ece436229d5935c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a22da0413561e07d85bd0bbc4f0802e4cad759215a1a63004edf7691a71797c186e397175322aead72062c704d72dec9179232f9034a907106ce885c180f079
|
7
|
+
data.tar.gz: daf6962a7a3a490a9b15b5c189d749d5c736691a4dbb8f561d98f67f9b1cc758eb44e9414f943066f748638966a5b7b958abca980337c31dc3a3c954eed97df9
|
data/README.md
CHANGED
@@ -1,8 +1,33 @@
|
|
1
|
-
|
1
|
+
## Tracee
|
2
2
|
|
3
|
-
|
3
|
+
Tracee is a development toolkit.
|
4
|
+
Primarily, it includes a simple extensible logger with stack tracing, benchmarking, preprocessing, and severity-based output splitting. The logger is designed for development and debugging of any type of an application or a library, and is compatible with Rails. The main reason of its existence is to help a developer to see through a stack. However, it can live in a production environment as well.
|
5
|
+
Secondarily, it decorates exception's backtracing by pointing out actual lines of code that has led to an error, and by grouping callers from indentical lines.
|
6
|
+
|
7
|
+
### Logger features
|
8
|
+
|
9
|
+
* **Caller processing**. A logger instance when configured appropriately (see below), will process a caller trace of each call to it.
|
10
|
+
|
11
|
+
That means you can easily figure out from which exact line did each message come from, and concentrate on debugging rather than marking up log messages.
|
12
|
+
You can also specify a slice of a trace which would be logged along with a specific call to the logger instance.
|
13
|
+
|
14
|
+
|
15
|
+
* **Preprocessors pipeline**. A logger is easily extensible by any amount of #call'able objects which affect a message logging.
|
16
|
+
|
17
|
+
You can configure a pipeline that will in turn format or just silence a message according to its semantics or source, copy something interesting into a DB, rotate a log file, and do anything else.
|
18
|
+
|
19
|
+
|
20
|
+
* **Splitted streaming**. Tracee can stream to any number of IOs simultaneously and conditionally.
|
21
|
+
|
22
|
+
If you're a fan of `tail -f`, with Tracee you can run `tail -f log/development.info.log` to read only the messages you're interested in the most time. And in the moment you think the last logs from the "debug" channel would be helpful (for example, you detect a floating bug), you run `tail -f log/development.debug.log`
|
23
|
+
and read all the last messages from the "debug" channel along with ones from "info", "warn" etc.
|
24
|
+
As well, you can read the "warn" channel which collects only the messages of the "warn" and higher levels.
|
25
|
+
|
26
|
+
|
27
|
+
* **Benchmarking**. Tracee logger provides simple benchmarking capabilities, measuring a block and a call-to-call time difference.
|
28
|
+
|
29
|
+
Using the logger you're able to measure weak spots in a block, getting a call-to-call report illustrated with the power of caller tracing.
|
4
30
|
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
6
31
|
|
7
32
|
## Installation
|
8
33
|
|
@@ -14,23 +39,248 @@ gem 'tracee'
|
|
14
39
|
|
15
40
|
And then execute:
|
16
41
|
|
17
|
-
|
42
|
+
`$ bundle`
|
18
43
|
|
19
|
-
Or install it
|
44
|
+
Or install it into your current gemset as:
|
20
45
|
|
21
|
-
|
46
|
+
`$ gem install tracee `
|
22
47
|
|
23
|
-
## Usage
|
24
48
|
|
25
|
-
|
49
|
+
## Logger usage
|
26
50
|
|
27
|
-
|
51
|
+
As you start, you get a `$log` variable unless it's already defined. It is there to present a basic usage of Tracee::Logger.
|
28
52
|
|
29
|
-
|
53
|
+
```ruby
|
54
|
+
$log.debug "Starting process..."
|
55
|
+
# 13:43:01.632 DEBUG [(irb):1 :irb_binding]: Starting process...
|
56
|
+
$log.info ["Got response:", {code: 200, body: "Hello"}]
|
57
|
+
# 13:43:20.524 INFO [(irb):2 :irb_binding]: ["Got response:", {:code=>200, :body=>"Hello"}]
|
58
|
+
$log.warn "Oops, something went wrong!"
|
59
|
+
# 13:43:32.030 WARN [(irb):3 :irb_binding]: Oops, something went wrong!
|
60
|
+
```
|
30
61
|
|
31
|
-
|
62
|
+
\#debug, #info and #warn aliased with #<=, #<< and #< respectively, so you can do << to your log.
|
32
63
|
|
33
|
-
|
64
|
+
Log level might be overriden globally by `LOGLEVEL` env variable, e.g. `LOGLEVEL=DEBUG` or `LOGLEVEL=FATAL`. Default level is INFO.
|
65
|
+
|
66
|
+
### Initialize
|
67
|
+
|
68
|
+
A common logger for Rails is initialized like this
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# config/environments/development.rb or a kind of a config file in your non-rails app
|
72
|
+
config.logger = Tracee::Logger.new(
|
73
|
+
# (optional) A list of #call'able objects which format messages in a chain
|
74
|
+
# based on the current datetime, a message level, and a caller trace slice, and can throw :halt to prevent logging of the message
|
75
|
+
preprocessors: [:quiet_assets], # or Tracee::Preprocessors::QuietAssets.new
|
76
|
+
# A #call'able object being the last in a preprocessors chain.
|
77
|
+
# It is distinguished to support compatibility with a default Logger
|
78
|
+
formatter: {:formatter => [:plain]}, # or Tracee::Preprocessors::Formatter.new(:plain), which would not do any formatting at all as a default Logger would
|
79
|
+
# A list of IOs or references the processed log messages will be written to
|
80
|
+
streams: [$stdout, # a reference to IO
|
81
|
+
{cascade: "#{config.root}/log/development.%{level}.log"}, # a set of paths inferred from a message severity (level)
|
82
|
+
"#{config.root}/log/development.log"], # a default path to which a default Rails logger would write messages
|
83
|
+
# Log level. You can set it by a number (0-5), a symbol, a string of any case, or to a Logger:: or Tracee::Logger:: constant. Rails resets it based on config.log_level value
|
84
|
+
level: :debug
|
85
|
+
)
|
86
|
+
```
|
87
|
+
|
88
|
+
However, the point of Tracee is to have another logger instance with another formatter's template which will highlight the messages been through it.
|
89
|
+
For example,
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# config/initializers/tracee.rb
|
93
|
+
$log = Tracee::Logger.new(
|
94
|
+
streams: [$stdout, {cascade: "#{Rails.root}/log/#{Rails.env}.%{level}.log"}],
|
95
|
+
formatter: {:formatter => [:tracee]} # :tracee template will colorize messages and print a reference to the line a logger was called from along with a datetime and a message severity
|
96
|
+
)
|
97
|
+
```
|
98
|
+
|
99
|
+
### Preprocessors
|
100
|
+
|
101
|
+
A preprocessor is a callable object which inherits from `Tracee::Preprocessors::Base` and implements a #call method with 5 arguments:
|
102
|
+
* severity \<String\>
|
103
|
+
* datetime \<DateTime\>
|
104
|
+
* progname \<String | nil\>
|
105
|
+
* message \<anything\>
|
106
|
+
* caller_slice \<Array\>
|
107
|
+
returning a formatted message or halting the preprocessors pipeline.
|
108
|
+
|
109
|
+
### Formatter templates
|
110
|
+
|
111
|
+
Formatter renders a template into an output message using the scope of logger call. There are 3 templates predefined:
|
112
|
+
|
113
|
+
* **logger_formatter** (which is equivalent of standard Logger::Formatter#call)
|
114
|
+
`{D, I, W, E, F, U}, [2000-10-20T11:22:33.123456 #%{pid}] {DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN} -- %{progname}: %{message}`
|
115
|
+
* **tracee** (a branded fancy template with ANSI coloring and tracing)
|
116
|
+
`11:22:33.123 {DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN} [%{file}:%{line} :%{method}]: %{message}`
|
117
|
+
* **plain** (lefts a message as is)
|
118
|
+
`%{message}`
|
34
119
|
|
35
|
-
|
120
|
+
To figure out what is that all about, see `Tracee::Preprocessors::Formatter::TEMPLATES` constant which is a hash with processable values. You can set as the first argument to `Tracee::Preprocessors::Formatter` something formed like one of them.
|
121
|
+
Notice that a logger whose formatter template does not include the `%{caller}` interpolation key, will not process a caller at all.
|
122
|
+
|
123
|
+
#### Check out the stack
|
124
|
+
|
125
|
+
Using a formatter that processes a caller, by default you would see within every log message a reference to the line from which a logger had been called, e.g.
|
126
|
+
```ruby
|
127
|
+
# app/models/user.rb, this line is 40
|
128
|
+
def mark_dialog_as_read(contact_id)
|
129
|
+
$log.info contact_id
|
130
|
+
|
131
|
+
# logs
|
132
|
+
11:22:33.123 INFO [user.rb:42 :mark_dialog_as_read]: 1265
|
133
|
+
```
|
134
|
+
|
135
|
+
but you can tell a logger to pick a caller stack slice by providing the `:caller_at => < Integer | Range | Array<Integer | Range> >` option. For more processable call forms see `spec/logger_spec.rb`.
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# app/controllers/dialogs_controller.rb, this line is 138
|
139
|
+
def show(contact_id)
|
140
|
+
current_user.mark_dialog_as_read(params[:contact_id])
|
141
|
+
|
142
|
+
# app/models/user.rb, this line is 40
|
143
|
+
def mark_dialog_as_read(contact_id)
|
144
|
+
$log.info contact_id, caller_at: 0..1
|
145
|
+
|
146
|
+
# logs
|
147
|
+
11:22:33.123 INFO [dialogs_controller.rb:140 :show -> user.rb:42 :mark_dialog_as_read]: 1265
|
148
|
+
```
|
149
|
+
|
150
|
+
Now you know for sure where the method is being called from.
|
151
|
+
|
152
|
+
### Stream
|
153
|
+
|
154
|
+
Tracee::Stream is a proxy for an IO. It can be initialized with
|
155
|
+
* a String that will be treated as a file path;
|
156
|
+
* an IO itself;
|
157
|
+
* a Hash that maps an incoming message level (String or Symbol) to a file path or an IO;
|
158
|
+
* a Hash with the `:cascade` key and a file path value with the `%{level}` interpolation key.
|
159
|
+
|
160
|
+
Initialized with a Hash, the Stream will write exactly to the IOs which is associated with the levels between the current log level of a logger instance and the incoming message level.
|
161
|
+
|
162
|
+
If you want to direct messages to a stream which is neither inherited from IO, nor StringIO, nor is a file path, then your stream object should at least respond to #<< method, and you should explicitly use `Tracee::IndifferentStream` as a logger stream:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
logger = Tracee::Logger.new stream: Tracee::IndifferentStream.new(my_stream)
|
166
|
+
```
|
167
|
+
|
168
|
+
or
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
logger = Tracee::Logger.new stream: Tracee::IndifferentStream.new(debug: my_verbose_stream, info: my_not_so_verbose_stream, error: my_crucial_stream)
|
172
|
+
```
|
173
|
+
|
174
|
+
### Benchmarking
|
175
|
+
|
176
|
+
Tracee can perform simple benchmarking, measuring call-to-call time difference.
|
177
|
+
|
178
|
+
`logger.tick!` starts or resets a measurement.
|
179
|
+
Each consequent `logger.tick` will log a time difference between the current #tick and the previous #tick or #tick!.
|
180
|
+
A tick can accept a message that would go to the "info" channel.
|
181
|
+
|
182
|
+
*If a logger level is higher than "info" then no benchmarking would be performed at all. Though, this behaviour may change in the next versions.*
|
183
|
+
|
184
|
+
To measure a block time you can run
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
logger.benchmark do # something
|
188
|
+
```
|
189
|
+
|
190
|
+
Suppose, we have a code that is executed for about 100ms, but it seems to be too long.
|
191
|
+
You measure time in 5 points between calls, and it gives you in turn:
|
192
|
+
|
193
|
+
0.025997, 0.012339, 0.037864, 0.0415 # the first turn
|
194
|
+
0.01658, 0.011366, 0.046607, 0.052117 # the second turn
|
195
|
+
0.016733, 0.011295, 0.032805, 0.036667 # the third turn
|
196
|
+
|
197
|
+
How long has each block of code been executed _actually_?
|
198
|
+
The code runtime may fluctuate slightly because a PC does some work beside your benchmarking.
|
199
|
+
The less the code execution time, the more relative fluctuations are. Thats why we do enough passes to minify them.
|
200
|
+
|
201
|
+
This module allows to not only measure time between arbitrary calls (ticks),
|
202
|
+
and to not only get an average from multiple repeats of a block,
|
203
|
+
but also to get a list of averages between each tick in a block.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
def retard
|
207
|
+
$log.tick!
|
208
|
+
sleep 0.001; $log.tick
|
209
|
+
sleep 0.01; $log.tick
|
210
|
+
sleep 0.1; $log.tick
|
211
|
+
$log << 'Got to say something...'
|
212
|
+
'Hello!'
|
213
|
+
end
|
214
|
+
|
215
|
+
$log.benchmark(times: 20) {retard} # execute a block 20 times
|
216
|
+
|
217
|
+
#logs
|
218
|
+
03:11:47.365 INFO [(irb):3 :retard]: [tick +0.022103]
|
219
|
+
03:11:47.375 INFO [(irb):4 :retard]: [tick +0.202998]
|
220
|
+
03:11:47.476 INFO [(irb):5 :retard]: [tick +2.003133]
|
221
|
+
03:11:47.476 INFO [(irb):6 :retard]: Got to say something...
|
222
|
+
03:11:47.477 INFO [(irb):10 :irb_binding]: [111.489342ms each; 2229.786832ms total] Hello!
|
223
|
+
```
|
224
|
+
As you may have noticed, a logger had been silenced until the last pass.
|
225
|
+
|
226
|
+
### Other notes
|
227
|
+
|
228
|
+
Tracee::Logger is compatible with ActiveSupport::TaggedLogging through a metaprogrammagick. If you want to use it, initialize a logger like this:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
config.logger = ActiveSupport::TaggedLogging.new(Tracee::Logger.new)
|
232
|
+
```
|
233
|
+
|
234
|
+
|
235
|
+
## Backtrace extension
|
236
|
+
|
237
|
+
Tracee makes exception's backtraces look more informative by
|
238
|
+
* representing each call along with the actual line of code where it happen
|
239
|
+
* reducing an amount of trace lines by grouping all the calls from the same line.
|
240
|
+
|
241
|
+
Unless you're from Python, it's easier to show than to tell,
|
242
|
+
```
|
243
|
+
001:0> load 'spec/exception_example.rb'
|
244
|
+
### => true
|
245
|
+
002:0> Bomb::Wick.new.light!
|
246
|
+
RuntimeError: It has appeared too wet!
|
247
|
+
from /home/shinku/gems/tracee/spec/exception_example.rb:8:in `boom!'
|
248
|
+
>> fail "It has appeared too wet!"
|
249
|
+
from /home/shinku/gems/tracee/spec/exception_example.rb:17:in `raise!' -> `loop' -> `raise!' -> `loop' -> `raise! {2}'
|
250
|
+
>> loop {loop {Bomb.new.boom! if (t += 1) == 100}}
|
251
|
+
from /home/shinku/gems/tracee/spec/exception_example.rb:23:in `heat!'
|
252
|
+
>> Temperature.new.raise!
|
253
|
+
from /home/shinku/gems/tracee/spec/exception_example.rb:31:in `light!'
|
254
|
+
>> Explosive.new.heat!
|
255
|
+
from (irb):2
|
256
|
+
>> Bomb::Wick.new.light!
|
257
|
+
```
|
258
|
+
|
259
|
+
This feature is integrated with BetterErrors and ActiveSupport::BacktraceCleaner to format a trace of exceptions that would be raised within Rails server environment before send it to a logger.
|
260
|
+
To enable it in a console, put
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
Tracee.decorate_exceptions_stack
|
264
|
+
```
|
265
|
+
|
266
|
+
into `.irbrc` or similar repl config file after `require 'tracee'`.
|
267
|
+
To enable it in a non-Rails application, call
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
Tracee.decorate_exceptions_stack
|
271
|
+
```
|
272
|
+
|
273
|
+
at a moment when an application has mostly been loaded.
|
274
|
+
However, don't run it from a Rails initializer, because it will significantly slowdown the Rails middleware.
|
275
|
+
|
276
|
+
|
277
|
+
## Next goals
|
278
|
+
|
279
|
+
For the last 3 years I have an idea of a logger that would stream by a WebSocket an HTML with lots of stack info (variable values) which I then could fold/unfold by hands and analyze by some 3rd party software.
|
280
|
+
By and large, we have the [BetterErrors](https://github.com/charliesome/better_errors) for such a thing for unhandled exceptions in a Rack-based application.
|
281
|
+
However, what I want to achieve may be of a much help to solve floating bugs, especially in those cases when to raise an exception is just inacceptable.
|
282
|
+
|
283
|
+
|
284
|
+
## Contributing
|
36
285
|
|
286
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tinbka/tracee.
|
data/lib/tracee/benchmarkable.rb
CHANGED
@@ -24,11 +24,15 @@ module Tracee
|
|
24
24
|
# 23:29:59.120 INFO [(irb):8 :irb_binding]: [tick +0.000559] [120.978946ms each; 2419.578914ms total] #<Ability:0x000000088c89c8>
|
25
25
|
module Benchmarkable
|
26
26
|
|
27
|
-
def benchmark(times: 1
|
27
|
+
def benchmark(times: 1)
|
28
|
+
return yield unless info?
|
29
|
+
|
28
30
|
@benchmark = Benchmark.new
|
29
31
|
before_proc = Time.now
|
30
32
|
|
33
|
+
prev_level, self.level = self.level, :unknown
|
31
34
|
(times - 1).times {yield}
|
35
|
+
self.level = prev_level
|
32
36
|
@benchmark.last_pass!
|
33
37
|
result = yield
|
34
38
|
|
@@ -39,9 +43,15 @@ module Tracee
|
|
39
43
|
milliseconds_each = highlight_time_diff(diff_ms/times)
|
40
44
|
milliseconds_total = highlight_time_diff(diff_ms)
|
41
45
|
info "[#{milliseconds_each}ms each; #{milliseconds_total}ms total] #{result}", caller_at: 1
|
46
|
+
|
47
|
+
ensure
|
48
|
+
self.level = prev_level
|
49
|
+
@benchmark = nil
|
42
50
|
end
|
43
51
|
|
44
52
|
def tick(msg='', caller_offset: 0)
|
53
|
+
return unless @benchmark or info?
|
54
|
+
|
45
55
|
now = Time.now
|
46
56
|
|
47
57
|
if @benchmark
|
@@ -66,6 +76,8 @@ module Tracee
|
|
66
76
|
end
|
67
77
|
|
68
78
|
def tick!(msg='', caller_offset: 0)
|
79
|
+
return unless @benchmark or info?
|
80
|
+
|
69
81
|
@benchmark.first! if @benchmark
|
70
82
|
Thread.current[:tracee_checkpoint] = nil
|
71
83
|
|
data/lib/tracee/logger.rb
CHANGED
@@ -128,11 +128,13 @@ module Tracee
|
|
128
128
|
if caller_at.is_a? Array
|
129
129
|
caller_slice = caller_at.map! {|i| caller[i]}
|
130
130
|
else
|
131
|
-
caller_slice = [
|
131
|
+
caller_slice = [caller[caller_at]]
|
132
132
|
end
|
133
|
+
else
|
134
|
+
caller_slice = []
|
133
135
|
end
|
134
136
|
|
135
|
-
write msg, progname, '#{level_name}', #{level_int}, caller_slice
|
137
|
+
write msg, progname, '#{level_name}', #{level_int}, caller_slice.flatten
|
136
138
|
end
|
137
139
|
|
138
140
|
def #{level_name}?
|
data/lib/tracee/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tracee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Baev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|