observed 0.1.1 → 0.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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