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 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