observed 0.1.1 → 0.2.0.rc1

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 (94) hide show
  1. checksums.yaml +9 -9
  2. data/.travis.yml +4 -0
  3. data/README.md +53 -78
  4. data/examples/observed.rb +1 -1
  5. data/exe/observed-oneshot +3 -1
  6. data/features/explicit_routing.feature +33 -0
  7. data/features/oneshot.feature +4 -0
  8. data/features/test_in_single_ruby_source.feature +4 -0
  9. data/integrations/observed-clockwork/features/run_observed_inside_clockwork.feature +6 -7
  10. data/integrations/observed-clockwork/lib/observed/clockwork/version.rb +1 -1
  11. data/integrations/observed-clockwork/lib/observed/clockwork.rb +0 -1
  12. data/integrations/observed-clockwork/observed-clockwork.gemspec +1 -1
  13. data/integrations/observed-eventmachine/.gitignore +17 -0
  14. data/integrations/observed-eventmachine/Gemfile +8 -0
  15. data/integrations/observed-eventmachine/LICENSE.txt +22 -0
  16. data/integrations/observed-eventmachine/README.md +29 -0
  17. data/integrations/observed-eventmachine/Rakefile +1 -0
  18. data/integrations/observed-eventmachine/examples/observed.rb +30 -0
  19. data/integrations/observed-eventmachine/features/integration_via_single_ruby_source.feature +48 -0
  20. data/integrations/observed-eventmachine/features/support/env.rb +8 -0
  21. data/integrations/observed-eventmachine/lib/observed/eventmachine/version.rb +5 -0
  22. data/integrations/observed-eventmachine/lib/observed/eventmachine.rb +70 -0
  23. data/integrations/observed-eventmachine/observed-eventmachine.gemspec +28 -0
  24. data/lib/observed/application/oneshot.rb +14 -37
  25. data/lib/observed/builtin_plugins/file.rb +5 -14
  26. data/lib/observed/builtin_plugins/stdout.rb +7 -14
  27. data/lib/observed/config.rb +4 -4
  28. data/lib/observed/config_builder.rb +154 -87
  29. data/lib/observed/config_dsl.rb +2 -8
  30. data/lib/observed/configurable.rb +61 -3
  31. data/lib/observed/context.rb +90 -0
  32. data/lib/observed/default/observer.rb +2 -5
  33. data/lib/observed/default.rb +0 -1
  34. data/lib/observed/event_bus.rb +31 -0
  35. data/lib/observed/execution_job_factory.rb +95 -0
  36. data/lib/observed/job.rb +163 -0
  37. data/lib/observed/jobbed_event_bus.rb +33 -0
  38. data/lib/observed/logging.rb +40 -0
  39. data/lib/observed/observer.rb +1 -0
  40. data/lib/observed/observer_helpers/timer.rb +13 -5
  41. data/lib/observed/pluggable.rb +11 -0
  42. data/lib/observed/reporter/regexp_matching.rb +2 -1
  43. data/lib/observed/reporter/report_formatting.rb +57 -0
  44. data/lib/observed/reporter.rb +0 -2
  45. data/lib/observed/system.rb +11 -78
  46. data/lib/observed/translator.rb +22 -0
  47. data/lib/observed/version.rb +1 -1
  48. data/lib/observed.rb +10 -12
  49. data/omnibus-observed/.gitignore +9 -0
  50. data/omnibus-observed/Berksfile +3 -0
  51. data/omnibus-observed/Berksfile.lock +52 -0
  52. data/omnibus-observed/Gemfile +4 -0
  53. data/omnibus-observed/README.md +102 -0
  54. data/omnibus-observed/Vagrantfile +93 -0
  55. data/omnibus-observed/config/projects/observed.rb +20 -0
  56. data/omnibus-observed/config/software/observed.rb +19 -0
  57. data/omnibus-observed/package-scripts/observed/makeselfinst +27 -0
  58. data/omnibus-observed/package-scripts/observed/postinst +17 -0
  59. data/omnibus-observed/package-scripts/observed/postrm +9 -0
  60. data/plugins/observed-fluentd/lib/observed/fluentd/version.rb +1 -1
  61. data/plugins/observed-gauge/README.md +5 -0
  62. data/plugins/observed-gauge/lib/observed/gauge/version.rb +1 -1
  63. data/plugins/observed-gauge/lib/observed/gauge.rb +11 -13
  64. data/plugins/observed-gauge/observed-gauge.gemspec +1 -1
  65. data/plugins/observed-gauge/spec/gauge_spec.rb +7 -7
  66. data/plugins/observed-growl/Gemfile +6 -0
  67. data/plugins/observed-growl/lib/observed/growl.rb +80 -0
  68. data/plugins/observed-http/lib/observed/http/version.rb +1 -1
  69. data/plugins/observed-http/lib/observed/http.rb +10 -8
  70. data/plugins/observed-http/observed-http.gemspec +1 -1
  71. data/plugins/observed-http/spec/http_spec.rb +62 -7
  72. data/plugins/observed-http/spec/integration_spec.rb +14 -0
  73. data/plugins/observed-shell/Gemfile +5 -0
  74. data/plugins/observed-shell/lib/observed/shell.rb +54 -0
  75. data/run-integration-tests +81 -0
  76. data/spec/builtin_plugins/stdout_spec.rb +7 -3
  77. data/spec/config_builder_spec.rb +42 -59
  78. data/spec/config_dsl_spec.rb +4 -0
  79. data/spec/configurable_spec.rb +141 -31
  80. data/spec/event_bus_spec.rb +16 -0
  81. data/spec/execution_job_factory_spec.rb +35 -0
  82. data/spec/job_factory_spec.rb +16 -0
  83. data/spec/job_spec.rb +228 -0
  84. data/spec/jobbed_event_bus_spec.rb +38 -0
  85. data/spec/observed_spec.rb +203 -0
  86. data/spec/observer_helpers/timer_spec.rb +187 -0
  87. data/spec/oneshot_spec.rb +7 -2
  88. data/spec/system_spec.rb +5 -39
  89. metadata +55 -12
  90. data/lib/observed/default/reporter.rb +0 -17
  91. data/lib/observed/reader.rb +0 -14
  92. data/lib/observed/writer.rb +0 -14
  93. data/spec/reader_spec.rb +0 -15
  94. data/spec/writer_spec.rb +0 -16
