celluloid 0.18.0.pre → 0.18.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.
Files changed (177) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES.md +258 -39
  3. data/CONDUCT.md +13 -0
  4. data/CONTRIBUTING.md +39 -0
  5. data/README.md +54 -165
  6. data/REFACTOR.md +1 -0
  7. data/architecture.md +120 -0
  8. data/examples/basic_usage.rb +1 -1
  9. data/examples/configurations.rb +78 -0
  10. data/examples/futures.rb +1 -1
  11. data/examples/ring.rb +5 -4
  12. data/examples/simple_pmap.rb +1 -1
  13. data/examples/stack.rb +2 -2
  14. data/examples/supervisors_and_registry.rb +82 -0
  15. data/examples/timers.rb +2 -2
  16. data/lib/celluloid.rb +72 -47
  17. data/lib/celluloid/actor.rb +27 -17
  18. data/lib/celluloid/actor/system.rb +13 -29
  19. data/lib/celluloid/autostart.rb +5 -5
  20. data/lib/celluloid/call/async.rb +2 -0
  21. data/lib/celluloid/call/sync.rb +10 -3
  22. data/lib/celluloid/calls.rb +5 -12
  23. data/lib/celluloid/cell.rb +5 -9
  24. data/lib/celluloid/condition.rb +3 -3
  25. data/lib/celluloid/core_ext.rb +0 -2
  26. data/lib/celluloid/debug.rb +3 -0
  27. data/lib/celluloid/exceptions.rb +2 -2
  28. data/lib/celluloid/future.rb +7 -9
  29. data/lib/celluloid/group.rb +12 -8
  30. data/lib/celluloid/group/pool.rb +1 -3
  31. data/lib/celluloid/group/spawner.rb +2 -6
  32. data/lib/celluloid/internals/call_chain.rb +15 -0
  33. data/lib/celluloid/internals/cpu_counter.rb +62 -0
  34. data/lib/celluloid/internals/handlers.rb +42 -0
  35. data/lib/celluloid/internals/links.rb +38 -0
  36. data/lib/celluloid/internals/logger.rb +104 -0
  37. data/lib/celluloid/internals/method.rb +34 -0
  38. data/lib/celluloid/internals/properties.rb +32 -0
  39. data/lib/celluloid/internals/receivers.rb +64 -0
  40. data/lib/celluloid/internals/registry.rb +102 -0
  41. data/lib/celluloid/internals/responses.rb +46 -0
  42. data/lib/celluloid/internals/signals.rb +24 -0
  43. data/lib/celluloid/internals/stack.rb +74 -0
  44. data/lib/celluloid/internals/stack/dump.rb +12 -0
  45. data/lib/celluloid/internals/stack/states.rb +72 -0
  46. data/lib/celluloid/internals/stack/summary.rb +12 -0
  47. data/lib/celluloid/internals/task_set.rb +51 -0
  48. data/lib/celluloid/internals/thread_handle.rb +52 -0
  49. data/lib/celluloid/internals/uuid.rb +40 -0
  50. data/lib/celluloid/logging/incident.rb +21 -0
  51. data/lib/celluloid/logging/incident_logger.rb +147 -0
  52. data/lib/celluloid/logging/incident_reporter.rb +49 -0
  53. data/lib/celluloid/logging/log_event.rb +20 -0
  54. data/lib/celluloid/logging/ring_buffer.rb +64 -0
  55. data/lib/celluloid/mailbox.rb +22 -9
  56. data/lib/celluloid/mailbox/evented.rb +13 -5
  57. data/lib/celluloid/notifications.rb +95 -0
  58. data/lib/celluloid/pool.rb +6 -0
  59. data/lib/celluloid/probe.rb +81 -0
  60. data/lib/celluloid/proxy/abstract.rb +9 -9
  61. data/lib/celluloid/proxy/async.rb +1 -1
  62. data/lib/celluloid/proxy/block.rb +2 -2
  63. data/lib/celluloid/proxy/cell.rb +1 -1
  64. data/lib/celluloid/proxy/future.rb +2 -4
  65. data/lib/celluloid/proxy/sync.rb +1 -3
  66. data/lib/celluloid/rspec.rb +22 -33
  67. data/lib/celluloid/supervision.rb +17 -0
  68. data/lib/celluloid/supervision/configuration.rb +169 -0
  69. data/lib/celluloid/supervision/configuration/injections.rb +8 -0
  70. data/lib/celluloid/supervision/configuration/instance.rb +113 -0
  71. data/lib/celluloid/supervision/constants.rb +123 -0
  72. data/lib/celluloid/supervision/container.rb +144 -0
  73. data/lib/celluloid/supervision/container/behavior.rb +89 -0
  74. data/lib/celluloid/supervision/container/behavior/pool.rb +71 -0
  75. data/lib/celluloid/supervision/container/behavior/tree.rb +23 -0
  76. data/lib/celluloid/supervision/container/injections.rb +8 -0
  77. data/lib/celluloid/supervision/container/instance.rb +116 -0
  78. data/lib/celluloid/supervision/container/pool.rb +210 -0
  79. data/lib/celluloid/supervision/service.rb +27 -0
  80. data/lib/celluloid/supervision/supervise.rb +34 -0
  81. data/lib/celluloid/supervision/validation.rb +40 -0
  82. data/lib/celluloid/supervision/version.rb +5 -0
  83. data/lib/celluloid/system_events.rb +11 -6
  84. data/lib/celluloid/task.rb +25 -12
  85. data/lib/celluloid/task/fibered.rb +2 -0
  86. data/lib/celluloid/task/threaded.rb +3 -3
  87. data/lib/celluloid/test.rb +5 -2
  88. data/lib/celluloid/thread.rb +0 -2
  89. data/lib/celluloid/version.rb +1 -1
  90. data/spec/celluloid/block_spec.rb +29 -32
  91. data/spec/celluloid/calls_spec.rb +5 -15
  92. data/spec/celluloid/future_spec.rb +2 -2
  93. data/spec/celluloid/internals/cpu_counter_spec.rb +129 -0
  94. data/spec/celluloid/internals/links_spec.rb +43 -0
  95. data/spec/celluloid/internals/properties_spec.rb +40 -0
  96. data/spec/celluloid/internals/registry_spec.rb +62 -0
  97. data/spec/celluloid/internals/stack/dump_spec.rb +4 -0
  98. data/spec/celluloid/internals/stack/summary_spec.rb +4 -0
  99. data/spec/celluloid/internals/thread_handle_spec.rb +60 -0
  100. data/spec/celluloid/internals/uuid_spec.rb +9 -0
  101. data/spec/celluloid/logging/ring_buffer_spec.rb +36 -0
  102. data/spec/celluloid/mailbox/evented_spec.rb +11 -22
  103. data/spec/celluloid/misc/leak_spec.rb +3 -4
  104. data/spec/celluloid/notifications_spec.rb +140 -0
  105. data/spec/celluloid/probe_spec.rb +102 -0
  106. data/spec/celluloid/proxy_spec.rb +30 -30
  107. data/spec/celluloid/supervision/behavior_spec.rb +74 -0
  108. data/spec/celluloid/supervision/configuration_spec.rb +181 -0
  109. data/spec/celluloid/supervision/container_spec.rb +72 -0
  110. data/spec/celluloid/supervision/instance_spec.rb +13 -0
  111. data/spec/celluloid/supervision/root_spec.rb +28 -0
  112. data/spec/celluloid/supervision/supervisor_spec.rb +93 -0
  113. data/spec/celluloid/task/fibered_spec.rb +1 -3
  114. data/spec/celluloid/task/threaded_spec.rb +1 -3
  115. data/spec/shared/actor_examples.rb +58 -33
  116. data/spec/shared/group_examples.rb +2 -2
  117. data/spec/shared/mailbox_examples.rb +1 -1
  118. data/spec/shared/stack_examples.rb +87 -0
  119. data/spec/shared/task_examples.rb +2 -3
  120. data/spec/spec_helper.rb +2 -4
  121. data/spec/support/configure_rspec.rb +2 -3
  122. data/spec/support/coverage.rb +2 -4
  123. data/spec/support/crash_checking.rb +2 -2
  124. data/spec/support/examples/actor_class.rb +3 -8
  125. data/spec/support/examples/call_class.rb +2 -2
  126. data/spec/support/examples/container_class.rb +35 -0
  127. data/spec/support/examples/evented_mailbox_class.rb +1 -2
  128. data/spec/support/examples/stack_classes.rb +58 -0
  129. data/spec/support/examples/stack_methods.rb +23 -0
  130. data/spec/support/examples/subordinate_class.rb +19 -0
  131. data/spec/support/logging.rb +2 -34
  132. data/spec/support/loose_threads.rb +3 -16
  133. data/spec/support/reset_class_variables.rb +5 -1
  134. data/spec/support/stubbing.rb +1 -1
  135. metadata +91 -323
  136. data/culture/CONDUCT.md +0 -38
  137. data/culture/GSoC/1010-why_we_will_participate.md +0 -17
  138. data/culture/GSoC/1020-how_mentors_stay_engaged.md +0 -7
  139. data/culture/GSoC/1030-keeping_students_on_schedule.md +0 -9
  140. data/culture/GSoC/1040-getting_students_involved.md +0 -5
  141. data/culture/GSoC/1050-student_involvement_after.md +0 -5
  142. data/culture/GSoC/README.md +0 -16
  143. data/culture/Gemfile +0 -9
  144. data/culture/LICENSE.txt +0 -22
  145. data/culture/README.md +0 -22
  146. data/culture/Rakefile +0 -5
  147. data/culture/SYNC.md +0 -70
  148. data/culture/celluloid-culture.gemspec +0 -18
  149. data/culture/gems/README.md +0 -39
  150. data/culture/gems/dependencies.yml +0 -93
  151. data/culture/gems/loader.rb +0 -101
  152. data/culture/rubocop/README.md +0 -38
  153. data/culture/rubocop/lint.yml +0 -8
  154. data/culture/rubocop/metrics.yml +0 -15
  155. data/culture/rubocop/perf.yml +0 -0
  156. data/culture/rubocop/rubocop.yml +0 -5
  157. data/culture/rubocop/style.yml +0 -61
  158. data/culture/spec/gems_spec.rb +0 -2
  159. data/culture/spec/spec_helper.rb +0 -0
  160. data/culture/spec/sync_spec.rb +0 -2
  161. data/culture/sync.rb +0 -56
  162. data/culture/tasks/rspec.rake +0 -5
  163. data/culture/tasks/rubocop.rake +0 -2
  164. data/lib/celluloid/actor/manager.rb +0 -7
  165. data/lib/celluloid/backported.rb +0 -2
  166. data/lib/celluloid/current.rb +0 -2
  167. data/lib/celluloid/deprecate.rb +0 -34
  168. data/lib/celluloid/fiber.rb +0 -32
  169. data/lib/celluloid/managed.rb +0 -3
  170. data/lib/celluloid/notices.rb +0 -15
  171. data/spec/deprecate/actor_system_spec.rb +0 -72
  172. data/spec/deprecate/block_spec.rb +0 -52
  173. data/spec/deprecate/calls_spec.rb +0 -39
  174. data/spec/deprecate/evented_mailbox_spec.rb +0 -34
  175. data/spec/deprecate/future_spec.rb +0 -32
  176. data/spec/deprecate/internal_pool_spec.rb +0 -4
  177. data/spec/support/env.rb +0 -21
