cabin 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,7 +6,8 @@ require "cabin/context"
6
6
  require "cabin/outputs/stdlib-logger"
7
7
  require "cabin/outputs/io"
8
8
  require "cabin/metrics"
9
- require "logger"
9
+ require "logger" # stdlib
10
+ require "thread"
10
11
 
11
12
  # A wonderful channel for logging.
12
13
  #
@@ -46,6 +47,9 @@ require "logger"
46
47
  # I, [2011-10-11T01:00:57.993575 #1209] INFO -- : {:timestamp=>"2011-10-11T01:00:57.993517-0700", :message=>"Done in foo", :level=>:info}
47
48
  #
48
49
  class Cabin::Channel
50
+ @channel_lock = Mutex.new
51
+ @channels = Hash.new { |h,k| h[k] = Cabin::Channel.new }
52
+
49
53
  class << self
50
54
  # Get a channel for a given identifier. If this identifier has never been
51
55
  # used, a new channel is created for it.
@@ -54,10 +58,21 @@ class Cabin::Channel
54
58
  # This is useful for using the same Cabin::Channel across your
55
59
  # entire application.
56
60
  def get(identifier=$0)
57
- @channels ||= Hash.new { |h,k| h[k] = Cabin::Channel.new }
58
- return @channels[identifier]
61
+ return @channel_lock.synchronize { @channels[identifier] }
59
62
  end # def Cabin::Channel.get
60
63
 
64
+ def set(identifier, channel)
65
+ return @channel_lock.synchronize { @channels[identifier] = channel }
66
+ end # def Cabin::Channel.set
67
+
68
+ def each(&block)
69
+ @channel_lock.synchronize do
70
+ @channels.each do |identifier, channel|
71
+ yield identifier, channel
72
+ end
73
+ end
74
+ end # def Cabin::Channel.each
75
+
61
76
  # Get a list of filters included in this class.
62
77
  def filters
63
78
  @filters ||= []
@@ -71,8 +86,8 @@ class Cabin::Channel
71
86
  end
72
87
  end # class << self
73
88
 
74
- include Cabin::Mixins::Logger
75
89
  include Cabin::Mixins::Timestamp
90
+ include Cabin::Mixins::Logger
76
91
  include Cabin::Mixins::Timer
77
92
 
78
93
  # All channels come with a metrics provider.
@@ -83,16 +98,19 @@ class Cabin::Channel
83
98
  # Create a new logging channel.
84
99
  # The default log level is 'info'
85
100
  def initialize
86
- @outputs = []
101
+ @subscribers = {}
87
102
  @data = {}
88
103
  @level = :info
89
104
  @metrics = Cabin::Metrics.new
90
105
  @metrics.channel = self
106
+ @subscriber_lock = Mutex.new
91
107
  end # def initialize
92
108
 
93
109
  # Subscribe a new input
94
110
  # New events will be sent to the subscriber using the '<<' method
95
111
  # foo << event
112
+ #
113
+ # Returns a subscription id you can use later to unsubscribe
96
114
  def subscribe(output)
97
115
  # Wrap ruby stdlib Logger if given.
98
116
  if output.is_a?(::Logger)
@@ -100,10 +118,18 @@ class Cabin::Channel
100
118
  elsif output.is_a?(::IO)
101
119
  output = Cabin::Outputs::IO.new(output)
102
120
  end
103
- @outputs << output
104
- # TODO(sissel): Return a method or object that allows you to easily
105
- # unsubscribe?
121
+ @subscriber_lock.synchronize do
122
+ @subscribers[output.object_id] = output
123
+ end
124
+ return output.object_id
106
125
  end # def subscribe
126
+
127
+ # Unsubscribe. Takes a 'subscription id' as returned by the subscribe method
128
+ def unsubscribe(id)
129
+ @subscriber_lock.synchronize do
130
+ @subscribers.delete(id)
131
+ end
132
+ end # def unsubscribe
107
133
 
