tracee 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 35e0c1ca049152c29c82fc41fb10eb9c5b5e4ae3
4
+ data.tar.gz: 6a5655c070b8a7d3a1702ce2ac60f38db4eb1912
5
+ SHA512:
6
+ metadata.gz: 89ebe61689eb24b7d43b45e7c3ad00617940f117dffda032a2a1417ce8d9cdb4dae08c4569a17a8f2a08239fa86f78df7db27b0616d50ddfbf110c5825a9da13
7
+ data.tar.gz: 7797753852f37ef9141a7bd1edde46d7ac126241063634cf806c911e92a1132ba784360e5e3f85ccfef0a46efc697b70f62c8cc5708b69584810b8ae6bd8f32b
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /tmp/
11
+
12
+ .directory
13
+ *.log
14
+ *.bak
15
+ *.old
16
+ *.gem
17
+
18
+ /auth.yml
19
+ /feed.json
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tracee.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Sergey Baev, https://github.com/tinbka
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Tracee
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.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'tracee'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install tracee
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
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.
30
+
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).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tracee.
36
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tracee"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,113 @@
1
+ module Tracee
2
+ # Suppose, we have a code that works too slow, still it runs for 50ms.
3
+ # You measure time in 5 points between calls, and it gives you the first time:
4
+ # 0.025997, 0.012339, 0.037864, 0.0415
5
+ # the next time:
6
+ # 0.01658, 0.011366, 0.046607, 0.052117
7
+ # the third time:
8
+ # 0.016733, 0.011295, 0.032805, 0.036667
9
+ # How long has each block of code been executed _actualy_?
10
+ # The code runtime may fluctuate slightly because a PC does some work beside your benchmarking.
11
+ # The less the code runtime, the more relative fluctuations are. Thats why we do enough passes to minify them.
12
+ #
13
+ # This module allows to not only measure time between arbitrary calls (ticks),
14
+ # and not only get an average from multiple repeats of a block,
15
+ # but to get a list of averages between each arbitrary call (tick) in a block.
16
+ #
17
+ # Here's a sample:
18
+ #
19
+ #> $log.benchmark(times:20) {Ability.new(u)}
20
+ # 23:29:59.021 INFO [ability.rb:76 :assistant_permissions]: [tick +0.576797]
21
+ # 23:29:59.034 INFO [ability.rb:84 :assistant_permissions]: [tick +0.245685]
22
+ # 23:29:59.075 INFO [ability.rb:93 :assistant_permissions]: [tick +0.728214]
23
+ # 23:29:59.120 INFO [ability.rb:111 :assistant_permissions]: [tick +0.866646]
24
+ # 23:29:59.120 INFO [(irb):8 :irb_binding]: [tick +0.000559] [120.978946ms each; 2419.578914ms total] #<Ability:0x000000088c89c8>
25
+ module Benchmarkable
26
+
27
+ def benchmark(times: 1, &block)
28
+ @benchmark = Benchmark.new
29
+ before_proc = Time.now
30
+
31
+ (times - 1).times {yield}
32
+ @benchmark.last_pass!
33
+ result = yield
34
+
35
+ now = Time.now
36
+ @benchmark = nil
37
+
38
+ diff_ms = (now - before_proc)*1000
39
+ milliseconds_each = highlight_time_diff(diff_ms/times)
40
+ milliseconds_total = highlight_time_diff(diff_ms)
41
+ info "[#{milliseconds_each}ms each; #{milliseconds_total}ms total] #{result}", caller_at: 1
42
+ end
43
+
44
+ def tick(msg='', caller_offset: 0)
45
+ now = Time.now
46
+
47
+ if @benchmark
48
+ if prev = Thread.current[:tracee_checkpoint]
49
+ tick_diff = @benchmark.add_time(now - prev)
50
+ if @benchmark.last_pass
51
+ info "[tick +#{highlight_time_diff(tick_diff)}] #{msg}", caller_at: caller_offset+1
52
+ end
53
+ @benchmark.next
54
+ # else we just write `now' to a thread var
55
+ end
56
+ else
57
+ if prev = Thread.current[:tracee_checkpoint]
58
+ info "[tick +#{highlight_time_diff(now - prev)}] #{msg}", caller_at: caller_offset+1
59
+ else
60
+ info "[tick] #{msg}", caller_at: caller_offset+1
61
+ end
62
+ end
63
+
64
+ Thread.current[:tracee_checkpoint] = now
65
+ nil
66
+ end
67
+
68
+ def tick!(msg='', caller_offset: 0)
69
+ @benchmark.first! if @benchmark
70
+ Thread.current[:tracee_checkpoint] = nil
71
+
72
+ tick msg, caller_offset: caller_offset+1
73
+ end
74
+
75
+
76
+ private
77
+
78
+ def highlight_time_diff(diff)
79
+ diff.round(6).to_s.sub(/(\d+)\.(\d{0,3})(\d*)$/) {|m| "#$1.".light_white + $2.white + $3.light_black}
80
+ end
81
+
82
+ end
83
+
84
+
85
+ class Benchmark
86
+ attr_reader :ticks_diffs, :last_pass, :tick_number
87
+
88
+ def initialize
89
+ @ticks_diffs = Hash.new {|h, k| h[k] = 0}
90
+ end
91
+
92
+ def last_pass!
93
+ @last_pass = true
94
+ end
95
+
96
+ def first!
97
+ @tick_number = 0
98
+ end
99
+
100
+ def next
101
+ @tick_number += 1
102
+ end
103
+
104
+ def add_time(amount)
105
+ @ticks_diffs[@tick_number] += amount
106
+ end
107
+
108
+ def tick_diff
109
+ @ticks_diffs[@tick_number]
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'rails'
3
+
4
+ module Tracee
5
+ class Engine < Rails::Engine
6
+
7
+ initializer "tracee.decorate_stack" do |app|
8
+ if defined? ::BetterErrors
9
+ Tracee.decorate_better_errors_stack
10
+ else
11
+ Tracee.decorate_active_support_stack
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ rescue LoadError
18
+ end
@@ -0,0 +1,51 @@
1
+ module Tracee
2
+ module Extensions
3
+ module ActiveSupport
4
+
5
+ module BacktraceCleaner
6
+ extend ::ActiveSupport::Concern
7
+
8
+ included do
9
+ if defined? Rails
10
+ Rails.backtrace_cleaner.add_silencer {|line| line =~ IGNORE_RE}
11
+ end
12
+ alias_method_chain :clean, :decorate
13
+ end
14
+
15
+ def clean_with_decorate(backtrace, kind=:silent)
16
+ Stack::BaseDecorator.(clean_without_decorate(backtrace, kind))
17
+ end
18
+
19
+ end
20
+
21
+ module TaggedLogging
22
+ module Formatter
23
+ extend ::ActiveSupport::Concern
24
+
25
+ # Well, metaprogamming is a game you can play together!
26
+ # We do it so that Tracee::Logger would not have to care about an arity of corresponding #call or whatever.
27
+ included do
28
+ def self.extended(a_formatter_to_apply_tagging_on)
29
+ if a_formatter_to_apply_tagging_on.is_a? Tracee::Preprocessors::Base
30
+ a_formatter_to_apply_tagging_on.instance_variable_set\
31
+ :@original_call,
32
+ a_formatter_to_apply_tagging_on.class
33
+ .instance_method(:call)
34
+ .bind(a_formatter_to_apply_tagging_on)
35
+
36
+ a_formatter_to_apply_tagging_on.class_eval do
37
+ def call(severity, timestamp, progname, msg, caller_slice=[])
38
+ @original_call.(severity, timestamp, progname, "#{tags_text}#{msg}", caller_slice)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,29 @@
1
+ module Tracee
2
+ module Extensions
3
+ module BetterErrors
4
+ module Middleware
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ alias_method_chain :log_exception, :decorate
9
+ end
10
+
11
+ def log_exception_with_decorate
12
+ return unless ::BetterErrors.logger
13
+
14
+ message = "\n#{@error_page.exception.class} - #{@error_page.exception.message}:\n"
15
+
16
+ frames = @error_page.backtrace_frames # original definition
17
+ frames = frames.map(&:to_s).reject {|line| line =~ IGNORE_RE}
18
+ frames = Stack::BaseDecorator.(frames)
19
+ frames.each do |frame|
20
+ message << " #{frame}\n"
21
+ end
22
+
23
+ ::BetterErrors.logger.fatal message
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,68 @@
1
+ module Tracee
2
+ module Extensions
3
+ module Exception
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ alias_method_chain :set_backtrace, :decorate
8
+ class_attribute :trace_decorator
9
+ end
10
+
11
+ ## Gotcha:
12
+ # If you also set (e.g. in irbrc file)
13
+ #
14
+ # module Readline
15
+ # alias :orig_readline :readline
16
+ # def readline(*args)
17
+ # ln = orig_readline(*args)
18
+ # SCRIPT_LINES__['(irb)'] << "#{ln}\n"
19
+ # ln
20
+ # end
21
+ # end
22
+ #
23
+ # it will be possible to fetch lines entered in IRB
24
+ # else format_trace would only read ordinary require'd files
25
+ ##
26
+ if RUBY_VERSION > '2.1'
27
+
28
+ def set_backtrace_with_decorate(trace)
29
+ if decorator = self.class.trace_decorator
30
+ if trace.is_a? Thread::Backtrace
31
+ return trace
32
+ else
33
+ trace = decorator.(trace)
34
+ end
35
+ end
36
+ set_backtrace_without_decorate(trace)
37
+ end
38
+
39
+ else
40
+
41
+ def set_backtrace(trace)
42
+ if decorator = self.class.trace_decorator
43
+ trace = decorator.(trace)
44
+ end
45
+ set_backtrace_without_decorate(trace)
46
+ end
47
+
48
+ end
49
+
50
+ ## Use case:
51
+ # We have some method that we don't want to crash application in production but want to have this crash potential been logged
52
+ #
53
+ # def unsured_method
54
+ # ... some crashable calls ...
55
+ # rescue PotentialException
56
+ # $!.log
57
+ # end
58
+ ##
59
+ def log
60
+ Rails.logger.error [
61
+ "The exception has been handled: #{self.class} — #{message.force_encoding('UTF-8')}:",
62
+ *Rails.backtrace_cleaner.clean(backtrace)
63
+ ]*"\n"
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,165 @@
1
+ module Tracee
2
+
3
+ class Logger
4
+ LEVELS = [
5
+ 'DEBUG', # Low-level information for developers
6
+ 'INFO', # Generic (useful) information about system operation
7
+ 'WARN', ## A warning
8
+ 'ERROR', # A handleable error condition
9
+ 'FATAL', # An unhandleable error that results in a program crash
10
+ 'UNKNOWN' # An unknown message that should always be logged
11
+ ].freeze
12
+ LEVEL_NAMES = LEVELS.map(&:downcase).freeze
13
+
14
+ include Tracee::Benchmarkable
15
+
16
+
17
+ attr_reader :level, :preprocessors, :formatter, :streams
18
+
19
+
20
+ def initialize(stream: $stdout, streams: nil, formatter: {:formatter => :tracee}, preprocessors: [], level: :info)
21
+ @streams = []
22
+ streams ||= [stream]
23
+ streams.each {|item| add_stream item}
24
+
25
+ if formatter.is_a? Hash
26
+ # `formatter=' can't accept *array
27
+ set_formatter *formatter.to_a.flatten
28
+ else
29
+ self.formatter = formatter
30
+ end
31
+
32
+ @preprocessors = []
33
+ preprocessors.each {|item|
34
+ if item.is_a? Hash
35
+ add_preprocessor *item.to_a.flatten
36
+ else
37
+ add_preprocessor item
38
+ end
39
+ }
40
+
41
+ self.level = level
42
+ read_log_level_from_env
43
+ end
44
+
45
+
46
+ def add_preprocessor(callable_or_symbol=nil, *preprocessor_params)
47
+ if callable_or_symbol.is_a? Symbol
48
+ @preprocessors << Tracee::Preprocessors.const_get(callable_or_symbol.to_s.camelize).new(*preprocessor_params)
49
+ elsif callable_or_symbol.respond_to? :call
50
+ @preprocessors << callable_or_symbol
51
+ else
52
+ raise TypeError, 'A preprocessor must respond to #call'
53
+ end
54
+ end
55
+
56
+ def set_formatter(callable_or_symbol=nil, *formatter_params)
57
+ if callable_or_symbol.is_a? Symbol
58
+ @formatter = Tracee::Preprocessors.const_get(callable_or_symbol.to_s.camelize).new(*formatter_params)
59
+ elsif callable_or_symbol.respond_to? :call
60
+ @formatters = callable_or_symbol
61
+ else
62
+ raise TypeError, 'A formatter must respond to #call'
63
+ end
64
+ end
65
+ alias formatter= set_formatter
66
+
67
+ def should_process_caller?
68
+ formatter.respond_to? :should_process_caller? and formatter.should_process_caller?
69
+ end
70
+
71
+ def add_stream(target_or_stream)
72
+ if target_or_stream.is_a? Tracee::Stream
73
+ @streams << target_or_stream
74
+ else
75
+ @streams << Stream.new(target_or_stream)
76
+ end
77
+ end
78
+
79
+ def level=(level)
80
+ @level = level.is_a?(Integer) ? level : LEVELS.index(level.to_s.upcase)
81
+ end
82
+
83
+ alias log_level= level=
84
+ alias log_level level
85
+
86
+
87
+ def write(msg, progname, level, level_int, caller_slice=[])
88
+ now = DateTime.now
89
+
90
+ catch :halt do
91
+ @preprocessors.each do |preprocessor|
92
+ msg = preprocessor.(level, now, progname, msg, caller_slice)
93
+ end
94
+
95
+ msg = @formatter.(level, now, progname, msg, caller_slice)
96
+
97
+ @streams.each do |stream|
98
+ stream.write msg, level_int, log_level
99
+ end
100
+ end
101
+ nil
102
+ end
103
+
104
+ LEVELS.each_with_index do |level, level_int|
105
+ const_set level, level_int
106
+
107
+ level_name = level.downcase
108
+
109
+ class_eval <<-EOS, __FILE__, __LINE__+1
110
+ def #{level_name}(*args, &block)
111
+ return if @level > #{level_int}
112
+
113
+ if block
114
+ msg = block.()
115
+ if args[0].is_a? Hash
116
+ caller_at = args[0][:caller_at] || 0
117
+ else
118
+ progname = args[0].to_s
119
+ end
120
+ else
121
+ msg = args[0]
122
+ end
123
+
124
+ if should_process_caller?
125
+ caller = caller(1)
126
+
127
+ caller_at ||= (args[1] || {})[:caller_at] || 0
128
+ if caller_at.is_a? Array
129
+ caller_slice = caller_at.map! {|i| caller[i]}
130
+ else
131
+ caller_slice = [*caller[caller_at]]
132
+ end
133
+ end
134
+
135
+ write msg, progname, '#{level_name}', #{level_int}, caller_slice
136
+ end
137
+
138
+ def #{level_name}?
139
+ @level <= #{level_int}
140
+ end
141
+ EOS
142
+ end
143
+
144
+ alias <= debug
145
+ alias << info
146
+ alias < warn
147
+
148
+
149
+ private
150
+
151
+ def read_log_level_from_env
152
+ if ENV['LOG_LEVEL'] and LEVELS.include? ENV['LOG_LEVEL']
153
+ self.log_level = ENV['LOG_LEVEL']
154
+ elsif ENV['DEBUG'] || ENV['VERBOSE']
155
+ self.log_level = 'DEBUG'
156
+ elsif ENV['WARN'] || ENV['QUIET']
157
+ self.log_level = 'WARN'
158
+ elsif ENV['SILENT']
159
+ self.log_level = 'ERROR'
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+ end
@@ -0,0 +1,15 @@
1
+ module Tracee
2
+ module Preprocessors
3
+ class Base
4
+
5
+ def call(msg_level, datetime, progname, msg, caller_slice=[])
6
+ msg
7
+ end
8
+
9
+ def halt!
10
+ throw :halt
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,135 @@
1
+ module Tracee
2
+ module Preprocessors
3
+ class Formatter < Base
4
+ COLORED_LEVELS = {
5
+ 'debug' => 'DEBUG'.white,
6
+ 'info' => 'INFO'.light_cyan,
7
+ 'warn' => 'WARN'.light_magenta,
8
+ 'error' => 'ERROR'.light_yellow,
9
+ 'fatal' => 'FATAL'.light_red,
10
+ 'unknown' => 'UNKNOWN'.light_black
11
+ }.freeze
12
+
13
+ UPCASE_LEVELS = Tracee::Logger::LEVEL_NAMES.map {|name| [name, name.upcase]}.to_h.freeze
14
+
15
+ TEMPLATES = {
16
+ tracee: {
17
+ summary: "%{datetime} %{level} [%{caller}]: %{message}",
18
+ datetime: "%T.%3N",
19
+ level: COLORED_LEVELS,
20
+ caller: "#{'%{file}:%{line}'.white} #{':%{method}'.light_red}"
21
+ },
22
+
23
+ logger_formatter: {
24
+ summary: "%{level_letter}, [%{datetime} #%{pid}] %{level} -- %{progname}: %{message}",
25
+ datetime: "%FT%T.%6N",
26
+ level: UPCASE_LEVELS
27
+ },
28
+
29
+ plain: "%{message}",
30
+
31
+ empty: ""
32
+ }.freeze
33
+
34
+ TEMPLATE_KEYS = %w{datetime level level_letter pid progname caller message}.freeze
35
+ CALLER_KEYS = %W{path file line method}.freeze
36
+
37
+ attr_reader :summary, :caller, :datetime, :level
38
+
39
+
40
+ # available template keys : datetime, level, level_letter, pid, thread_id, progname, caller, message
41
+ # available caller keys : path, file, line, method
42
+ # params : {
43
+ # summary: <string containing available template keys as interpolation marks>,
44
+ # datetime: <format available to DateTime#strftime>, # optional
45
+ # level: {<severity level name> => <label string>, ... }, # optional
46
+ # caller: <string containing available caller keys as interpolation marks> # required if summary refers caller
47
+ # }
48
+ def initialize(params_or_key)
49
+ if params_or_key.is_a? Symbol
50
+ params = TEMPLATES[params_or_key]
51
+ end
52
+ if params.is_a? String
53
+ params = {summary: params}
54
+ end
55
+
56
+ unless params.is_a? Hash
57
+ raise TypeError, 'params must be a Hash or a reference to one of Tracee::Formatters::Template::TEMPLATES'
58
+ end
59
+
60
+ @summary, @caller, @datetime, @level = params.values_at(:summary, :caller, :datetime, :level).map &:freeze
61
+ @references = TEMPLATE_KEYS.select {|key| @summary["%{#{key}}"]}.to_set
62
+ end
63
+
64
+
65
+ def call(msg_level, datetime, progname, msg, caller_slice=[])
66
+ result = @summary.dup
67
+
68
+ if @references.include? 'datetime'
69
+ result.sub! '%{datetime}', datetime.strftime(@datetime || '%FT%T%Z')
70
+ end
71
+
72
+ if @references.include? 'level' or @references.include? 'level_letter'
73
+ level = @level[msg_level] || msg_level
74
+ result.sub! '%{level}', level
75
+ result.sub! '%{level_letter}', level[0]
76
+ end
77
+
78
+ if @references.include? 'pid'
79
+ result.sub! '%{pid}', Process.pid.to_s
80
+ end
81
+
82
+ if @references.include? 'thread_id'
83
+ result.sub! '%{thread_id}', Thread.current.object_id
84
+ end
85
+
86
+ if @references.include? 'progname'
87
+ result.sub! '%{progname}', progname
88
+ end
89
+
90
+ if @references.include? 'caller'
91
+ caller_slice = caller_slice.reverse.map {|line|
92
+ path, file, line, is_block, block_level, method = line.match(CALLER_RE)[1..-1]
93
+ block_level ||= is_block && '1'
94
+ method = "#{method} {#{block_level}}" if block_level
95
+ @caller % {path: path, file: file, line: line, method: method}
96
+ } * ' -> '
97
+ result.sub! '%{caller}', caller_slice
98
+ end
99
+
100
+ if @references.include? 'message'
101
+ if msg.nil?
102
+ msg = "\b\b"
103
+ elsif !msg.is_a?(String)
104
+ msg = msg.inspect
105
+ end
106
+ result.sub! '%{message}', msg
107
+ end
108
+
109
+ return result + "\n"
110
+ end
111
+
112
+
113
+ def should_process_caller?
114
+ @references.include? 'caller'
115
+ end
116
+
117
+
118
+ def inspect
119
+ summary = @summary.dup
120
+ if @datetime
121
+ summary.sub!('%{datetime}', DateTime.parse('2000-10-20 11:22:33.123456789').strftime(@datetime))
122
+ end
123
+ if @level
124
+ summary.sub!('%{level}', "{#{@level.values*', '}}")
125
+ summary.sub!('%{level_letter}', "{#{@level.values.map {|w| w[0]}*', '}}")
126
+ end
127
+ if @caller
128
+ summary.sub!('%{caller}', @caller.to_s)
129
+ end
130
+ %{#{to_s.chop} "#{summary}">}
131
+ end
132
+
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,25 @@
1
+ # quiet_assets gem works only on thread-safe environment or on multithreaded one with only few assets.
2
+ # Known issue: https://github.com/evrone/quiet_assets/issues/40
3
+ # I'd fix it so that it would always reset log_level to that of Rails.application.config.log_level
4
+ # But such an approach will not allow to change log_level within runtime.
5
+ #
6
+ # Tracee deals with it in pretty straightforward manner. It just ignores whatever we do not care about.
7
+ module Tracee
8
+ module Preprocessors
9
+ class QuietAssets < Base
10
+
11
+ def initialize(assets_paths=['assets'])
12
+ @assets_paths_pattern = assets_paths * '|'
13
+ end
14
+
15
+ def call(msg_level, datetime, progname, msg, caller_slice=[])
16
+ if msg =~ %r{^Started GET "/(#@assets_paths_pattern)/}
17
+ halt!
18
+ else
19
+ msg
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ module Tracee
2
+ module Stack
3
+ module BaseDecorator
4
+
5
+ # Make sure that Dir.pwd is the application root directory
6
+ def self.call(source)
7
+ return source if source.empty? or source[0]["\n"] # already decorated
8
+
9
+ result, current_line_steps = [], []
10
+ # path, file, line, is_block, block_level, method
11
+ step_details = source[0].match(CALLER_RE)
12
+
13
+ source.each_with_index do |step, i|
14
+ next_step_details = source[i+1] && source[i+1].match(CALLER_RE)
15
+
16
+ if step_details and step_details[:path] !~ Tracee::IGNORE_RE
17
+ #if level = step_details[:block_level]
18
+ # step = step.sub(/block (\(\d+ levels\) )?in/, "{#{level}}")
19
+ #end
20
+ if method = step_details[:method] and next_step_details and [step_details[:path], step_details[:line]] == [next_step_details[:path], next_step_details[:line]]
21
+ current_line_steps.unshift "`#{method}#{" {#{step_details[:block_level]}}" if step_details[:block_level]}'"
22
+ elsif step_details[:line].to_i > 0 and code_line = Tracee::Stack.readline(step_details[:path], step_details[:line].to_i)
23
+ current_line_steps.unshift step
24
+ result << "#{current_line_steps * ' -> '}\n >> #{code_line}"
25
+ current_line_steps = []
26
+ else
27
+ result << step
28
+ end
29
+ end
30
+ step_details = next_step_details
31
+ end
32
+
33
+ if defined? IRB and IRB.conf[:BACK_TRACE_LIMIT] and result.size > IRB.conf[:BACK_TRACE_LIMIT] and result.last[-1] != "\n"
34
+ result.last << "\n"
35
+ end
36
+
37
+ result
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ require 'tracee/stack/base_decorator'
2
+
3
+ module Tracee
4
+ module Stack
5
+ SCRIPT_LINES_MTIMES = {}
6
+
7
+ # Rails' autoreload of code doesn't rewrite SCRIPT_LINES__,
8
+ # to perform that automatically, Tracee::Stack.reload_script_lines should be turned on.
9
+ # This mattr is left writable mostly for debug purposes.
10
+ mattr_accessor :reload_script_lines
11
+ self.reload_script_lines = true
12
+
13
+ def self.readlines(file)
14
+ if reload_script_lines
15
+ if File.exists?(file)
16
+ mtime = File.mtime(file)
17
+ unless SCRIPT_LINES_MTIMES[file] and SCRIPT_LINES_MTIMES[file] >= mtime
18
+ SCRIPT_LINES_MTIMES[file] = mtime
19
+ SCRIPT_LINES__[file] = IO.readlines(file)
20
+ end
21
+ SCRIPT_LINES__[file]
22
+ elsif lines = SCRIPT_LINES__[file]
23
+ lines
24
+ end
25
+ else
26
+ if lines = SCRIPT_LINES__[file]
27
+ lines
28
+ else
29
+ if File.exists?(file)
30
+ SCRIPT_LINES_MTIMES[file] = File.mtime(file)
31
+ SCRIPT_LINES__[file] = IO.readlines(file)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.readline(file, line)
38
+ if lines = readlines(file)
39
+ (lines[line.to_i - 1] || "<line #{line} is not found> ").chomp.green
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,100 @@
1
+ module Tracee
2
+
3
+ class Stream
4
+ attr_reader :target
5
+
6
+ class TargetError < TypeError
7
+ def initialize(message='A target must be IO | String | Hash{<level name> => <level log file path | IO>, ... } | Hash{:cascade => <level log file path pattern with "level" key>}', *) super end
8
+ end
9
+
10
+ # @ target : IO | String | {<level name> => < level log file path | IO >, ... } | {:cascade => < level log file path pattern >}
11
+ # pattern example : "log/development.%{level}.log"
12
+ def initialize(target)
13
+ if target.is_a? Hash
14
+ raise TargetError if target.values.any? {|val| !( val.is_a? String or val.is_a? IO or val.is_a? StringIO )}
15
+
16
+ if pattern = target[:cascade]
17
+ target = Tracee::Logger::LEVEL_NAMES.map {|name|
18
+ [name, pattern % {level: name}]
19
+ }.to_h
20
+ else
21
+ target = target.with_indifferent_access
22
+ end
23
+
24
+ else
25
+ raise TargetError unless target.is_a? String or target.is_a? IO or target.is_a? StringIO
26
+ end
27
+
28
+ @target = target
29
+ end
30
+
31
+ # cascade principle:
32
+ #
33
+ # logger.log_level = :debug
34
+ # logger.warn msg
35
+ # development.debug.log << msg
36
+ # development.info.log << msg
37
+ # development.warn.log << msg
38
+ #
39
+ # logger.log_level = :warn
40
+ # logger.error msg
41
+ # development.warn.log << msg
42
+ # development.error.log << msg
43
+ def write(msg, msg_level=nil, log_level=nil)
44
+ return if msg.nil?
45
+
46
+ case @target
47
+ when Hash # cascade
48
+ Tracee::Logger::LEVEL_NAMES[log_level..msg_level].each do |name|
49
+ if target = @target[name]
50
+ io_write target, msg
51
+ end
52
+ end
53
+ else
54
+ io_write @target, msg
55
+ end
56
+ end
57
+
58
+ alias << write
59
+
60
+ private
61
+
62
+ def io_write(target, msg)
63
+ case target
64
+ when IO, StringIO then target << msg
65
+ when String then File.open(target, 'a') {|f| f << msg}
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+
72
+ class IndifferentStream < Stream
73
+
74
+ class TargetError < TypeError
75
+ def initialize(message='A target must be an object implementing #<< method | Hash{<level name> => <such an object>, ... }', *) super end
76
+ end
77
+
78
+ # @ target : IO | String | {<level name> => < level log file path | IO >, ... } | {:cascade => < level log file path pattern >}
79
+ # pattern example : "log/development.%{level}.log"
80
+ def initialize(target)
81
+ if target.is_a? Hash
82
+ raise TargetError if target.values.any? {|val| !val.respond_to? :<<}
83
+
84
+ target = target.with_indifferent_access
85
+ else
86
+ raise TargetError unless target.respond_to? :<<
87
+ end
88
+
89
+ @target = target
90
+ end
91
+
92
+ private
93
+
94
+ def io_write(target, msg)
95
+ target << msg
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,3 @@
1
+ module Tracee
2
+ VERSION = "0.1.0"
3
+ end
data/lib/tracee.rb ADDED
@@ -0,0 +1,95 @@
1
+ # When you working with IRB/Pry/Ripl it should be defined in according rc-file for all loaded ruby files to be cached into.
2
+ unless defined? SCRIPT_LINES__
3
+ SCRIPT_LINES__ = {}
4
+ end
5
+
6
+ require 'active_support'
7
+ require 'active_support/inflector'
8
+ require 'active_support/core_ext/hash/indifferent_access'
9
+ require 'active_support/core_ext/module/aliasing'
10
+ require 'active_support/core_ext/class/attribute'
11
+ require 'active_support/tagged_logging'
12
+
13
+ require 'colorize'
14
+
15
+ require 'tracee/version'
16
+ require 'tracee/benchmarkable'
17
+ require 'tracee/logger'
18
+ require 'tracee/preprocessors/base'
19
+ require 'tracee/preprocessors/formatter'
20
+ require 'tracee/preprocessors/quiet_assets'
21
+ require 'tracee/stream'
22
+ require 'tracee/stack'
23
+ require 'tracee/ext/exception'
24
+ require 'tracee/ext/active_support'
25
+ require 'tracee/ext/better_errors'
26
+ require 'tracee/engine'
27
+
28
+ module Tracee
29
+ CALLER_RE = \
30
+ %r{^(?<path>.*?(?<file>[^/\\]+?))#{ # ( path ( file ) )
31
+ }:(?<line>\d+)(?::in #{ # :( line )[ :in
32
+ }`(?<is_block>block (?:\((?<block_level>\d+) levels\) )?in )?(?<method>.+?)'#{ # `( [ block in ] closure )' ]
33
+ })?$}
34
+
35
+ IGNORE_RE = \
36
+ %r{/irb(/|\.rb$)#{ # irb internals
37
+ }|lib/active_support/dependencies.rb$#{ # everywhere-proxy
38
+ }|^-e:#{ # ruby -e oneliner
39
+ }|^(script|bin)/#{ # other common entry points
40
+ }|/gems/bundler-\d|ruby-\d.\d.\d(@[^/]+)?/bin/#{ # bundle console
41
+ }|lib/rails/commands(/|\.rb$)#{ # rails console
42
+ }}
43
+
44
+
45
+ class ::Exception
46
+ include Tracee::Extensions::Exception
47
+ end
48
+
49
+ module ::ActiveSupport::TaggedLogging::Formatter
50
+ include Tracee::Extensions::ActiveSupport::TaggedLogging::Formatter
51
+ end
52
+
53
+ # Use `Tracee.decorate_stack_everywhere` only within a console, because it significantly slowdown rails middleware.
54
+ # So better put it into .irbrc or similar.
55
+ class << self
56
+
57
+ def decorate_exceptions_stack
58
+ Exception.trace_decorator = Stack::BaseDecorator
59
+
60
+ # These would extremely slowdown or stop runtime
61
+ [SystemStackError, NoMemoryError, NameError, defined?(IRB::Abort) && IRB::Abort].compact.each do |klass|
62
+ klass.trace_decorator = nil
63
+ end
64
+
65
+ # But this NameError's subclass would not
66
+ NoMethodError.trace_decorator = Exception.trace_decorator
67
+ end
68
+
69
+ def decorate_better_errors_stack(from_decorate_everywhere=false)
70
+ if defined? BetterErrors
71
+ BetterErrors::Middleware.class_eval do
72
+ include Tracee::Extensions::BetterErrors::Middleware
73
+ end
74
+ elsif !from_decorate_everywhere
75
+ warn "Tracee.decorate_better_errors_stack was ignored, because BetterErrors hadn't been defined."
76
+ end
77
+ end
78
+
79
+ def decorate_active_support_stack
80
+ ActiveSupport::BacktraceCleaner.class_eval do
81
+ include Tracee::Extensions::ActiveSupport::BacktraceCleaner
82
+ end
83
+ end
84
+
85
+ def decorate_stack_everywhere
86
+ decorate_exceptions_stack
87
+ decorate_better_errors_stack(true)
88
+ decorate_active_support_stack
89
+ end
90
+
91
+ end
92
+
93
+
94
+ $log ||= Logger.new
95
+ end
data/tracee.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tracee/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tracee"
8
+ spec.version = Tracee::VERSION
9
+ spec.authors = ["Sergey Baev"]
10
+ spec.email = ["tinbka@gmail.com"]
11
+
12
+ spec.summary = %q{An extensible logger with stack tracing, benchmarking, preprocessing, and severity-based output splitting}
13
+ spec.description = %q{Tracee is a simple extensible logger with stack tracing, benchmarking, preprocessing, and severity-based output splitting. Tracee is meant for development and debugging of any type of application or library, and compatible with Rails. The main reason of its existence is to help you see through a stack.}
14
+ spec.homepage = "https://github.com/tinbka/tracee"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.10"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec", "~> 3.3"
23
+
24
+ spec.add_dependency "colorize"
25
+ spec.add_dependency "activesupport"
26
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tracee
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sergey Baev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: colorize
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Tracee is a simple extensible logger with stack tracing, benchmarking,
84
+ preprocessing, and severity-based output splitting. Tracee is meant for development
85
+ and debugging of any type of application or library, and compatible with Rails.
86
+ The main reason of its existence is to help you see through a stack.
87
+ email:
88
+ - tinbka@gmail.com
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".gitignore"
94
+ - ".rspec"
95
+ - ".travis.yml"
96
+ - Gemfile
97
+ - LICENSE
98
+ - README.md
99
+ - Rakefile
100
+ - bin/console
101
+ - bin/setup
102
+ - lib/tracee.rb
103
+ - lib/tracee/benchmarkable.rb
104
+ - lib/tracee/engine.rb
105
+ - lib/tracee/ext/active_support.rb
106
+ - lib/tracee/ext/better_errors.rb
107
+ - lib/tracee/ext/exception.rb
108
+ - lib/tracee/logger.rb
109
+ - lib/tracee/preprocessors/base.rb
110
+ - lib/tracee/preprocessors/formatter.rb
111
+ - lib/tracee/preprocessors/quiet_assets.rb
112
+ - lib/tracee/stack.rb
113
+ - lib/tracee/stack/base_decorator.rb
114
+ - lib/tracee/stream.rb
115
+ - lib/tracee/version.rb
116
+ - tracee.gemspec
117
+ homepage: https://github.com/tinbka/tracee
118
+ licenses: []
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.4.8
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: An extensible logger with stack tracing, benchmarking, preprocessing, and
140
+ severity-based output splitting
141
+ test_files: []