tracee 0.1.0 → 1.0.0
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 +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
|