@@ -0,0 +1,90 @@
1
+ require 'logger'
2
+
3
+ require 'observed/system'
4
+ require 'observed/config_builder'
5
+ require 'observed/config_dsl'
6
+ require 'observed/job'
7
+ require 'observed/jobbed_event_bus'
8
+
9
+ module Observed
10
+ # The run context of an Observed system.
11
+ # It can be initialized via parameters to automatically configure the system and everything needed such as the config
12
+ # builder, the DSL, the logger, etc.
13
+ class Context
14
+
15
+ def initialize(args={})
16
+ configure args
17
+ end
18
+
19
+ def configure(args)
20
+ @logger ||= begin
21
+ logger_out = if args[:log_file]
22
+ File.open(args[:log_file], 'a')
23
+ else
24
+ STDOUT
25
+ end
26
+ Logger.new(logger_out)
27
+ end
28
+
29
+ @executor = args[:executor]
30
+
31
+ set_log_level_to_debug(!!args[:debug])
32
+
33
+ if args[:config_file]
34
+ load_config_file(args[:config_file])
35
+ end
36
+
37
+ self
38
+ end
39
+
40
+ def logger
41
+ @logger
42
+ end
43
+
44
+ def system
45
+ @system ||= Observed::System.new(logger: logger, context: self)
46
+ end
47
+
48
+ def executor
49
+ @executor ||= Observed::BlockingJobExecutor.new
50
+ end
51
+
52
+ def jobbed_event_bus
53
+ @event_bus ||= Observed::JobbedEventBus.new(job_factory: job_factory)
54
+ end
55
+
56
+ def job_factory
57
+ @job_factory ||= Observed::JobFactory.new(executor: executor)
58
+ end
59
+
60
+ def execution_job_factory
61
+ @execution_job_factory ||= Observed::ExecutionJobFactory.new(job_factory: job_factory)
62
+ end
63
+
64
+ def config_builder
65
+ @config_builder ||= Observed::ConfigBuilder.new(system: system, logger: logger, context: self)
66
+ end
67
+
68
+ def config_dsl
69
+ Observed::ConfigDSL.new(builder: config_builder, logger: logger)
70
+ end
71
+
72
+ private
73
+
74
+ def set_log_level_to_debug(enabled)
75
+ @logger.level = if enabled
76
+ Logger::DEBUG
77
+ else
78
+ Logger::INFO
79
+ end
80
+
81
+ @logger.debug "Enabling Debug logs." if enabled
82
+ end
83
+
84
+ def load_config_file(path)
85
+ config_dsl.eval_file(path)
86
+ system.config = config_dsl.config
87
+ end
88
+
89
+ end
90
+ end
@@ -4,11 +4,8 @@ module Observed
4
4
  module Default
