kojak 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +157 -0
- data/Rakefile +9 -0
- data/examples/hello.rb +21 -0
- data/examples/more_complex.rb +39 -0
- data/kojak.gemspec +27 -0
- data/lib/kojak.rb +12 -0
- data/lib/kojak/caller.rb +58 -0
- data/lib/kojak/central.rb +54 -0
- data/lib/kojak/inspector.rb +62 -0
- data/lib/kojak/output.rb +66 -0
- data/lib/kojak/printer.rb +34 -0
- data/lib/kojak/printer/basic.rb +20 -0
- data/lib/kojak/printer/colorized.rb +28 -0
- data/lib/kojak/printer/nulled.rb +7 -0
- data/lib/kojak/printer/randomly_colorized.rb +30 -0
- data/lib/kojak/printer/synchronized.rb +10 -0
- data/lib/kojak/version.rb +3 -0
- data/test/dummies/dummy.rb +9 -0
- data/test/minitest_helper.rb +9 -0
- data/test/test_caller.rb +73 -0
- data/test/test_inspector.rb +35 -0
- data/test/test_printer/test_basic.rb +51 -0
- data/test/test_printer/test_colorized.rb +31 -0
- data/test/test_printer/test_nulled.rb +24 -0
- data/test/test_printer/test_randomly_colorized.rb +25 -0
- data/test/test_printer/test_synchronized.rb +31 -0
- data/test/test_top_level.rb +17 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e1ccedf06853a170a84c39986ea3baad7ea38347
|
4
|
+
data.tar.gz: 7393b032fc39c06f0e4b311aead7b719165ae23c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2a4452b4d5f0bcd9e2e7a10fd787383beb3a4bf58cebe5ffa573d8bba70a958e551df4e41e39b807b9283a60706e024a4237268b9538dd7cb3880f6cf6f7b644
|
7
|
+
data.tar.gz: d18cefa933631004fcb2dd5393970655f670016ce388f46d53cb8099d2a46453bb56f298eeddc66b7bba7625a798b79793ebdfe649dd23a1ad2218c821b73741
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Kris Kovalik
|
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,157 @@
|
|
1
|
+
# Kojak
|
2
|
+
|
3
|
+
Kojak is a bald and bold inspector and debug logger for Ruby apps. It aims to
|
4
|
+
provide debug (a.k.a. verbose) logging without writing actual any single line
|
5
|
+
of logging and printing junk. It also aims to add no overhead to the execution
|
6
|
+
whatsoever.
|
7
|
+
|
8
|
+
## Introduction
|
9
|
+
|
10
|
+
Take a look at the following code...
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class SomeFancyTransport
|
14
|
+
# *snip *
|
15
|
+
|
16
|
+
def receive
|
17
|
+
begin
|
18
|
+
log "Waiting for messages ..."
|
19
|
+
line = readline
|
20
|
+
log "Message received, decoding ..."
|
21
|
+
payload = JSON.parse(line)
|
22
|
+
log "Decoded: #{payload.inspect}"
|
23
|
+
log "Acknowledging ..."
|
24
|
+
send(:_ack => true, :_id => payload['_id'])
|
25
|
+
return payload
|
26
|
+
rescue Timeout::Error
|
27
|
+
log "Pinging ..."
|
28
|
+
send(:_ping => true)
|
29
|
+
rescue EOFError
|
30
|
+
log "Connection closed!"
|
31
|
+
@eof = true
|
32
|
+
rescue => err
|
33
|
+
log "Receive error: #{err.to_s}"
|
34
|
+
send(:_err => err.to_h) rescue nil
|
35
|
+
end
|
36
|
+
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Uhh, let's face it, this sucks... It looks ugly, getting throuhg and figuring
|
43
|
+
out what that function does is complicated. It has 2x much code than it should,
|
44
|
+
and even though `log` can be only a stub in non-verbose mode, it'll still takes
|
45
|
+
execution time and adds overhead. I heard some wisdom from my friend Jens some
|
46
|
+
time ago:
|
47
|
+
|
48
|
+
*Your goal toward every single line of code in your app is go get rid of her,
|
49
|
+
less code == less bugs.*
|
50
|
+
|
51
|
+
So let's get rid of junk...
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class SomeFancyTransport
|
55
|
+
# *snip *
|
56
|
+
|
57
|
+
def decode(s)
|
58
|
+
JSON.parse(s)
|
59
|
+
end
|
60
|
+
|
61
|
+
def ack(id)
|
62
|
+
send(:_ack => true, :_id => payload['_id'])
|
63
|
+
end
|
64
|
+
|
65
|
+
def ping
|
66
|
+
send(:_ping => true)
|
67
|
+
end
|
68
|
+
|
69
|
+
def receive_err(err)
|
70
|
+
send(:_err => err.to_h)
|
71
|
+
end
|
72
|
+
|
73
|
+
def eof!
|
74
|
+
@eof = true
|
75
|
+
end
|
76
|
+
|
77
|
+
def receive
|
78
|
+
begin
|
79
|
+
payload = decode(readline)
|
80
|
+
ack(payload['_id'])
|
81
|
+
return payload
|
82
|
+
rescue Timeout::Error
|
83
|
+
ping
|
84
|
+
rescue EOFError
|
85
|
+
eof!
|
86
|
+
rescue => err
|
87
|
+
receive_err(err) rescue nil
|
88
|
+
end
|
89
|
+
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
Um, but that's more code! Well, yes and no... Let's see, actual code to be
|
96
|
+
executed stays the same, rest are just method names and definitions. Though,
|
97
|
+
now the code is self documented and it's not sliced through and through
|
98
|
+
with junk logging lines. Easier to read, easier to test. But hey... where's the
|
99
|
+
logging? Well, here it is:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
class SomeFancyTransport
|
103
|
+
extend Kojak::Inspector
|
104
|
+
investigate :readline, :decode, :ack, :ping, ...
|
105
|
+
end
|
106
|
+
|
107
|
+
Kojak.investigate_all!
|
108
|
+
```
|
109
|
+
|
110
|
+
Hell yeah, magic! But not so much... `investigate` declares all methods that
|
111
|
+
should be inspected. Then `investigate_all!` from `Kojak` goes through all
|
112
|
+
these methods and redeclares them - it wraps original method with debugging
|
113
|
+
stuff. Now when you call that method, our inspector will gather informatin
|
114
|
+
about the execution (arguments, returns, thrown errors, execution time, etc.)
|
115
|
+
and nicely print it out for you.
|
116
|
+
|
117
|
+
Now answer youself to one very important question. When looking at verbose
|
118
|
+
log, what's more important for you? This...
|
119
|
+
|
120
|
+
```
|
121
|
+
Waiting for messages ...
|
122
|
+
Message Received! Decoding ...
|
123
|
+
Acknowledging ...
|
124
|
+
Receive error: couldn't acknowledge
|
125
|
+
```
|
126
|
+
|
127
|
+
Or maybe this:
|
128
|
+
|
129
|
+
```
|
130
|
+
SomeFancyTransport#readline (in ./lib/foo/some_fancy_transport.rb:15):
|
131
|
+
| realtime = 10.12388s
|
132
|
+
| pid = 38711
|
133
|
+
| input = []
|
134
|
+
| output = "{\"_id\":\"1\"}"
|
135
|
+
SomeFancyTransport#decode (in ./lib/foo/some_fancy_transport.rb:20):
|
136
|
+
| realtime = 0.01425s
|
137
|
+
| pid = 38711
|
138
|
+
| input = ["{\"_id\":\"1\"}"]
|
139
|
+
| output = {:id => 1}
|
140
|
+
SomeFancyTransport#ack (in ./lib/foo/some_fancy_transport.rb:30):
|
141
|
+
| realtime = 0.03331s
|
142
|
+
| pid = 38711
|
143
|
+
| input = [1]
|
144
|
+
| error = #<StandardError: couldn't acknowledge>
|
145
|
+
SomeFancyTransport#receive_err (in ./lib/foo/some_fancy_transport.rb:40):
|
146
|
+
| realtime = 0.00010s
|
147
|
+
| pid = 38711
|
148
|
+
| input = #<StandardError: couldn't acknowledge>
|
149
|
+
| output = [true, {:_err => {:kind => "standard_error", :message => "couldn't acknowledge"}}]
|
150
|
+
```
|
151
|
+
|
152
|
+
So here we swapped bunch of crappy logging code that produces a bit of useless
|
153
|
+
text messages into self-documented code, with no logging that produces this
|
154
|
+
full debug info including source location, input arguments, output and even
|
155
|
+
real execution time or pid of the process.
|
156
|
+
|
157
|
+
TODO: continue ...
|
data/Rakefile
ADDED
data/examples/hello.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'kojak'
|
2
|
+
|
3
|
+
class Hello
|
4
|
+
extend Kojak::Inspector
|
5
|
+
investigate :greet, :this_one_does_nothing
|
6
|
+
|
7
|
+
def greet(attrs = {})
|
8
|
+
raise ArgumentError.new("missing name") unless attrs[:name]
|
9
|
+
"Hello #{attrs[:name]}!"
|
10
|
+
end
|
11
|
+
|
12
|
+
def this_one_does_nothing
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Kojak.investigate!
|
17
|
+
|
18
|
+
h = Hello.new
|
19
|
+
h.greet(:name => "Jon Snow")
|
20
|
+
h.greet(:a => 1, :b => 2, :c => 3) rescue nil
|
21
|
+
h.this_one_does_nothing
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'kojak'
|
2
|
+
|
3
|
+
class Hello
|
4
|
+
extend Kojak::Inspector
|
5
|
+
investigate :greet, :this_one_does_nothing
|
6
|
+
|
7
|
+
def greet(attrs = {})
|
8
|
+
raise ArgumentError.new("missing name") unless attrs[:name]
|
9
|
+
"Hello #{attrs[:name]}!"
|
10
|
+
end
|
11
|
+
|
12
|
+
def this_one_does_nothing
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class HelloTyrion < Hello
|
17
|
+
extend Kojak::Inspector
|
18
|
+
investigate :greet, :this_one_takes_a_while
|
19
|
+
|
20
|
+
def greet(attrs = {})
|
21
|
+
super(:name => "Tyrion Lannister")
|
22
|
+
end
|
23
|
+
|
24
|
+
def this_one_takes_a_while
|
25
|
+
sleep(1.2)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Kojak.investigate!
|
30
|
+
|
31
|
+
h1 = Hello.new
|
32
|
+
h1.greet(:name => "Jon Snow")
|
33
|
+
h1.greet(:a => 1, :b => 2, :c => 3) rescue nil
|
34
|
+
h1.this_one_does_nothing
|
35
|
+
|
36
|
+
h2 = HelloTyrion.new
|
37
|
+
h2.greet(:foo => :bar)
|
38
|
+
h2.this_one_does_nothing
|
39
|
+
h2.this_one_takes_a_while
|
data/kojak.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'kojak/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "kojak"
|
9
|
+
spec.version = Kojak::VERSION
|
10
|
+
spec.authors = ["Kris Kovalik", "NIMBOOST Team"]
|
11
|
+
spec.email = ["dev@nimboost.com"]
|
12
|
+
spec.summary = %q{Kojak is a neat and bald tracer and debug logger.}
|
13
|
+
spec.description = %q{Kojak allows you to trace and debug method calls.}
|
14
|
+
spec.homepage = "http://docs.nimboost.com/lib/ruby/kojak"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "json", "~> 1.8"
|
23
|
+
spec.add_dependency "rainbow", "~> 2.0"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "mocha"
|
27
|
+
end
|
data/lib/kojak.rb
ADDED
data/lib/kojak/caller.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Kojak
|
2
|
+
# Internal: Caller wraps a method with inspection mechanism.
|
3
|
+
class Caller < Hash
|
4
|
+
# Public: Constructor. Wraps given method with caller. Gathers initial
|
5
|
+
# information about this call.
|
6
|
+
#
|
7
|
+
# method - The Method to be wrapped.
|
8
|
+
#
|
9
|
+
# Returns nothing.
|
10
|
+
def initialize(method)
|
11
|
+
@method = method
|
12
|
+
|
13
|
+
replace({
|
14
|
+
:name => "#{method.owner.name}##{method.name}",
|
15
|
+
:method => method,
|
16
|
+
:location => method.source_location,
|
17
|
+
:pid => Process.pid,
|
18
|
+
:thread => Thread.current,
|
19
|
+
:thread_id => Thread.object_id,
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Calls wrapped method with given arguments and block, and
|
24
|
+
# collects all information about this particular call.
|
25
|
+
#
|
26
|
+
# args - The Array with arguments to pass.
|
27
|
+
# block - The Proc with block to pass.
|
28
|
+
#
|
29
|
+
# Returns stuff returned by called method. Raises exceptions
|
30
|
+
# by called method if any.
|
31
|
+
def call(*args, &block)
|
32
|
+
merge!({
|
33
|
+
:block_given => block_given?,
|
34
|
+
:block => block,
|
35
|
+
:args => args
|
36
|
+
})
|
37
|
+
|
38
|
+
res, err = nil, nil
|
39
|
+
rt = Benchmark.realtime do
|
40
|
+
begin
|
41
|
+
res = @method.call(*args, &block)
|
42
|
+
rescue Exception => e
|
43
|
+
err = e
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
merge!({
|
48
|
+
:receiver => @method.receiver.class.name,
|
49
|
+
:realtime => rt,
|
50
|
+
:result => res,
|
51
|
+
:err => err
|
52
|
+
})
|
53
|
+
|
54
|
+
raise err if err
|
55
|
+
res
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Kojak
|
4
|
+
# Internal: An extension with central utilities. This module extends top
|
5
|
+
# level Kojak module, don't use it directly.
|
6
|
+
module Central
|
7
|
+
# Global mutex.
|
8
|
+
@@mtx = Mutex.new
|
9
|
+
|
10
|
+
# Slow log of investigated methods. Contains list of called names in
|
11
|
+
# order of calling.
|
12
|
+
@@log = Array.new
|
13
|
+
|
14
|
+
# Global printer.
|
15
|
+
@@printer = Printer::RandomlyColorized.new($stderr)
|
16
|
+
|
17
|
+
# Investigated classes.
|
18
|
+
@@investigated = {}
|
19
|
+
|
20
|
+
# Internal: Returns slow log array.
|
21
|
+
def slowlog
|
22
|
+
@@mtx.synchronize { @@log }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Internal: List of classes marked for investigation.
|
26
|
+
def investigated
|
27
|
+
@@mtx.synchronize { @@investigated.values }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Marks given class as investigated.
|
31
|
+
#
|
32
|
+
# cls - The Class to register.
|
33
|
+
#
|
34
|
+
# Returns list of investigated classes.
|
35
|
+
def mark_for_investigation(cls)
|
36
|
+
@@mtx.synchronize { @@investigated[cls.object_id] = cls }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Internal: Prints investigation results and writes to slow log.
|
40
|
+
#
|
41
|
+
# caller - The Caller to print.
|
42
|
+
#
|
43
|
+
# Returns nothing.
|
44
|
+
def print(caller)
|
45
|
+
@@mtx.synchronize { @@log << caller[:name] }
|
46
|
+
@@printer.puts(Output.new(caller))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Internal: Enables investigation of all registered classes.
|
50
|
+
def investigate!
|
51
|
+
@@mtx.synchronize { @@investigated.values.each(&:investigate!) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Kojak
|
2
|
+
# Internal: Registers investigator within given class for specified method.
|
3
|
+
#
|
4
|
+
# cls - The Class that will be affected.
|
5
|
+
# m - The Method to override with investigation support.
|
6
|
+
#
|
7
|
+
# Returns nothing.
|
8
|
+
def self.register_investigator_for!(cls, m)
|
9
|
+
cls.send(:define_method, m.name) do |*args,&block|
|
10
|
+
begin
|
11
|
+
caller = Caller.new(m.bind(self))
|
12
|
+
caller.call(*args,&block)
|
13
|
+
ensure
|
14
|
+
Kojak.print(caller) if caller
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Extend your class with this inspector module to get
|
20
|
+
# investigation functionality.
|
21
|
+
#
|
22
|
+
# Example
|
23
|
+
#
|
24
|
+
# class Dummy
|
25
|
+
# investigate :foo, :bar
|
26
|
+
#
|
27
|
+
# def foo
|
28
|
+
# *snip*...
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def bar
|
32
|
+
# *snip*...
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Dummy.investigate! # ... or Kojak.investigate_all!
|
37
|
+
#
|
38
|
+
module Inspector
|
39
|
+
def self.extended(cls)
|
40
|
+
Kojak.mark_for_investigation(cls)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Marks given methods for investigation.
|
44
|
+
#
|
45
|
+
# names - The Array of method names to investigate.
|
46
|
+
#
|
47
|
+
# Returns Array with names of all methods being under investigation.
|
48
|
+
def investigate(*names)
|
49
|
+
Kojak.mark_for_investigation(self)
|
50
|
+
@__kojak_investigate ||= []
|
51
|
+
@__kojak_investigate.concat(names).uniq!
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Enables investigation of caller class.
|
55
|
+
def investigate!
|
56
|
+
@__kojak_investigate.each do |name|
|
57
|
+
m = instance_method(name)
|
58
|
+
Kojak.register_investigator_for!(self, m)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/kojak/output.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Kojak
|
2
|
+
# Internal: Pretty output string from given caller information.
|
3
|
+
class Output < String
|
4
|
+
# Public: Constructor. Generates output from given caller.
|
5
|
+
def initialize(caller)
|
6
|
+
@c = caller
|
7
|
+
super("\n" + all.flatten.compact.join("\n") + "\n")
|
8
|
+
end
|
9
|
+
|
10
|
+
def all
|
11
|
+
[
|
12
|
+
header, receiver, location, pid, realtime, arguments,
|
13
|
+
result, err, footer
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def header
|
18
|
+
"=== #{@c[:name]} ".ljust(80, "=")
|
19
|
+
end
|
20
|
+
|
21
|
+
def receiver
|
22
|
+
"receiver_class=#{@c[:receiver]}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def location
|
26
|
+
"source_location=#{@c[:location].join(":")}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def pid
|
30
|
+
"pid=#{@c[:pid]}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def realtime
|
34
|
+
"real_time=%.5f" % @c[:realtime]
|
35
|
+
end
|
36
|
+
|
37
|
+
def arguments
|
38
|
+
args = @c[:args]
|
39
|
+
return if !args || args.empty?
|
40
|
+
["--- arguments ".ljust(80, '-'), pp(args)]
|
41
|
+
end
|
42
|
+
|
43
|
+
def result
|
44
|
+
res = @c[:result]
|
45
|
+
return if res.nil?
|
46
|
+
["--- result ".ljust(80, '-'), pp(res)]
|
47
|
+
end
|
48
|
+
|
49
|
+
def err
|
50
|
+
err = @c[:err]
|
51
|
+
return if err.nil?
|
52
|
+
errname = err.class.name + ": " + err.to_s
|
53
|
+
["--- exception ".ljust(80, '-'), errname]
|
54
|
+
end
|
55
|
+
|
56
|
+
def footer
|
57
|
+
"=" * 80
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def pp(x)
|
63
|
+
JSON.pretty_generate(x) rescue x.inspect
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
|
3
|
+
module Kojak
|
4
|
+
# Internal: Abstract design of a printing class.
|
5
|
+
#
|
6
|
+
# This is just a stub with documentation, implement actual printers
|
7
|
+
# in inherited classes.
|
8
|
+
class Printer
|
9
|
+
# Public: Writes given string and a newline.
|
10
|
+
#
|
11
|
+
# s - The String to print.
|
12
|
+
# args - The Array with interpolation arguments.
|
13
|
+
#
|
14
|
+
# Should return number of writen characters.
|
15
|
+
def puts(s, *args)
|
16
|
+
write("#{s}\n", *args)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Writes given string to the output.
|
20
|
+
#
|
21
|
+
# s - The String to print.
|
22
|
+
# args - The Array with interpolation arguments.
|
23
|
+
#
|
24
|
+
# Should return number of written characters.
|
25
|
+
def write(s, *args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'kojak/printer/nulled'
|
30
|
+
require 'kojak/printer/basic'
|
31
|
+
require 'kojak/printer/synchronized'
|
32
|
+
require 'kojak/printer/colorized'
|
33
|
+
require 'kojak/printer/randomly_colorized'
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Kojak
|
4
|
+
# Internal: Basic printer. It prints stuff to configured output stream.
|
5
|
+
class Printer::Basic < Printer
|
6
|
+
# Public: Constructor. Configures printer to write to given output stream.
|
7
|
+
#
|
8
|
+
# out - The Stream to write data to.
|
9
|
+
#
|
10
|
+
# Returns nothing.
|
11
|
+
def initialize(out)
|
12
|
+
@out = out
|
13
|
+
end
|
14
|
+
|
15
|
+
def write(s, *args)
|
16
|
+
args = args.map { |x| JSON.pretty_generate(x) rescue x.inspect }
|
17
|
+
@out.write(s.to_s % args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
|
3
|
+
module Kojak
|
4
|
+
# Internal: Special version of thread-safe printer that additionally colorizes
|
5
|
+
# the output with one preconfigured color.
|
6
|
+
class Printer::Colorized < Printer::Synchronized
|
7
|
+
# Public: The String or Symbol with name of the assigned color.
|
8
|
+
attr_accessor :color
|
9
|
+
|
10
|
+
# Public: Constructor. Configures printer to write to given output in
|
11
|
+
# specified color.
|
12
|
+
#
|
13
|
+
# out - The Stream to write data to.
|
14
|
+
# color - The String or Symbol with name of the color to use.
|
15
|
+
#
|
16
|
+
# Returns nothing.
|
17
|
+
def initialize(out, color = :white)
|
18
|
+
super(out)
|
19
|
+
|
20
|
+
@color = color
|
21
|
+
@rainbow = Rainbow.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(s, *args)
|
25
|
+
super(@rainbow.wrap(s.to_s).color(color), *args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
|
3
|
+
module Kojak
|
4
|
+
# Internal: Colorized logger that uses random color from the palette.
|
5
|
+
class Printer::RandomlyColorized < Printer::Colorized
|
6
|
+
# Internal: List of colors to pick from. This list is always
|
7
|
+
# randomized on start.
|
8
|
+
PALETTE = %w[
|
9
|
+
#af8700 #af875f #af8787 #af87af #af87d7 #af87ff #afaf00 #afaf5f
|
10
|
+
#afafaf #afafd7 #afafff #afd700 #afd75f #afd787 #afd7af #afd7d7
|
11
|
+
#afd7ff #afff00 #afff5f #afff87 #afffaf #afffd7 #afffff #878787
|
12
|
+
#8787af #8787d7 #8787ff #87af00 #87af5f #87af87 #87afaf #87afd7
|
13
|
+
#87afff #87d700 #87d75f #87d787 #87d7af #87d7d7 #87d7ff #87ff00
|
14
|
+
#87ff5f #87ff87 #87ffaf #87ffd7 #87ffff #d78700 #d7875f #d78787
|
15
|
+
#d787af #d787d7 #d787ff #d7af00 #d7af5f #d7af87 #d7afaf #d7afd7
|
16
|
+
].compact.shuffle
|
17
|
+
|
18
|
+
def initialize(out)
|
19
|
+
super(out, nil)
|
20
|
+
@current = -1
|
21
|
+
end
|
22
|
+
|
23
|
+
def color
|
24
|
+
@@lock.synchronize do
|
25
|
+
@current += 1
|
26
|
+
PALETTE[@current % PALETTE.size]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/test/test_caller.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe Kojak::Caller do
|
4
|
+
let(:obj) do
|
5
|
+
Dummy.new
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:method) do
|
9
|
+
:dummy1
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:caller) do
|
13
|
+
Kojak::Caller.new(obj.method(method))
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "initialize" do
|
17
|
+
it "should gather base information about the method" do
|
18
|
+
caller[:name].must_equal "#{obj.class.name}##{method}"
|
19
|
+
caller[:method].must_equal obj.method(method)
|
20
|
+
caller[:location].must_equal obj.method(method).source_location
|
21
|
+
caller[:pid].must_equal Process.pid
|
22
|
+
caller[:thread_id].must_equal Thread.object_id
|
23
|
+
caller[:thread].must_equal Thread.current
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "call" do
|
28
|
+
describe "when normal call" do
|
29
|
+
it "should store args" do
|
30
|
+
args = {:foo => 1}
|
31
|
+
caller.call(args)
|
32
|
+
caller[:args].must_equal [args]
|
33
|
+
caller[:block].must_be_nil
|
34
|
+
caller[:block_given].must_equal false
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return same result as method would" do
|
38
|
+
res = caller.call
|
39
|
+
res.must_equal 1
|
40
|
+
caller[:result].must_equal res
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should measure execution time" do
|
44
|
+
res = caller.call
|
45
|
+
caller[:realtime].must_be ">", 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "when block given" do
|
50
|
+
it "should store info about the block" do
|
51
|
+
block = proc {}
|
52
|
+
caller.call(&block)
|
53
|
+
assert_equal caller[:block], block
|
54
|
+
caller[:block_given].must_equal true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when error raised from method" do
|
59
|
+
let(:method) do
|
60
|
+
:dummy2
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should capture it and re raise" do
|
64
|
+
res = nil
|
65
|
+
err = assert_raises ArgumentError do
|
66
|
+
res = caller.call(1,2,3,4)
|
67
|
+
end
|
68
|
+
caller[:err].must_equal err
|
69
|
+
caller[:result].must_be_nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe Kojak::Inspector do
|
4
|
+
let(:cls) do
|
5
|
+
Class.new(Dummy) do
|
6
|
+
extend Kojak::Inspector
|
7
|
+
investigate :dummy1
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:obj) do
|
12
|
+
cls.new
|
13
|
+
end
|
14
|
+
|
15
|
+
before do
|
16
|
+
cls.extend(Kojak::Inspector)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "investigation" do
|
20
|
+
it "should register class in top level" do
|
21
|
+
assert Kojak.investigated.include?(cls)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should redefine investigated methods" do
|
25
|
+
cls.investigate!
|
26
|
+
res = nil
|
27
|
+
out, err = capture_subprocess_io do
|
28
|
+
res = obj.dummy1(:foo => 1)
|
29
|
+
end
|
30
|
+
res.must_equal(1)
|
31
|
+
err.must_match(/Dummy#dummy1/)
|
32
|
+
Kojak.slowlog.last.must_equal("Dummy#dummy1")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe Kojak::Printer::Basic do
|
5
|
+
let(:out) do
|
6
|
+
StringIO.new
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:printer) do
|
10
|
+
Kojak::Printer::Basic.new(out)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:text) do
|
14
|
+
"foo"
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "write" do
|
18
|
+
it "should write given string to the output" do
|
19
|
+
printer.write(text)
|
20
|
+
out.seek(0)
|
21
|
+
out.read.must_equal(text)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "interpolation" do
|
25
|
+
it "should interpolate text" do
|
26
|
+
printer.write("%s", text)
|
27
|
+
out.seek(0)
|
28
|
+
out.read.must_equal(text.inspect)
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "with objects" do
|
32
|
+
let(:obj) do
|
33
|
+
{:foo => 1}
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should pretty print interpolated objects" do
|
37
|
+
printer.write("%s", obj)
|
38
|
+
out.seek(0)
|
39
|
+
out.read.must_equal(JSON.pretty_generate(obj))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "puts" do
|
46
|
+
it "should write given string and newline to the output" do
|
47
|
+
printer.expects(:write).with(text + "\n")
|
48
|
+
printer.puts(text)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe Kojak::Printer::Colorized do
|
5
|
+
let(:out) do
|
6
|
+
StringIO.new
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:printer) do
|
10
|
+
Kojak::Printer::Colorized.new(out, :red)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:text) do
|
14
|
+
"foo"
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "write" do
|
18
|
+
it "should write given string to the output" do
|
19
|
+
printer.write(text)
|
20
|
+
out.seek(0)
|
21
|
+
out.read.must_equal("\e[31m#{text}\e[0m")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "puts" do
|
26
|
+
it "should write given string and newline to the output" do
|
27
|
+
printer.expects(:write).with(text + "\n")
|
28
|
+
printer.puts(text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe Kojak::Printer::Nulled do
|
5
|
+
let(:printer) do
|
6
|
+
Kojak::Printer::Nulled.new
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:text) do
|
10
|
+
"foo"
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "write" do
|
14
|
+
it "should do nothing" do
|
15
|
+
printer.write(text)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "puts" do
|
20
|
+
it "should do nothing" do
|
21
|
+
printer.puts(text)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe Kojak::Printer::RandomlyColorized do
|
5
|
+
let(:out) do
|
6
|
+
StringIO.new
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:printer) do
|
10
|
+
Kojak::Printer::RandomlyColorized.new(out)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should be colorized" do
|
14
|
+
printer.must_be_kind_of Kojak::Printer::Colorized
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should always pick different color" do
|
18
|
+
prev = nil
|
19
|
+
100.times do
|
20
|
+
current = printer.color
|
21
|
+
current.wont_equal prev
|
22
|
+
prev = current
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe Kojak::Printer::Synchronized do
|
5
|
+
let(:out) do
|
6
|
+
StringIO.new
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:printer) do
|
10
|
+
Kojak::Printer::Synchronized.new(out)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:text) do
|
14
|
+
"foo"
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "write" do
|
18
|
+
it "should write given string to the output" do
|
19
|
+
printer.write(text)
|
20
|
+
out.seek(0)
|
21
|
+
out.read.must_equal(text)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "puts" do
|
26
|
+
it "should write given string and newline to the output" do
|
27
|
+
printer.expects(:write).with(text + "\n")
|
28
|
+
printer.puts(text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe Kojak do
|
4
|
+
it "should have correct version number" do
|
5
|
+
Kojak::VERSION.must_equal "0.0.1"
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "investigate!" do
|
9
|
+
it "should run investigation on all registered classes" do
|
10
|
+
dummy = Class.new { extend Kojak::Inspector }
|
11
|
+
dummy.expects(:investigate!)
|
12
|
+
Kojak.investigated.clear
|
13
|
+
Kojak.mark_for_investigation(dummy)
|
14
|
+
Kojak.investigate!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kojak
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kris Kovalik
|
8
|
+
- NIMBOOST Team
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-09-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ~>
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.8'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.8'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rainbow
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.6'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.6'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rake
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '10.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '10.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: mocha
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
description: Kojak allows you to trace and debug method calls.
|
85
|
+
email:
|
86
|
+
- dev@nimboost.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- examples/hello.rb
|
97
|
+
- examples/more_complex.rb
|
98
|
+
- kojak.gemspec
|
99
|
+
- lib/kojak.rb
|
100
|
+
- lib/kojak/caller.rb
|
101
|
+
- lib/kojak/central.rb
|
102
|
+
- lib/kojak/inspector.rb
|
103
|
+
- lib/kojak/output.rb
|
104
|
+
- lib/kojak/printer.rb
|
105
|
+
- lib/kojak/printer/basic.rb
|
106
|
+
- lib/kojak/printer/colorized.rb
|
107
|
+
- lib/kojak/printer/nulled.rb
|
108
|
+
- lib/kojak/printer/randomly_colorized.rb
|
109
|
+
- lib/kojak/printer/synchronized.rb
|
110
|
+
- lib/kojak/version.rb
|
111
|
+
- test/dummies/dummy.rb
|
112
|
+
- test/minitest_helper.rb
|
113
|
+
- test/test_caller.rb
|
114
|
+
- test/test_inspector.rb
|
115
|
+
- test/test_printer/test_basic.rb
|
116
|
+
- test/test_printer/test_colorized.rb
|
117
|
+
- test/test_printer/test_nulled.rb
|
118
|
+
- test/test_printer/test_randomly_colorized.rb
|
119
|
+
- test/test_printer/test_synchronized.rb
|
120
|
+
- test/test_top_level.rb
|
121
|
+
homepage: http://docs.nimboost.com/lib/ruby/kojak
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata: {}
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.0.6
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Kojak is a neat and bald tracer and debug logger.
|
145
|
+
test_files:
|
146
|
+
- test/dummies/dummy.rb
|
147
|
+
- test/minitest_helper.rb
|
148
|
+
- test/test_caller.rb
|
149
|
+
- test/test_inspector.rb
|
150
|
+
- test/test_printer/test_basic.rb
|
151
|
+
- test/test_printer/test_colorized.rb
|
152
|
+
- test/test_printer/test_nulled.rb
|
153
|
+
- test/test_printer/test_randomly_colorized.rb
|
154
|
+
- test/test_printer/test_synchronized.rb
|
155
|
+
- test/test_top_level.rb
|