kojak 0.0.1
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 +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
|