5
5
  class Observer < Observed::Observer
6
6
 
7
- attribute :reader
8
-
9
- def observe
10
- data = reader.read
11
- system.report(tag, data)
7
+ def observe(data)
8
+ [tag, data]
12
9
  end
13
10
 
14
11
  end
@@ -1,2 +1 @@
1
1
  require 'observed/default/observer'
2
- require 'observed/default/reporter'
@@ -0,0 +1,31 @@
1
+ require 'thread'
2
+
3
+ module Observed
4
+ class EventBus
5
+ def initialize
6
+ @mutex = ::Mutex.new
7
+ @subscribers = []
8
+ end
9
+ def emit(tag, *params)
10
+ handle_event(tag, *params)
11
+ end
12
+
13
+ def on_receive(pattern, &block)
14
+ @mutex.synchronize do
15
+ @subscribers.push [pattern, block]
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def handle_event(tag, *params)
22
+ @mutex.synchronize do
23
+ @subscribers.each do |pattern, s|
24
+ if pattern.match(tag)
25
+ s.call *params
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,95 @@
1
+ require 'observed/observer'
2
+ require 'observed/reporter'
3
+ require 'observed/translator'
4
+ require 'observed/job'
5
+
6
+ module Observed
7
+ # An yet another cushion from the deprecated plugin interface to the new plugin interface
8
+ class FakeSystem
9
+
10
+ def initialize(args)
11
+ @time = args[:time] || Time.now
12
+ end
13
+
14
+ def report(tag, time, data=nil)
15
+ options = nil
16
+ if tag.is_a?(::Hash)
17
+ data = tag
18
+ options = time || {}
19
+ tag = nil
20
+ elsif tag.is_a?(String) && time.is_a?(::Hash)
21
+ options = data
22
+ data = time
23
+ else
24
+ options = {tag: tag, time: time}
25
+ end
26
+ options ||= {}
27
+ options[:tag] ||= tag
28
+ options[:time] ||= now
29
+ @reported = [data, options]
30
+ end
31
+
32
+ def reported
33
+ @reported
34
+ end
35
+
36
+ def now
37
+ @time
38
+ end
39
+ end
40
+
41
+ class ExecutionJobFactory
42
+
43
+ def initialize(args={})
44
+ @job_factory = args[:job_factory] || Observed::JobFactory.new(executor: Observed::BlockingJobExecutor.new)
45
+ end
46
+
47
+ # Convert the observer/translator/reporter to a job
48
+ def convert_to_job(underlying)
49
+ if underlying.is_a? Observed::Observer
50
+ @job_factory.job {|data, options|
51
+ options ||= {}
52
+ m = underlying.method(:observe)
53
+ fake_system = FakeSystem.new(time: options[:time])
54
+ # For 0.1.0 compatibility
55
+ underlying.configure(system: fake_system)
56
+ underlying.configure(tag: options[:tag]) unless underlying.get_attribute_value(:tag)
57
+ result = dispatch_method m, data, options
58
+ fake_system.reported || result
59
+ }
60
+ elsif underlying.is_a? Observed::Reporter
61
+ @job_factory.job {|data, options|
62
+ options ||= {}
63
+ m = underlying.method(:report)
64
+ dispatch_method m, data, options
65
+ }
66
+ elsif underlying.is_a? Observed::Translator
67
+ @job_factory.job {|data, options|
68
+ options ||= {}
69
+ m = underlying.method(:translate)
70
+ dispatch_method m, data, options
71
+ }
72
+ else
73
+ fail "Unexpected type of object which can not be converted to a job: #{underlying}"
74
+ end
75
+ end
76
+
77
+ def dispatch_method(m, data, options)
78
+ num_parameters = m.parameters.size
79
+ case num_parameters
80
+ when 0
81
+ m.call
82
+ when 1
83
+ m.call data
84
+ when 2
85
+ m.call data, options
86
+ when 3
87
+ # Deprecated. This is here for backward compatiblity
88
+ m.call options[:tag], options[:time], data
89
+ else
90
+ fail "Unexpected number of parameters for the method `#{m}`: #{num_parameters}"
91
+ end
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,163 @@
1
+ require 'logger'
2
+ require 'thread'
3
+
4
+ module Observed
5
+ class Job
6
+ def then(*jobs)
7
+ next_job = if jobs.size == 1
8
+ jobs.first
9
+ elsif jobs.size > 1
10
+ ParallelJob.new(jobs)
11
+ else
12
+ raise 'No jobs to be executed'
13
+ end
14
+ SequenceJob.new(self, next_job)
15
+ end
16
+ end
17
+
18
+ class MutableJob
19
+ def initialize(current_job)
20
+ @current_job = current_job
21
+ @mutex = Mutex.new
22
+ end
23
+ def now(data={}, options=nil)
24
+ @current_job.now(data, options) do |data, options2|
25
+ yield data, (options2 || options) if block_given?
26
+ end
27
+ end
28
+ def then(*jobs)
29
+ @mutex.synchronize do
30
+ @current_job = @current_job.then(*jobs)
31
+ end
32
+ self
33
+ end
34
+ end
35
+
36
+ class SequenceJob < Job
37
+ attr_reader :base_job
38
+ def initialize(base_job, next_job)
39
+ @base_job = base_job
40
+ @next_job = next_job
41
+ end
42
+ def now(data={}, options=nil)
43
+ @base_job.now(data, options) do |data, options2|
44
+ @next_job.now(data, (options2 || options)) do |data, options3|
45
+ yield data, (options3 || options2 || options) if block_given?
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ class ParallelJob < Job
52
+ def initialize(jobs)
53
+ @jobs = jobs || fail('jobs missing')
54
+ @next_job = NoOpJob.instance
55
+ end
56
+ def now(data={}, options=nil)
57
+ @jobs.each do |job|
58
+ job.now(data, options) do |data, options2|
59
+ yield data, (options2 || options) if block_given?
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ class NoOpJob < Job
66
+ def now(data={}, options={}); end
67
+ def self.instance
68
+ SINGLETON_INSTANCE
69
+ end
70
+ SINGLETON_INSTANCE = NoOpJob.new
71
+ end
72
+
73
+ class ProcJob < Job
74
+ def initialize(args, &block)
75
+ @executor = args[:executor] || fail('Missing a value for :executor')
76
+ @listener = args[:listener] || fail('Missing a value for :listener')
77
+ @logger = args[:logger]
78
+ @block = block
79
+ @next_job = NoOpJob.instance
80
+
81
+ if @logger.nil?
82
+ @logger = ::Logger.new(STDERR)
83
+ @logger.level = ::Logger::WARN
84
+ end
85
+ end
86
+ def now(data={}, options=nil)
87
+ @executor.execute {
88
+ result = @block.call(data, options)
89
+ yield result if block_given?
90
+ notify_listener(data: data, options: options, result: result)
91
+ }
92
+ end
93
+
94
+ private
95
+
96
+ def notify_listener(args)
97
+ return unless @listener
98
+
99
+ data = args[:data]
100
+ options = args[:options]
101
+ result = args[:result]
102
+
103
+ @logger.debug "Notifying listeners with the result(#{result}) generated from the input data(#{data}) and the options(#{options})"
104
+
105
+ if result.is_a? ::Hash
106
+ if options
107
+ @listener.on_result(result, options)
108
+ else
109
+ @listener.on_result(result)
110
+ end
111
+ elsif result.is_a? ::Array
112
+ if result.size == 1 && options
113
+ @listener.on_result(result, options)
114
+ elsif result.size == 2
115
+ @listener.on_result(*result)
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ class JobListener
122
+ def on_result(data={}, options={})
123
+
124
+ end
125
+ end
126
+
127
+ class JobFactory
128
+ def initialize(args)
129
+ @executor = args[:executor] || fail('Missing a value for :executor')
130
+ @listener = args[:listener] || JobListener.new
131
+ end
132
+
133
+ def job(&block)
134
+ ProcJob.new(executor: @executor, listener: @listener, &block)
135
+ end
136
+
137
+ def mutable_job(&block)
138
+ MutableJob.new(job(&block))
139
+ end
140
+
141
+ def parallel(jobs)
142
+ ParallelJob.new(jobs)
143
+ end
144
+ end
145
+
146
+ class JobExecutor
147
+ def execute; end
148
+ end
149
+
150
+ class BlockingJobExecutor
151
+ def execute
152
+ yield
153
+ end
154
+ end
155
+
156
+ class ThreadedJobExecutor
157
+ def execute
158
+ Thread.start {
159
+ yield
160
+ }
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,33 @@
1
+ require 'thread'
2
+ require 'observed/event_bus'
3
+
4
+ module Observed
5
+ class JobbedEventBus
6
+ def initialize(args={})
7
+ @bus = Observed::EventBus.new
8
+ @receives = {}
9
+ @job_factory = args[:job_factory] || fail("The parameter :job_factory is missing in args(#{args}")
10
+ @mutex = ::Mutex.new
11
+ end
12
+ def pipe_to_emit(tag)
13
+ @job_factory.job { |*params|
14
+ self.emit(tag, *params)
15
+ params
16
+ }
17
+ end
18
+ def emit(tag, *params)
19
+ @bus.emit tag, *params
20
+ end
21
+ def receive(pattern)
22
+ job = @job_factory.mutable_job {|data, options|
23
+ [data, options]
24
+ }
25
+ @bus.on_receive(pattern) do |*params|
26
+ @mutex.synchronize do
27
+ job.now(*params)
28
+ end
29
+ end
30
+ job
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ require 'observed/configurable'
2
+
3
+ module Observed
4
+ module Logging
5
+ include Observed::Configurable
6
+
7
+ # !@attribute [r] logger
8
+ # @return [Logger]
9
+ attribute :logger
10
+
11
+ # @return [Boolean] `true` if a value is set for the attribute :logger
12
+ def logging_enabled?
13
+ has_attribute_value? :logger
14
+ end
15
+
16
+ # Log the debug message through the logger configured via the :logger attribute
17
+ # @param [String] message
18
+ def log_debug(message)
19
+ logger.debug message if logging_enabled?
20
+ end
21
+
22
+ # Log the info message through the logger configured via the :logger attribute
23
+ # @param [String] message
24
+ def log_info(message)
25
+ logger.info message if logging_enabled?
26
+ end
27
+
28
+ # Log the warn message through the logger configured via the :logger attribute
29
+ # @param [String] message
30
+ def log_warn(message)
31
+ logger.warn message if logging_enabled?
32
+ end
33
+
34
+ # Log the error message through the logger configured via the :logger attribute
35
+ # @param [String] message
36
+ def log_error(message)
37
+ logger.error message if logging_enabled?
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,6 @@
1
1
  require 'observed/configurable'
2
2
  require 'observed/pluggable'
3
+ require 'observed/logging'
3
4
 
4
5
  module Observed
5
6
 
@@ -17,10 +17,12 @@ module Observed
17
17
  elapsed_time = after - before
18
18
  r[:elapsed_time] = elapsed_time
19
19
  r
20
- rescue Timeout::Error => e1
21
- { status: :error, error: {message: "#{e2.message}\n#{e2.backtrace}"}, timed_out: true }
22
- rescue => e2
23
- { status: :error, error: {message: "#{e2.message}\n#{e2.backtrace}"} }
20
+ rescue Timeout::Error => e
21
+ log_debug "Handled the error but logging it just for your info: #{e.message}\n#{e.backtrace.join("\n")}" if self.is_a? Logging
22
+ { status: :error, error: {message: 'Timed out.'}, timed_out: true }
23
+ rescue => e
24
+ log_error "Handled the error: #{e.message}\n#{e.backtrace.join("\n")}" if self.is_a? Logging
25
+ { status: :error, error: {message: e.message} }
24
26
  end
25
27
  end
26
28
 
@@ -29,7 +31,13 @@ module Observed
29
31
  format = options[:format] || ->(r){ r }
30
32
  result = time(options, &block)
31
33
 
32
- system.report("#{tag}.#{result[:status]}", format.call(result))
34
+ data = ["#{tag}.#{result[:status]}", format.call(result)]
35
+
36
+ if self.method(:observe).parameters.size != 1
37
+ system.report(*data)
38
+ end
39
+
40
+ data
33
41
  end
34
42
 
35
43
  end
@@ -1,4 +1,14 @@
1
1
  module Observed
2
+ # Indicates that the class is pluggable (or extensible or a extension point).
3
+ # "pluggable" means that the class included this module will be the outlet in where Observed plug-ins are plugged.
4
+ #
5
+ # @example
6
+ # class Reader
7
+ # include Pluggable
8
+ # end
9
+ # class FooReader < Reader; end
10
+ # class BarReader < Reader; end
11
+ # Reader.plugins #=> [FooReader, BarReader]
2
12
  module Pluggable
3
13
 
4
14
  module ClassMethods
@@ -7,6 +17,7 @@ module Observed
7
17
  end
8
18
 
9
19
  def inherited(klass)
20
+ super if defined? super
10
21
  plugins << klass
11
22
  end
12
23
 
@@ -3,7 +3,8 @@ module Observed
3
3
  module RegexpMatching
4
4
 
5
5
  def match(tag)
6
- tag_pattern.match(tag)
6
+ tag_pattern = get_attribute_value(:tag_pattern)
7
+ tag_pattern.match(tag) if tag_pattern
7
8
  end
8
9
 
9
10
  end
@@ -0,0 +1,57 @@
1
+ require 'observed/configurable'
2
+ require 'observed/hash/fetcher'
3
+
4
+ module Observed
5
+ class Reporter
6
+ # The module to equip an observer with a `formatter` to format the data being reported.
7
+ # The `formatter` is just a proc and is able to be configured via an attribute.
8
+ #
9
+ # @example
10
+ # class YourObserver < Observed::Reporter
11
+ # include Observed::Reporter::Configurable
12
+ # include Observed::Reporter::ReportFormatting
13
+ #
14
+ # attribute :format, default: -> tag, time, data { "#{Time.at(time)} #{tag} #{data}" }
15
+ #
16
+ # def report(tag, time, data)
17
+ # formatted_data = format_report(tag, time, data)
18
+ # # The output of your choice
19
+ # end
20
+ # end
21
+ #
22
+ # observer = YourObserver.new(format: -> tag, time, data { "The data being reported: #{tag} #{time} #{data}" })
23
+ # observer.report('test', Time.now, {data: 1})
24
+ # #=> outputs "#{Time.at(Time.now)} test {:data=>1}"
25
+ module ReportFormatting
26
+
27
+ include Observed::Configurable
28
+
29
+ attribute :format, default: -> tag, time, data {
30
+ begin
31
+ "#{Time.at(time)} #{tag} #{data}"
32
+ rescue => e
33
+ nested = Exception.new("Error while formatting the data: tag=#{tag} time=#{time} data=#{data} " + e.message)
34
+ nested.set_backtrace(e.backtrace)
35
+ raise nested
36
+ end
37
+ }
38
+
39
+ # Format the data being reported. The data includes 3 parameters: `tag`, `time` and `data`.
40
+ # @param [String] tag
41
+ # @param [Time] time
42
+ # @param [Hash] data
43
+ def format_report(tag, time, data)
44
+ num_params = format.parameters.size
45
+ case num_params
46
+ when 3
47
+ format.call(tag, time, data)
48
+ when 4
49
+ format.call(tag, time, data, Observed::Hash::Fetcher.new(data))
50
+ else
51
+ fail "Number of parameters for the function for the key :format must be 3 or 4, but was #{num_params}(#{format.parameters}"
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -10,8 +10,6 @@ module Observed
10
10
  # @return [Regexp]
11
11
  attribute :tag_pattern
12
12
 
13
- attribute :system
14
-
15
13
  # @param [String] tag
16
14
  def match(tag)
17
15
  raise NotImplementedError.new