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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 35e0c1ca049152c29c82fc41fb10eb9c5b5e4ae3
4
- data.tar.gz: 6a5655c070b8a7d3a1702ce2ac60f38db4eb1912
3
+ metadata.gz: f5cf4bf19f3573beff7a7b2bbd48dea36af50e3a
4
+ data.tar.gz: b653e49210ff047f41231be4ece436229d5935c8
5
5
  SHA512:
6
- metadata.gz: 89ebe61689eb24b7d43b45e7c3ad00617940f117dffda032a2a1417ce8d9cdb4dae08c4569a17a8f2a08239fa86f78df7db27b0616d50ddfbf110c5825a9da13
7
- data.tar.gz: 7797753852f37ef9141a7bd1edde46d7ac126241063634cf806c911e92a1132ba784360e5e3f85ccfef0a46efc697b70f62c8cc5708b69584810b8ae6bd8f32b
6
+ metadata.gz: 1a22da0413561e07d85bd0bbc4f0802e4cad759215a1a63004edf7691a71797c186e397175322aead72062c704d72dec9179232f9034a907106ce885c180f079
7
+ data.tar.gz: daf6962a7a3a490a9b15b5c189d749d5c736691a4dbb8f561d98f67f9b1cc758eb44e9414f943066f748638966a5b7b958abca980337c31dc3a3c954eed97df9
data/README.md CHANGED
@@ -1,8 +1,33 @@
1
- # Tracee
1
+ ## Tracee
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/tracee`. To experiment with that code, run `bin/console` for an interactive prompt.
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
- $ bundle
42
+ `$ bundle`
18
43
 
19
- Or install it yourself as:
44
+ Or install it into your current gemset as:
20
45
 
21
- $ gem install tracee
46
+ `$ gem install tracee `
22
47
 
23
- ## Usage
24
48
 
25
- TODO: Write usage instructions here
49
+ ## Logger usage
26
50
 
27
- ## Development
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
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
62
+ \#debug, #info and #warn aliased with #<=, #<< and #< respectively, so you can do << to your log.
32
63
 
33
- ## Contributing
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tracee.
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.
@@ -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, &block)
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 = [*caller[caller_at]]
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}?
@@ -1,3 +1,3 @@
1
1
  module Tracee
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
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: 0.1.0
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: 2015-11-16 00:00:00.000000000 Z
11
+ date: 2016-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler