cabin 0.4.4 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rubygems-cabin-test +0 -0
- data/lib/cabin/metrics.rb +7 -2
- data/lib/cabin/outputs/em/stdlib-logger.rb +2 -5
- data/lib/cabin/outputs/io.rb +5 -6
- data/lib/cabin/outputs/stdlib-logger.rb +3 -3
- data/lib/cabin/outputs/zeromq.rb +96 -0
- data/test/all.rb +4 -0
- data/test/test_zeromq.rb +99 -0
- metadata +82 -64
data/bin/rubygems-cabin-test
CHANGED
File without changes
|
data/lib/cabin/metrics.rb
CHANGED
@@ -51,6 +51,7 @@ class Cabin::Metrics
|
|
51
51
|
# Get us a new metrics container.
|
52
52
|
public
|
53
53
|
def initialize
|
54
|
+
@metrics_lock = Mutex.new
|
54
55
|
@metrics = {}
|
55
56
|
end # def initialize
|
56
57
|
|
@@ -76,7 +77,9 @@ class Cabin::Metrics
|
|
76
77
|
if @channel
|
77
78
|
@channel.debug("Created metric", :instance => instance, :type => metric_object.class)
|
78
79
|
end
|
79
|
-
return @
|
80
|
+
return @metrics_lock.synchronize do
|
81
|
+
@metrics[metric_name] = metric_object
|
82
|
+
end
|
80
83
|
end # def create
|
81
84
|
|
82
85
|
# Create a new Counter metric
|
@@ -114,6 +117,8 @@ class Cabin::Metrics
|
|
114
117
|
# iterate over each metric. yields identifer, metric
|
115
118
|
def each(&block)
|
116
119
|
# delegate to the @metrics hash until we need something fancier
|
117
|
-
@
|
120
|
+
@metrics_lock.synchronize do
|
121
|
+
@metrics.each(&block)
|
122
|
+
end
|
118
123
|
end # def each
|
119
124
|
end # class Cabin::Metrics
|
@@ -1,11 +1,8 @@
|
|
1
1
|
require "cabin"
|
2
|
-
require "json"
|
3
2
|
require "eventmachine"
|
4
3
|
|
5
4
|
# Wrap Ruby stdlib's logger and make it EventMachine friendly. This
|
6
|
-
# allows you to output to a normal ruby logger with Cabin.
|
7
|
-
# Ruby's Logger has a love for strings alone, this wrapper will
|
8
|
-
# convert the data/event to json before sending it to Logger.
|
5
|
+
# allows you to output to a normal ruby logger with Cabin.
|
9
6
|
class Cabin::Outputs::EM::StdlibLogger
|
10
7
|
public
|
11
8
|
def initialize(logger)
|
@@ -33,7 +30,7 @@ class Cabin::Outputs::EM::StdlibLogger
|
|
33
30
|
def <<(data)
|
34
31
|
line = Hash.new
|
35
32
|
line[:method] = data[:level] || "info"
|
36
|
-
line[:message] = "#{data[:message]} #{data.
|
33
|
+
line[:message] = "#{data[:message]} #{data.inspect}"
|
37
34
|
if EM::reactor_running?
|
38
35
|
# Push line onto queue for later sending
|
39
36
|
@logger_queue.push(line)
|
data/lib/cabin/outputs/io.rb
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
require "cabin"
|
2
|
-
require "json"
|
3
2
|
require "thread"
|
4
3
|
|
5
4
|
# Wrap IO objects with a reasonable log output.
|
6
5
|
#
|
7
6
|
# If the IO is *not* attached to a tty (io#tty? returns false), then
|
8
|
-
# the event will be written in
|
7
|
+
# the event will be written in ruby inspect format terminated by a newline:
|
9
8
|
#
|
10
|
-
# { "timestamp"
|
9
|
+
# { "timestamp" => ..., "message" => message, ... }
|
11
10
|
#
|
12
11
|
# If the IO is attached to a TTY, there are # human-friendly in this format:
|
13
12
|
#
|
14
|
-
# message {
|
13
|
+
# message { event data }
|
15
14
|
#
|
16
15
|
# Additionally, colorized output may be available. If the event has :level,
|
17
16
|
# :color, or :bold. Any of the Cabin::Mixins::Logger methods (info, error, etc)
|
@@ -50,7 +49,7 @@ class Cabin::Outputs::IO
|
|
50
49
|
def <<(event)
|
51
50
|
@lock.synchronize do
|
52
51
|
if !@io.tty?
|
53
|
-
@io.puts(event.
|
52
|
+
@io.puts(event.inspect)
|
54
53
|
else
|
55
54
|
tty_write(event)
|
56
55
|
end
|
@@ -77,7 +76,7 @@ class Cabin::Outputs::IO
|
|
77
76
|
if data.empty?
|
78
77
|
message = [event[:message]]
|
79
78
|
else
|
80
|
-
message = ["#{event[:message]} #{data.
|
79
|
+
message = ["#{event[:message]} #{data.inspect}"]
|
81
80
|
end
|
82
81
|
message.unshift("\e[#{CODEMAP[color.to_sym]}m") if !color.nil?
|
83
82
|
message.unshift("\e[#{CODEMAP[bold]}m") if !bold.nil?
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require "cabin"
|
2
|
-
require "json"
|
3
2
|
|
4
3
|
# Wrap Ruby stdlib's logger. This allows you to output to a normal ruby logger
|
5
4
|
# with Cabin. Since Ruby's Logger has a love for strings alone, this
|
6
|
-
# wrapper will convert the data/event to
|
5
|
+
# wrapper will convert the data/event to ruby inspect format before sending it
|
6
|
+
# to Logger.
|
7
7
|
class Cabin::Outputs::StdlibLogger
|
8
8
|
public
|
9
9
|
def initialize(logger)
|
@@ -24,7 +24,7 @@ class Cabin::Outputs::StdlibLogger
|
|
24
24
|
# delete things from the 'data' portion that's not really data.
|
25
25
|
data.delete(:message)
|
26
26
|
data.delete(:timestamp)
|
27
|
-
message = "#{event[:message]} #{data.
|
27
|
+
message = "#{event[:message]} #{data.inspect}"
|
28
28
|
|
29
29
|
#p [@logger.level, logger.class::DEBUG]
|
30
30
|
# This will call @logger.info(data) or something similar.
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'cabin'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
|
4
|
+
# Output to a zeromq socket.
|
5
|
+
class Cabin::Outputs::ZeroMQ
|
6
|
+
DEFAULTS = {
|
7
|
+
:topology => "pushpull",
|
8
|
+
:hwm => 0, # zeromq default: no limit
|
9
|
+
:linger => -1, # zeromq default: wait until all messages are sent.
|
10
|
+
:topic => ""
|
11
|
+
}
|
12
|
+
|
13
|
+
CONTEXT = ZMQ::Context.new
|
14
|
+
|
15
|
+
attr_reader :socket, :topology, :topic
|
16
|
+
|
17
|
+
# Create a new ZeroMQ output.
|
18
|
+
#
|
19
|
+
# arguments:
|
20
|
+
# addresses A list of addresses to connect to. These are round-robined by zeromq.
|
21
|
+
#
|
22
|
+
# :topology Either 'pushpull' or 'pubsub'. Specifies which zeromq socket type to use. Default pushpull.
|
23
|
+
# :hwm Specifies the High Water Mark for the socket. Default 0, which means there is none.
|
24
|
+
# :linger Specifies the linger time in milliseconds for the socket. Default -1, meaning wait forever for the socket to close.
|
25
|
+
# :topic Specifies the topic for a pubsub topology. This can be a string or a proc with the event as the only argument.
|
26
|
+
def initialize(addresses, options={})
|
27
|
+
options = DEFAULTS.merge(options)
|
28
|
+
|
29
|
+
@topology = options[:topology].to_s
|
30
|
+
case @topology
|
31
|
+
when "pushpull"
|
32
|
+
socket_type = ZMQ::PUSH
|
33
|
+
when "pubsub"
|
34
|
+
socket_type = ZMQ::PUB
|
35
|
+
end
|
36
|
+
|
37
|
+
@topic = options[:topic]
|
38
|
+
@socket = CONTEXT.socket(socket_type)
|
39
|
+
|
40
|
+
Array(addresses).each do |address|
|
41
|
+
error_check @socket.connect(address), "connecting to #{address}"
|
42
|
+
end
|
43
|
+
|
44
|
+
error_check @socket.setsockopt(ZMQ::LINGER, options[:linger]), "while setting ZMQ::LINGER to #{options[:linger]}"
|
45
|
+
error_check @socket.setsockopt(ZMQ::HWM, options[:hwm]), "while setting ZMQ::HWM to #{options[:hwm]}"
|
46
|
+
|
47
|
+
#TODO use cabin's teardown when it exists
|
48
|
+
at_exit do
|
49
|
+
teardown
|
50
|
+
end
|
51
|
+
|
52
|
+
#define_finalizer
|
53
|
+
end
|
54
|
+
|
55
|
+
def linger
|
56
|
+
array = []
|
57
|
+
error_check @socket.getsockopt(ZMQ::LINGER, array), "while getting ZMQ::LINGER"
|
58
|
+
array.first
|
59
|
+
end
|
60
|
+
|
61
|
+
def hwm
|
62
|
+
array = []
|
63
|
+
error_check @socket.getsockopt(ZMQ::HWM, array), "while getting ZMQ::HWM"
|
64
|
+
array.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def <<(event)
|
68
|
+
if @socket.name == "PUB"
|
69
|
+
topic = @topic.is_a?(Proc) ? @topic.call(event) : @topic
|
70
|
+
error_check @socket.send_string(topic, ZMQ::SNDMORE), "in topic send_string"
|
71
|
+
end
|
72
|
+
error_check @socket.send_string(event.inspect), "in send_string"
|
73
|
+
end
|
74
|
+
|
75
|
+
def teardown
|
76
|
+
@socket.close if @socket
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def error_check(rc, doing)
|
81
|
+
unless ZMQ::Util.resultcode_ok?(rc)
|
82
|
+
raise "ZeroMQ Error while #{doing}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# This causes the following message on exit:
|
87
|
+
# File exists (epoll.cpp:69)
|
88
|
+
# [1] 26175 abort bundle exec irb
|
89
|
+
# def define_finalizer
|
90
|
+
# ObjectSpace.define_finalizer(self, self.class.finalize(@socket))
|
91
|
+
# end
|
92
|
+
|
93
|
+
# def self.finalize(socket)
|
94
|
+
# Proc.new { puts "finalizing"; socket.close unless socket.nil?; puts "done" }
|
95
|
+
# end
|
96
|
+
end
|
data/test/all.rb
CHANGED
@@ -9,5 +9,9 @@ SimpleCov.start
|
|
9
9
|
dir = File.dirname(File.expand_path(__FILE__))
|
10
10
|
Dir.glob(File.join(dir, "**", "test_*.rb")).each do |path|
|
11
11
|
puts "Loading tests from #{path}"
|
12
|
+
if path =~ /test_zeromq/
|
13
|
+
puts "Skipping zeromq tests because they force ruby to exit if libzmq is not found"
|
14
|
+
next
|
15
|
+
end
|
12
16
|
require path
|
13
17
|
end
|
data/test/test_zeromq.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
$: << File.dirname(__FILE__)
|
2
|
+
$: << File.join(File.dirname(__FILE__), "..", "lib")
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require "minitest-patch"
|
6
|
+
require "cabin/outputs/zeromq"
|
7
|
+
require "minitest/autorun" if __FILE__ == $0
|
8
|
+
|
9
|
+
describe Cabin::Outputs::ZeroMQ do
|
10
|
+
|
11
|
+
def error_check(rc, doing)
|
12
|
+
unless ZMQ::Util.resultcode_ok?(rc)
|
13
|
+
raise "ZeroMQ Error while #{doing}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
NonBlockingFlag = (ZMQ::LibZMQ.version2? ? ZMQ::NOBLOCK : ZMQ::DONTWAIT) unless defined?(NonBlockingFlag)
|
18
|
+
def receive(socket)
|
19
|
+
received = ""
|
20
|
+
error_check socket.recv_string(received, NonBlockingFlag), "receiving"
|
21
|
+
received
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
@logger = Cabin::Channel.new
|
26
|
+
@address = "inproc://zeromq-output"
|
27
|
+
@pull = Cabin::Outputs::ZeroMQ::CONTEXT.socket(ZMQ::PULL)
|
28
|
+
@sub = Cabin::Outputs::ZeroMQ::CONTEXT.socket(ZMQ::SUB)
|
29
|
+
end
|
30
|
+
|
31
|
+
after do
|
32
|
+
@pull.close
|
33
|
+
@sub.close
|
34
|
+
@output.teardown if @output
|
35
|
+
end
|
36
|
+
|
37
|
+
test 'push messages' do
|
38
|
+
@pull.bind(@address); sleep 0.1 # sleeps are necessary for inproc transport
|
39
|
+
@output = Cabin::Outputs::ZeroMQ.new(@address)
|
40
|
+
@logger.subscribe(@output)
|
41
|
+
@logger.info("hello")
|
42
|
+
@logger.info("hello2")
|
43
|
+
assert_equal "hello", JSON.parse(receive(@pull))['message']
|
44
|
+
assert_equal "hello2", JSON.parse(receive(@pull))['message']
|
45
|
+
end
|
46
|
+
|
47
|
+
test "pub messages" do
|
48
|
+
@sub.bind(@address); sleep 0.1
|
49
|
+
error_check @sub.setsockopt(ZMQ::SUBSCRIBE, ""), "subscribing"
|
50
|
+
@output = Cabin::Outputs::ZeroMQ.new(@address, :topology => "pubsub")
|
51
|
+
@logger.subscribe(@output)
|
52
|
+
@logger.info("hi")
|
53
|
+
assert_equal "", receive(@sub)
|
54
|
+
assert_equal "hi", JSON.parse(receive(@sub))['message']
|
55
|
+
end
|
56
|
+
|
57
|
+
test "pub messages on a topic" do
|
58
|
+
@sub.bind(@address); sleep 0.1
|
59
|
+
error_check @sub.setsockopt(ZMQ::SUBSCRIBE, "topic"), "subscribing"
|
60
|
+
@output = Cabin::Outputs::ZeroMQ.new(@address, :topology => "pubsub", :topic => "topic")
|
61
|
+
@logger.subscribe(@output)
|
62
|
+
@logger.info("hi")
|
63
|
+
assert_equal "topic", receive(@sub)
|
64
|
+
assert_equal "hi", JSON.parse(receive(@sub))['message']
|
65
|
+
end
|
66
|
+
|
67
|
+
test "topic proc" do
|
68
|
+
@sub.bind(@address); sleep 0.1
|
69
|
+
error_check @sub.setsockopt(ZMQ::SUBSCRIBE, "topic2"), "subscribing"
|
70
|
+
@output = Cabin::Outputs::ZeroMQ.new(@address, :topology => "pubsub", :topic => Proc.new { |event| event[:message] })
|
71
|
+
@logger.subscribe(@output)
|
72
|
+
@logger.info("topic1")
|
73
|
+
@logger.info("topic2")
|
74
|
+
assert_equal "topic2", receive(@sub)
|
75
|
+
assert_equal "topic2", JSON.parse(receive(@sub))['message']
|
76
|
+
end
|
77
|
+
|
78
|
+
test "multiple addresses" do
|
79
|
+
@pull.bind(@address); sleep 0.1
|
80
|
+
@pull2 = Cabin::Outputs::ZeroMQ::CONTEXT.socket(ZMQ::PULL)
|
81
|
+
@pull2.bind(@address.succ); sleep 0.1
|
82
|
+
|
83
|
+
@output = Cabin::Outputs::ZeroMQ.new([@address, @address.succ])
|
84
|
+
@logger.subscribe(@output)
|
85
|
+
@logger.info("yo")
|
86
|
+
@logger.info("yo")
|
87
|
+
|
88
|
+
assert_equal "yo", JSON.parse(receive(@pull))['message']
|
89
|
+
assert_equal "yo", JSON.parse(receive(@pull2))['message']
|
90
|
+
end
|
91
|
+
|
92
|
+
test "options" do
|
93
|
+
@pull.bind(@address); sleep 0.1
|
94
|
+
@output = Cabin::Outputs::ZeroMQ.new(@address, :hwm => 10, :linger => 100)
|
95
|
+
|
96
|
+
assert_equal 10, @output.hwm
|
97
|
+
assert_equal 100, @output.linger
|
98
|
+
end
|
99
|
+
end
|
metadata
CHANGED
@@ -1,35 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cabin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 0.5.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jordan Sissel
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
name: json
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ! '>='
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: '0'
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ! '>='
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '0'
|
30
|
-
description: This is an experiment to try and make logging more flexible and more
|
31
|
-
consumable. Plain text logs are bullshit, let's emit structured and contextual logs.
|
32
|
-
Metrics, too!
|
12
|
+
date: 2012-11-09 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: This is an experiment to try and make logging more flexible and more consumable. Plain text logs are bullshit, let's emit structured and contextual logs. Metrics, too!
|
33
15
|
email:
|
34
16
|
- jls@semicomplete.com
|
35
17
|
executables:
|
@@ -37,65 +19,101 @@ executables:
|
|
37
19
|
extensions: []
|
38
20
|
extra_rdoc_files: []
|
39
21
|
files:
|
40
|
-
-
|
41
|
-
|
42
|
-
-
|
43
|
-
|
44
|
-
-
|
45
|
-
|
46
|
-
-
|
47
|
-
|
48
|
-
-
|
49
|
-
|
50
|
-
-
|
51
|
-
|
52
|
-
-
|
53
|
-
|
54
|
-
-
|
55
|
-
|
56
|
-
-
|
57
|
-
|
58
|
-
-
|
59
|
-
|
60
|
-
-
|
61
|
-
|
62
|
-
-
|
63
|
-
|
64
|
-
-
|
65
|
-
|
66
|
-
-
|
67
|
-
|
68
|
-
-
|
69
|
-
|
70
|
-
-
|
22
|
+
- !binary |-
|
23
|
+
bGliL2NhYmluLnJi
|
24
|
+
- !binary |-
|
25
|
+
bGliL2NhYmluL2NoYW5uZWwucmI=
|
26
|
+
- !binary |-
|
27
|
+
bGliL2NhYmluL2NvbnRleHQucmI=
|
28
|
+
- !binary |-
|
29
|
+
bGliL2NhYmluL2luc3BlY3RhYmxlLnJi
|
30
|
+
- !binary |-
|
31
|
+
bGliL2NhYmluL21ldHJpYy5yYg==
|
32
|
+
- !binary |-
|
33
|
+
bGliL2NhYmluL21ldHJpY3MucmI=
|
34
|
+
- !binary |-
|
35
|
+
bGliL2NhYmluL25hbWVzcGFjZS5yYg==
|
36
|
+
- !binary |-
|
37
|
+
bGliL2NhYmluL3B1Ymxpc2hlci5yYg==
|
38
|
+
- !binary |-
|
39
|
+
bGliL2NhYmluL3RpbWVyLnJi
|
40
|
+
- !binary |-
|
41
|
+
bGliL2NhYmluL21ldHJpY3MvY291bnRlci5yYg==
|
42
|
+
- !binary |-
|
43
|
+
bGliL2NhYmluL21ldHJpY3MvZ2F1Z2UucmI=
|
44
|
+
- !binary |-
|
45
|
+
bGliL2NhYmluL21ldHJpY3MvaGlzdG9ncmFtLnJi
|
46
|
+
- !binary |-
|
47
|
+
bGliL2NhYmluL21ldHJpY3MvbWV0ZXIucmI=
|
48
|
+
- !binary |-
|
49
|
+
bGliL2NhYmluL21ldHJpY3MvdGltZXIucmI=
|
50
|
+
- !binary |-
|
51
|
+
bGliL2NhYmluL21peGlucy9DQVBTTE9DSy5yYg==
|
52
|
+
- !binary |-
|
53
|
+
bGliL2NhYmluL21peGlucy9jb2xvcnMucmI=
|
54
|
+
- !binary |-
|
55
|
+
bGliL2NhYmluL21peGlucy9kcmFnb25zLnJi
|
56
|
+
- !binary |-
|
57
|
+
bGliL2NhYmluL21peGlucy9sb2dnZXIucmI=
|
58
|
+
- !binary |-
|
59
|
+
bGliL2NhYmluL21peGlucy90aW1lci5yYg==
|
60
|
+
- !binary |-
|
61
|
+
bGliL2NhYmluL21peGlucy90aW1lc3RhbXAucmI=
|
62
|
+
- !binary |-
|
63
|
+
bGliL2NhYmluL291dHB1dHMvaW8ucmI=
|
64
|
+
- !binary |-
|
65
|
+
bGliL2NhYmluL291dHB1dHMvc3RkbGliLWxvZ2dlci5yYg==
|
66
|
+
- !binary |-
|
67
|
+
bGliL2NhYmluL291dHB1dHMvemVyb21xLnJi
|
68
|
+
- !binary |-
|
69
|
+
bGliL2NhYmluL291dHB1dHMvZW0vc3RkbGliLWxvZ2dlci5yYg==
|
70
|
+
- !binary |-
|
71
|
+
ZXhhbXBsZXMvZmlib25hY2NpLXRpbWluZy5yYg==
|
72
|
+
- !binary |-
|
73
|
+
ZXhhbXBsZXMvbWV0cmljcy5yYg==
|
74
|
+
- !binary |-
|
75
|
+
ZXhhbXBsZXMvc2FtcGxlLnJi
|
76
|
+
- !binary |-
|
77
|
+
ZXhhbXBsZXMvc2luYXRyYS1sb2dnaW5nLnJi
|
78
|
+
- !binary |-
|
79
|
+
dGVzdC9hbGwucmI=
|
80
|
+
- !binary |-
|
81
|
+
dGVzdC9taW5pdGVzdC1wYXRjaC5yYg==
|
82
|
+
- !binary |-
|
83
|
+
dGVzdC90ZXN0X2xvZ2dpbmcucmI=
|
84
|
+
- !binary |-
|
85
|
+
dGVzdC90ZXN0X21ldHJpY3MucmI=
|
86
|
+
- !binary |-
|
87
|
+
dGVzdC90ZXN0X3plcm9tcS5yYg==
|
71
88
|
- LICENSE
|
72
89
|
- CHANGELIST
|
73
90
|
- bin/rubygems-cabin-test
|
74
91
|
homepage: https://github.com/jordansissel/ruby-cabin
|
75
92
|
licenses:
|
76
93
|
- Apache License (2.0)
|
77
|
-
post_install_message:
|
94
|
+
post_install_message:
|
78
95
|
rdoc_options: []
|
79
96
|
require_paths:
|
80
97
|
- lib
|
81
98
|
- lib
|
82
99
|
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
-
none: false
|
84
100
|
requirements:
|
85
101
|
- - ! '>='
|
86
102
|
- !ruby/object:Gem::Version
|
87
|
-
version:
|
88
|
-
|
103
|
+
version: !binary |-
|
104
|
+
MA==
|
89
105
|
none: false
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
107
|
requirements:
|
91
108
|
- - ! '>='
|
92
109
|
- !ruby/object:Gem::Version
|
93
|
-
version:
|
110
|
+
version: !binary |-
|
111
|
+
MA==
|
112
|
+
none: false
|
94
113
|
requirements: []
|
95
|
-
rubyforge_project:
|
96
|
-
rubygems_version: 1.8.
|
97
|
-
signing_key:
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.8.24
|
116
|
+
signing_key:
|
98
117
|
specification_version: 3
|
99
118
|
summary: Experiments in structured and contextual logging
|
100
119
|
test_files: []
|
101
|
-
has_rdoc:
|