108
134
  # Set some contextual map value
109
135
  def []=(key, value)
@@ -129,20 +155,21 @@ class Cabin::Channel
129
155
  # is a string ISO8601 timestamp with microsecond precision.
130
156
  def publish(data)
131
157
  event = {}
132
- event.merge!(@data) # Merge any logger context
158
+ self.class.filters.each do |filter|
159
+ filter.call(event)
160
+ end
133
161
 
134
162
  if data.is_a?(String)
135
163
  event[:message] = data
136
164
  else
137
165
  event.merge!(data)
138
166
  end
167
+ event.merge!(@data) # Merge any logger context
139
168
 
140
- self.class.filters.each do |filter|
141
- filter.call(event)
142
- end
143
-
144
- @outputs.each do |out|
145
- out << event
169
+ @subscriber_lock.synchronize do
170
+ @subscribers.each do |id, output|
171
+ output << event
172
+ end
146
173
  end
147
174
  end # def publish
148
175
 
@@ -158,5 +185,5 @@ class Cabin::Channel
158
185
  return data
159
186
  end # def dataify
160
187
 
161
- public(:initialize, :context, :subscribe, :[]=, :[], :remove, :publish, :time, :context)
188
+ public(:initialize, :context, :subscribe, :unsubscribe, :[]=, :[], :remove, :publish, :time, :context)
162
189
  end # class Cabin::Channel
@@ -93,7 +93,7 @@ module Cabin::Mixins::Logger
93
93
  data[:exception] = message.class
94
94
  data[:backtrace] = message.backtrace
95
95
  else
96
- data[:message] = message
96
+ data = { :message => message }.merge(data)
97
97
  end
98
98
 
99
99
  # Add extra debugging bits (file, line, method) if level is debug.
@@ -1,5 +1,6 @@
1
1
  require "cabin"
2
2
  require "json"
3
+ require "thread"
3
4
 
4
5
  # Wrap IO objects with a reasonable log output.
5
6
  #
@@ -42,14 +43,17 @@ class Cabin::Outputs::IO
42
43
 
43
44
  def initialize(io)
44
45
  @io = io
46
+ @lock = Mutex.new
45
47
  end # def initialize
46
48
 
47
49
  # Receive an event
48
50
  def <<(event)
49
- if !@io.tty?
50
- @io.puts(event.to_json)
51
- else
52
- tty_write(event)
51
+ @lock.synchronize do
52
+ if !@io.tty?
53
+ @io.puts(event.to_json)
54
+ else
55
+ tty_write(event)
56
+ end
53
57
  end
54
58
  end # def <<
55
59
 
@@ -56,12 +56,11 @@ describe Cabin::Channel do
56
56
  assert(event[:duration].is_a?(Numeric))
57
57
  end
58
58
 
59
- test "double subscription" do
59
+ test "double subscription should still only subscribe once" do
60
60
  @logger.subscribe(@target)
61
61
  @logger.publish("Hello world")
62
- assert_equal(2, @target.data.length)
62
+ assert_equal(1, @target.data.length)
63
63
  assert_equal("Hello world", @target.data[0][:message])
64
- assert_equal("Hello world", @target.data[1][:message])
65
64
  end
66
65
 
67
66
  test "context values" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cabin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-09 00:00:00.000000000 Z
12
+ date: 2012-03-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &17116580 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,12 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *17116580
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  description: This is an experiment to try and make logging more flexible and more
26
31
  consumable. Plain text logs are bullshit, let's emit structured and contextual logs.
27
32
  Metrics, too!
@@ -88,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
93
  version: '0'
89
94
  requirements: []
90
95
  rubyforge_project:
91
- rubygems_version: 1.8.16
96
+ rubygems_version: 1.8.18
92
97
  signing_key:
93
98
  specification_version: 3
94
99
  summary: Experiments in structured and contextual logging