@@ -0,0 +1,46 @@
1
+ module Celluloid
2
+ module Internals
3
+ # Responses to calls
4
+ class Response
5
+ attr_reader :call, :value
6
+
7
+ def initialize(call, value)
8
+ @call = call
9
+ @value = value
10
+ end
11
+
12
+ def dispatch
13
+ @call.task.resume self
14
+ end
15
+
16
+ # Call completed successfully
17
+ class Success < Response; end
18
+
19
+ # Call was aborted due to sender error
20
+ class Error < Response
21
+ def value
22
+ ex = super
23
+ ex = ex.cause if ex.is_a? Celluloid::AbortError
24
+
25
+ if ex.backtrace
26
+ ex.backtrace << "(celluloid):0:in `remote procedure call'"
27
+ ex.backtrace.concat(caller)
28
+ end
29
+
30
+ raise ex
31
+ end
32
+ end
33
+
34
+ class Block
35
+ def initialize(call, result)
36
+ @call = call
37
+ @result = result
38
+ end
39
+
40
+ def dispatch
41
+ @call.task.resume(@result)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ module Celluloid
2
+ module Internals
3
+ # Event signaling between methods of the same object
4
+ class Signals
5
+ def initialize
6
+ @conditions = {}
7
+ end
8
+
9
+ # Wait for the given signal and return the associated value
10
+ def wait(name)
11
+ raise "cannot wait for signals while exclusive" if Celluloid.exclusive?
12
+
13
+ @conditions[name] ||= Condition.new
14
+ @conditions[name].wait
15
+ end
16
+
17
+ # Send a signal to all method calls waiting for the given name
18
+ def broadcast(name, value = nil)
19
+ condition = @conditions.delete(name)
20
+ condition.broadcast(value) if condition
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,74 @@
1
+ module Celluloid
2
+ module Internals
3
+ class Stack
4
+ attr_accessor :actors, :threads
5
+
6
+ def initialize(threads)
7
+ @group = threads
8
+ @actors = []
9
+ @threads = []
10
+ end
11
+
12
+ def snapshot(backtrace = nil)
13
+ @group.each do |thread|
14
+ if thread.role == :actor
15
+ @actors << snapshot_actor(thread.actor, backtrace) if thread.actor
16
+ else
17
+ @threads << snapshot_thread(thread, backtrace)
18
+ end
19
+ end
20
+ end
21
+
22
+ def snapshot_actor(actor, backtrace = nil)
23
+ state = ActorState.new
24
+ state.id = actor.object_id
25
+
26
+ # TODO: delegate to the behavior
27
+ state.cell = snapshot_cell(actor.behavior) if actor.behavior.is_a?(Cell)
28
+
29
+ tasks = actor.tasks
30
+ if tasks.empty?
31
+ state.status = :idle
32
+ else
33
+ state.status = :running
34
+ state.tasks = tasks.to_a.map { |t| TaskState.new(t.class, t.type, t.meta, t.status, t.backtrace) }
35
+ end
36
+
37
+ state.backtrace = actor.thread.backtrace if backtrace && actor.thread
38
+ state
39
+ end
40
+
41
+ def snapshot_cell(behavior)
42
+ state = CellState.new
43
+ state.subject_id = behavior.subject.object_id
44
+ state.subject_class = behavior.subject.class
45
+ state
46
+ end
47
+
48
+ def snapshot_thread(thread, backtrace = nil)
49
+ if backtrace
50
+ backtrace = begin
51
+ thread.backtrace
52
+ rescue NoMethodError # for Rubinius < 2.5.2.c145
53
+ []
54
+ end
55
+ end
56
+ ThreadState.new(thread.object_id, backtrace, thread.role)
57
+ end
58
+
59
+ def print(output = STDERR)
60
+ @actors.each do |actor|
61
+ output.print actor.dump
62
+ end
63
+
64
+ @threads.each do |thread|
65
+ output.print thread.dump
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ require "celluloid/internals/stack/states"
73
+ require "celluloid/internals/stack/dump"
74
+ require "celluloid/internals/stack/summary"
@@ -0,0 +1,12 @@
1
+ module Celluloid
2
+ module Internals
3
+ class Stack
4
+ class Dump < Stack
5
+ def initialize(threads)
6
+ super(threads)
7
+ snapshot(true)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,72 @@
1
+ module Celluloid
2
+ module Internals
3
+ class Stack
4
+ module DisplayBacktrace
5
+ def display_backtrace(backtrace, output, indent = nil)
6
+ backtrace ||= ["EMPTY BACKTRACE"]
7
+ backtrace.each do |line|
8
+ output << indent if indent
9
+ output << "\t" << line << "\n"
10
+ end
11
+ output << "\n\n"
12
+ end
13
+ end
14
+
15
+ class TaskState < Struct.new(:task_class, :type, :meta, :status, :backtrace); end
16
+
17
+ class CellState < Struct.new(:subject_id, :subject_class)
18
+ def dump
19
+ "Celluloid::Cell 0x#{subject_id.to_s(16)}: #{subject_class}"
20
+ end
21
+ end
22
+
23
+ class ThreadState < Struct.new(:thread_id, :backtrace, :role)
24
+ include DisplayBacktrace
25
+ def dump
26
+ string = ""
27
+ string << "Thread 0x#{thread_id.to_s(16)} (#{role}):\n"
28
+ display_backtrace backtrace, string if backtrace
29
+ string
30
+ end
31
+ end
32
+
33
+ class ActorState
34
+ include DisplayBacktrace
35
+ attr_accessor :name, :id, :cell
36
+ attr_accessor :status, :tasks
37
+ attr_accessor :backtrace
38
+
39
+ def dump
40
+ string = ""
41
+ string << "Celluloid::Actor 0x#{id.to_s(16)}"
42
+ string << " [#{name}]" if name
43
+ string << "\n"
44
+
45
+ if cell
46
+ string << cell.dump
47
+ string << "\n"
48
+ end
49
+
50
+ if status == :idle
51
+ string << "State: Idle (waiting for messages)\n"
52
+ display_backtrace backtrace, string if backtrace
53
+ else
54
+ string << "State: Running (executing tasks)\n"
55
+ display_backtrace backtrace, string if backtrace
56
+ string << "\tTasks:\n"
57
+
58
+ tasks.each_with_index do |task, i|
59
+ string << "\t #{i + 1}) #{task.task_class}[#{task.type}]: #{task.status}\n"
60
+ if task.backtrace
61
+ string << "\t #{task.meta.inspect}\n"
62
+ display_backtrace task.backtrace, string, "\t"
63
+ end
64
+ end
65
+ end
66
+ string << "\n" unless backtrace
67
+ string
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ module Celluloid
2
+ module Internals
3
+ class Stack
4
+ class Summary < Stack
5
+ def initialize(threads)
6
+ super(threads)
7
+ snapshot
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ require "set"
2
+ require "forwardable"
3
+
4
+ module Celluloid
5
+ module Internals
6
+ if RUBY_PLATFORM == "java"
7
+ require "jruby/synchronized"
8
+
9
+ class TaskSet
10
+ extend Forwardable
11
+ include JRuby::Synchronized
12
+
13
+ def_delegators :@tasks, :<<, :delete, :first, :empty?, :to_a
14
+
15
+ def initialize
16
+ @tasks = Set.new
17
+ end
18
+ end
19
+ elsif RUBY_ENGINE == "rbx"
20
+ class TaskSet
21
+ def initialize
22
+ @tasks = Set.new
23
+ end
24
+
25
+ def <<(task)
26
+ Rubinius.synchronize(self) { @tasks << task }
27
+ end
28
+
29
+ def delete(task)
30
+ Rubinius.synchronize(self) { @tasks.delete task }
31
+ end
32
+
33
+ def first
34
+ Rubinius.synchronize(self) { @tasks.first }
35
+ end
36
+
37
+ def empty?
38
+ Rubinius.synchronize(self) { @tasks.empty? }
39
+ end
40
+
41
+ def to_a
42
+ Rubinius.synchronize(self) { @tasks.to_a }
43
+ end
44
+ end
45
+ else
46
+ # Assume we're on MRI, where we have the GIL. But what about IronRuby?
47
+ # Or MacRuby. Do people care? This will break Celluloid::Internals::StackDumps
48
+ TaskSet = Set
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ module Celluloid
2
+ module Internals
3
+ # An abstraction around threads from the InternalPool which ensures we don't
4
+ # accidentally do things to threads which have been returned to the pool,
5
+ # such as, say, killing them
6
+ class ThreadHandle
7
+ def initialize(actor_system, role = nil)
8
+ @mutex = Mutex.new
9
+ @join = ConditionVariable.new
10
+
11
+ @thread = actor_system.get_thread do
12
+ Thread.current.role = role
13
+ begin
14
+ yield
15
+ ensure
16
+ @mutex.synchronize do
17
+ @thread = nil
18
+ @join.broadcast
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # Is the thread running?
25
+ def alive?
26
+ @mutex.synchronize { @thread && @thread.alive? }
27
+ end
28
+
29
+ # Forcibly kill the thread
30
+ def kill
31
+ @mutex.synchronize { @thread && @thread.kill }
32
+ self
33
+ end
34
+
35
+ # Join to a running thread, blocking until it terminates
36
+ def join(limit = nil)
37
+ raise ThreadError, "Target thread must not be current thread" if @thread == Thread.current
38
+ @mutex.synchronize { @join.wait(@mutex, limit) if @thread }
39
+ self
40
+ end
41
+
42
+ # Obtain the backtrace for this thread
43
+ def backtrace
44
+ @thread.backtrace
45
+ rescue NoMethodError
46
+ # undefined method `backtrace' for nil:NilClass
47
+ # Swallow this in case this ThreadHandle was terminated and @thread was
48
+ # set to nil
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,40 @@
1
+ require "securerandom"
2
+
3
+ module Celluloid
4
+ module Internals
5
+ # Clearly Ruby doesn't have enough UUID libraries
6
+ # This one aims to be fast and simple with good support for multiple threads
7
+ # If there's a better UUID library I can use with similar multithreaded
8
+ # performance, I certainly wouldn't mind using a gem for this!
9
+ module UUID
10
+ values = SecureRandom.hex(9).match(/(.{8})(.{4})(.{3})(.{3})/)
11
+ PREFIX = "#{values[1]}-#{values[2]}-4#{values[3]}-8#{values[4]}".freeze
12
+ BLOCK_SIZE = 0x10000
13
+
14
+ @counter = 0
15
+ @counter_mutex = Mutex.new
16
+
17
+ def self.generate
18
+ thread = Thread.current
19
+
20
+ unless thread.uuid_limit
21
+ @counter_mutex.synchronize do
22
+ block_base = @counter
23
+ @counter += BLOCK_SIZE
24
+ thread.uuid_counter = block_base
25
+ thread.uuid_limit = @counter - 1
26
+ end
27
+ end
28
+
29
+ counter = thread.uuid_counter
30
+ if thread.uuid_counter >= thread.uuid_limit
31
+ thread.uuid_counter = thread.uuid_limit = nil
32
+ else
33
+ thread.uuid_counter += 1
34
+ end
35
+
36
+ "#{PREFIX}-#{format('%012x', counter)}".freeze
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module Celluloid
2
+ # Wraps all events and context for a single incident.
3
+ class Incident
4
+ attr_accessor :pid
5
+ attr_accessor :events, :triggering_event
6
+
7
+ def initialize(events, triggering_event = nil)
8
+ @events = events
9
+ @triggering_event = triggering_event
10
+ @pid = $PROCESS_ID
11
+ end
12
+
13
+ # Merge two incidents together. This may be useful if two incidents occur at the same time.
14
+ def merge(*other_incidents)
15
+ merged_events = other_incidents.flatten.inject(events) do |events, incident|
16
+ events += incident.events
17
+ end
18
+ Incident.new(merged_events.sort, triggering_event)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,147 @@
1
+ require "logger"
2
+
3
+ module Celluloid
4
+ # A logger that holds all messages in circular buffers, then flushes the buffers
5
+ # when an event occurs at a configurable severity threshold.
6
+ #
7
+ # Unlike ruby's Logger, this class only supports a single progname.
8
+ class IncidentLogger
9
+ module Severity
10
+ include ::Logger::Severity
11
+
12
+ TRACE = -1
13
+
14
+ def severity_to_string(severity)
15
+ case severity
16
+ when TRACE then "TRACE"
17
+ when DEBUG then "DEBUG"
18
+ when INFO then "INFO"
19
+ when WARN then "WARN"
20
+ when ERROR then "ERROR"
21
+ when FATAL then "FATAL"
22
+ when UNKNOWN then "UNKNOWN"
23
+ end
24
+ end
25
+ end
26
+ include Severity
27
+
28
+ # The progname (facility) for this instance.
29
+ attr_accessor :progname
30
+
31
+ # The logging level. Messages below this severity will not be logged at all.
32
+ attr_accessor :level
33
+
34
+ # The incident threshold. Messages at or above this severity will generate an
35
+ # incident and be published to incident reporters.
36
+ attr_accessor :threshold
37
+
38
+ # The buffer size limit. Each log level will retain this number of messages
39
+ # at maximum.
40
+ attr_accessor :sizelimit
41
+
42
+ attr_accessor :buffers
43
+
44
+ # Create a new IncidentLogger.
45
+ def initialize(progname = nil, options = {})
46
+ @progname = progname || "default"
47
+ @level = options[:level] || DEBUG
48
+ @threshold = options[:threshold] || ERROR
49
+ @sizelimit = options[:sizelimit] || 100
50
+
51
+ @buffer_mutex = Mutex.new
52
+ @buffers = Hash.new do |progname_hash, pn|
53
+ @buffer_mutex.synchronize do
54
+ progname_hash[pn] = Hash.new do |severity_hash, severity|
55
+ severity_hash[severity] = RingBuffer.new(@sizelimit)
56
+ end
57
+ end
58
+ end
59
+
60
+ # When the IncidentLogger itself encounters an error, it falls back to logging to stderr
61
+ @fallback_logger = ::Logger.new(STDERR)
62
+ @fallback_logger.progname = "FALLBACK"
63
+ end
64
+
65
+ # add an event.
66
+ def add(severity, message = nil, progname = nil, &block)
67
+ progname ||= @progname
68
+ severity ||= UNKNOWN
69
+
70
+ return event.id if severity < @level
71
+
72
+ if message.nil? && !block_given?
73
+ message = progname
74
+ progname = @progname
75
+ end
76
+
77
+ event = LogEvent.new(severity, message, progname, &block)
78
+
79
+ @buffers[progname][severity] << event
80
+
81
+ if severity >= @threshold
82
+ begin
83
+ Celluloid::Notifications.notifier.async.publish(incident_topic, create_incident(event))
84
+ rescue => ex
85
+ @fallback_logger.error(ex)
86
+ end
87
+ end
88
+ event.id
89
+ end
90
+ alias log add
91
+
92
+ # See docs for Logger#info
93
+ def trace(progname = nil, &block)
94
+ add(TRACE, nil, progname, &block)
95
+ end
96
+
97
+ def debug(progname = nil, &block)
98
+ add(DEBUG, nil, progname, &block)
99
+ end
100
+
101
+ def info(progname = nil, &block)
102
+ add(INFO, nil, progname, &block)
103
+ end
104
+
105
+ def warn(progname = nil, &block)
106
+ add(WARN, nil, progname, &block)
107
+ end
108
+
109
+ def error(progname = nil, &block)
110
+ add(ERROR, nil, progname, &block)
111
+ end
112
+
113
+ def fatal(progname = nil, &block)
114
+ add(FATAL, nil, progname, &block)
115
+ end
116
+
117
+ def unknown(progname = nil, &block)
118
+ add(UNKNOWN, nil, progname, &block)
119
+ end
120
+
121
+ def flush
122
+ messages = []
123
+ @buffer_mutex.synchronize do
124
+ @buffers.each do |_progname, severities|
125
+ severities.each do |_severity, buffer|
126
+ messages += buffer.flush
127
+ end
128
+ end
129
+ end
130
+ messages.sort
131
+ end
132
+
133
+ def clear
134
+ @buffer_mutex.synchronize do
135
+ @buffers.each(&:clear)
136
+ end
137
+ end
138
+
139
+ def create_incident(event = nil)
140
+ Incident.new(flush, event)
141
+ end
142
+
143
+ def incident_topic
144
+ "log.incident.#{@progname}"
145
+ end
146
+ end
147
+ end