cabin 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -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 @metrics[metric_name] = metric_object
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
- @metrics.each(&block)
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. Since
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.to_json}"
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)
@@ -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 json format terminated by a newline:
7
+ # the event will be written in ruby inspect format terminated by a newline:
9
8
  #
10
- # { "timestamp": ..., "message": message, ... }
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 {json data}
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.to_json)
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.to_json}"]
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 json before sending it to Logger.
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.to_json}"
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
@@ -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
@@ -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
- version: 0.4.4
5
- prerelease:
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-03-23 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
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
- - lib/cabin/metrics/histogram.rb
41
- - lib/cabin/metrics/timer.rb
42
- - lib/cabin/metrics/gauge.rb
43
- - lib/cabin/metrics/meter.rb
44
- - lib/cabin/metrics/counter.rb
45
- - lib/cabin/publisher.rb
46
- - lib/cabin/channel.rb
47
- - lib/cabin/mixins/logger.rb
48
- - lib/cabin/mixins/colors.rb
49
- - lib/cabin/mixins/dragons.rb
50
- - lib/cabin/mixins/CAPSLOCK.rb
51
- - lib/cabin/mixins/timer.rb
52
- - lib/cabin/mixins/timestamp.rb
53
- - lib/cabin/timer.rb
54
- - lib/cabin/metric.rb
55
- - lib/cabin/metrics.rb
56
- - lib/cabin/namespace.rb
57
- - lib/cabin/inspectable.rb
58
- - lib/cabin/outputs/io.rb
59
- - lib/cabin/outputs/stdlib-logger.rb
60
- - lib/cabin/outputs/em/stdlib-logger.rb
61
- - lib/cabin/context.rb
62
- - lib/cabin.rb
63
- - examples/fibonacci-timing.rb
64
- - examples/sinatra-logging.rb
65
- - examples/sample.rb
66
- - examples/metrics.rb
67
- - test/test_logging.rb
68
- - test/test_metrics.rb
69
- - test/minitest-patch.rb
70
- - test/all.rb
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: '0'
88
- required_rubygems_version: !ruby/object:Gem::Requirement
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: '0'
110
+ version: !binary |-
111
+ MA==
112
+ none: false
94
113
  requirements: []
95
- rubyforge_project:
96
- rubygems_version: 1.8.18
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: