daemon_objects 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ tmp
18
18
  *.swp
19
19
  bin
20
20
  log
21
+ tags
data/Gemfile CHANGED
@@ -3,6 +3,3 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in daemon_objects.gemspec
4
4
  gemspec
5
5
 
6
- group :test do
7
- gem 'pry-nav'
8
- end
data/README.md CHANGED
@@ -5,10 +5,10 @@
5
5
 
6
6
  Daemon Objects is designed to simplify using daemons in your ruby applications. Under the hood, it uses the
7
7
  [daemons](http://daemons.rubyforge.org) gem, which is a mature and tested solution. But, it adds support for managing via rake tasks,
8
- error handling and instrumentation.
8
+ error handling and instrumentation.
9
9
 
10
- The [daemons](http://daemons.rubyforge.org) gem also is intended to be used to daemonize a ruby script. DaemonObjects provides an
11
- object-oriented framework for developing daemons. This allows the application developer to focus on the specific behavior of the daemon
10
+ The [daemons](http://daemons.rubyforge.org) gem also is intended to be used to daemonize a ruby script. DaemonObjects provides an
11
+ object-oriented framework for developing daemons. This allows the application developer to focus on the specific behavior of the daemon
12
12
  instead of the infrastructure of daemon management.
13
13
 
14
14
  ## Installation
@@ -27,7 +27,7 @@ Or install it yourself as:
27
27
 
28
28
  ## Usage
29
29
 
30
- DaemonObjects will create daemons based on a simple convention. It will search a directory for files name \*Daemon.rb. These typically
30
+ DaemonObjects will create daemons based on a simple convention. It will search a directory for files name \*Daemon.rb. These typically
31
31
  will just inherit from the base Daemon class.
32
32
 
33
33
  class MyDaemon < DaemonObjects::Base; end
@@ -37,7 +37,7 @@ This provides the basic daemon control methods (start, stop, run and restart) to
37
37
  To add behavior to your daemon, you will need a consumer. DaemonObjects will load the consumer using the name of the daemon and
38
38
  will search in the same directory for it. For example, if your daemon is name MyDaemon, the consumer should be named MyConsumer.
39
39
 
40
- A consumer needs to inherit from the consumer base and implement run. For example,
40
+ A consumer needs to inherit from the consumer base and implement run. For example,
41
41
 
42
42
  class MyConsumer < DaemonObjects::ConsumerBase
43
43
 
@@ -50,6 +50,25 @@ A consumer needs to inherit from the consumer base and implement run. For examp
50
50
 
51
51
  end
52
52
 
53
+ ### Environment
54
+
55
+ You can pass an environment argument to the consumer in two ways. If the project is
56
+ using Rails, it will automatically use `Rails.env`. Otherwise, you can use the
57
+ `DAEMON_ENV` environment variable.
58
+
59
+ ### Application directory
60
+
61
+ Application directory can be set a number of ways. If the project is using Rails, the
62
+ application directory is `Rails.root`. If the task was started with Rake, it will be
63
+ `Rake.original_dir`. You can also override this value by defining an `app_directory`
64
+ method in the `DaemonBase` subclass.
65
+
66
+ class MyDaemon < DaemonObjects::Base
67
+ def app_directory
68
+ "/some/other/directory"
69
+ end
70
+ end
71
+
53
72
  ### Rake tasks
54
73
 
55
74
  Once you have defined the daemon, you can control it with rake tasks. To access the rake tasks,
@@ -74,22 +93,19 @@ Four commands are supported
74
93
 
75
94
  ### Amqp Support
76
95
 
77
- _in beta_
96
+ DaemonObjects also has support for monitoring amqp queues. This is done with the bunny gem. To support this with your daemon, add `consumes_amqp` to your daemon class, one pair of daemon/consumer classes per queue you wish to support. Queues are constructed as 'durable' by default.
78
97
 
79
- DaemonObjects also has support for monitoring an amqp queue. This is done with the amqp gem. To support this
80
- with your daemon, add `supports_amqp` to your daemon class.
81
-
82
- class MyQueueProcessingDaemon < Daemon::Base
83
- supports_amqp :endpoint => "http://localhost:5672",
98
+ class MyQueueProcessingDaemon < DaemonObjects::Base
99
+ consumes_amqp :endpoint => "http://localhost:5672",
84
100
  :queue_name => "my_awesome_queue"
85
101
  end
86
102
 
87
103
  This will add the code to monitor the queue, so all you need now is code to handle the messages.
88
104
 
89
- class MyQueueProcessingConsumer < Daemon::ConsumerBase
105
+ class MyQueueProcessingConsumer < DaemonObjects::ConsumerBase
90
106
 
91
107
  handle_messages_with do |payload|
92
- puts "payload"
108
+ puts payload
93
109
  end
94
110
 
95
111
  end
@@ -99,6 +115,8 @@ This will add the code to monitor the queue, so all you need now is code to hand
99
115
  DaemonObjects will create a new log file for your daemon using the pattern _daemon\_file\_name_\_daemon.log. In a rails project,
100
116
  this will be created in the log directory of your application.
101
117
 
118
+ If the daemon does not have access to create the log, it will log errors to /tmp/_daemon_name_.output.
119
+
102
120
  ### Support for third-party libraries
103
121
 
104
122
  DaemonObjects supports the following third-party libraries. If they are required in your application, your daemon will use them.
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.add_development_dependency "rspec"
27
27
  spec.add_development_dependency "pry-nav"
28
+ spec.add_development_dependency "memory_logger", "~> 0.0.3"
28
29
  end
@@ -17,8 +17,16 @@ class DaemonObjects::Base
17
17
  logger.info "Worker class is '#{worker_class}'"
18
18
  end
19
19
 
20
+ def self.app_directory
21
+ @app_directory ||= (defined? Rails) ? Rails.root : Rake.original_dir
22
+ end
23
+
24
+ def self.environment
25
+ @environment ||= (defined? Rails) ? Rails.env : ENV["DAEMON_ENV"]
26
+ end
27
+
20
28
  def self.pid_directory
21
- (defined? Rails) ? File.join(Rails.root, "tmp/pids") : "."
29
+ File.join(app_directory, "tmp/pids")
22
30
  end
23
31
 
24
32
  def self.consumer_class
@@ -30,16 +38,27 @@ class DaemonObjects::Base
30
38
  end
31
39
 
32
40
  def self.get_consumer
33
- consumer_class.new(logger)
41
+ consumer_class.new(:logger => logger, :app_directory => app_directory, :environment => environment)
42
+ rescue Exception => e
43
+ logger.error("An exception occured while instantiating the consumer #{consumer_class}. Startup will be aborted.")
44
+ logger.error("Error: #{e.class}")
45
+ logger.error("Message: #{e.message}")
46
+ logger.error(e.backtrace.join("\n"))
47
+ Airbrake.notify(e) if defined?(Airbrake)
48
+ raise e
34
49
  end
35
50
 
36
51
  def self.run
37
- get_consumer.run
52
+ begin
53
+ get_consumer.run
54
+ rescue StandardError => e
55
+ handle_error(e)
56
+ end
38
57
  end
39
58
 
40
59
  def self.after_fork
41
60
  # daemonizing closes all file handles, so this will reopen the log
42
- force_logger_reset
61
+ force_logger_reset
43
62
  # this seems to be enough to initialize NewRelic if it's defined
44
63
  defined?(NewRelic)
45
64
  end
@@ -50,20 +69,18 @@ class DaemonObjects::Base
50
69
 
51
70
  FileUtils.mkdir_p(pid_directory)
52
71
 
53
- Daemons.run_proc(proc_name,
72
+ Daemons.run_proc(proc_name,
54
73
  { :ARGV => ["start", "-f"],
55
74
  :log_dir => "/tmp",
56
75
  :dir => pid_directory,
57
76
  :log_output => true}) do
58
-
77
+
59
78
  after_fork
60
- run
79
+ run
61
80
  end
62
81
 
63
82
  rescue StandardError => e
64
- logger.error(e.message)
65
- logger.error(e.backtrace.join("\n"))
66
- Airbrake.notify(e) if defined?(Airbrake)
83
+ handle_error(e)
67
84
  end
68
85
 
69
86
  def self.stop
@@ -75,4 +92,9 @@ class DaemonObjects::Base
75
92
  stop
76
93
  end
77
94
 
95
+ def self.handle_error(e)
96
+ logger.error(e.message)
97
+ logger.error(e.backtrace.join("\n"))
98
+ Airbrake.notify(e) if defined?(Airbrake)
99
+ end
78
100
  end
@@ -6,10 +6,12 @@ class DaemonObjects::ConsumerBase
6
6
  attr_accessor :message_handler
7
7
  end
8
8
 
9
- attr_accessor :logger
9
+ attr_reader :logger, :app_directory, :environment
10
10
 
11
- def initialize(logger)
12
- @logger = logger
11
+ def initialize(opts)
12
+ @logger = opts[:logger]
13
+ @app_directory = opts[:app_directory]
14
+ @environment = opts[:environment]
13
15
  end
14
16
 
15
17
  def run
@@ -5,7 +5,7 @@ module DaemonObjects::Logging
5
5
  end
6
6
 
7
7
  def log_directory
8
- (defined? Rails) ? File.join(Rails.root, "log") : "log"
8
+ File.join(app_directory, "log")
9
9
  end
10
10
 
11
11
  def logger
@@ -9,20 +9,20 @@ namespace :daemon do
9
9
  "or use run to run the daemon in the foreground"
10
10
 
11
11
  [:start, :stop, :run].each do |action|
12
- task action => :environment do
12
+ task action do
13
+ Rake::Task[:environment].invoke if Rake::Task.task_defined?(:environment)
13
14
 
14
15
  require "daemon_objects"
15
16
  require "#{DaemonObjects.daemon_path}/#{daemon}_daemon.rb"
16
17
  require "#{DaemonObjects.daemon_path}/#{daemon}_consumer.rb"
17
18
 
18
-
19
19
  puts "#{description} #{action}"
20
20
  daemon_class = "#{daemon}_daemon".camelcase.constantize
21
21
  daemon_class.send(action)
22
22
  end
23
23
  end
24
24
 
25
- task :restart => [:environment, :stop, :start]
25
+ task :restart => [:stop, :start]
26
26
  end
27
27
  end
28
28
 
@@ -1,3 +1,3 @@
1
1
  module DaemonObjects
2
- VERSION = "0.1.9"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -7,6 +7,80 @@ describe DaemonObjects::Base do
7
7
  end
8
8
  end
9
9
 
10
+ describe '#app_directory' do
11
+ it 'should be Rake.original_directory if Rails is not defined' do
12
+ Rake.stub(:original_dir).and_return("/mydir")
13
+ MyDaemon = Class.new(DaemonObjects::Base)
14
+ MyDaemon.app_directory.should == Rake.original_dir
15
+ end
16
+
17
+ context 'Rails' do
18
+ before :each do
19
+ Rails = Module.new do
20
+ def self.root
21
+ "/mydir"
22
+ end
23
+ end
24
+ end
25
+
26
+ after :each do
27
+ Object.send(:remove_const, :Rails)
28
+ end
29
+
30
+ it 'should be Rails.root is Rails is defined' do
31
+ MyDaemon = Class.new(DaemonObjects::Base)
32
+ MyDaemon.app_directory.should == Rails.root
33
+ end
34
+ end
35
+
36
+ it 'should allow app_directory to be set explicitly' do
37
+ MyDaemon = Class.new(DaemonObjects::Base) do
38
+ def app_directory
39
+ "."
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#environment' do
46
+ context 'Rails' do
47
+ before :each do
48
+ Rails = Module.new do
49
+ def self.env
50
+ "railsenv"
51
+ end
52
+ end
53
+ end
54
+
55
+ after :each do
56
+ Object.send(:remove_const, :Rails)
57
+ end
58
+
59
+ it 'should use Rails.env if Rails is defined' do
60
+ MyDaemon = Class.new(DaemonObjects::Base)
61
+ MyDaemon.environment.should == Rails.env
62
+ end
63
+ end
64
+
65
+ context 'Env variable set' do
66
+ before :each do
67
+ ENV["DAEMON_ENV"] = "daemonenv"
68
+ end
69
+ after :each do
70
+ ENV["DAEMON_ENV"] = nil
71
+ end
72
+ it 'should use environment variable if Rails is not defined' do
73
+ MyDaemon = Class.new(DaemonObjects::Base)
74
+ MyDaemon.environment.should == ENV["DAEMON_ENV"]
75
+ end
76
+ end
77
+
78
+ it 'should be nil if not Rails and no environment set' do
79
+ MyDaemon = Class.new(DaemonObjects::Base)
80
+ MyDaemon.environment.should be_nil
81
+ end
82
+ end
83
+
10
84
  describe '#extends' do
11
85
  it 'should extend logging' do
12
86
  MyDaemon = Class.new(DaemonObjects::Base)
@@ -19,11 +93,11 @@ describe DaemonObjects::Base do
19
93
  MyConsumer = Class.new(DaemonObjects::ConsumerBase)
20
94
 
21
95
  MyDaemon = Class.new(DaemonObjects::Base) do
22
- self.logger = StubLogger.new
96
+ self.logger = MemoryLogger::Logger.new
23
97
  end
24
98
 
25
99
  MyDaemon.run
26
- MyDaemon.logger.logged_output.should =~ /Starting consumer\n/
100
+ MyDaemon.logger.logged_output.should =~ /Starting consumer/
27
101
  end
28
102
 
29
103
  end
@@ -55,7 +129,7 @@ describe DaemonObjects::Base do
55
129
 
56
130
  describe '##consumer_class' do
57
131
  it 'should constantize a file with multiple part name' do
58
- ThreePartNameConsumer = Class.new
132
+ ThreePartNameConsumer = Class.new
59
133
  ThreePartNameDaemon = Class.new(DaemonObjects::Base)
60
134
  ThreePartNameDaemon.consumer_class.should == ThreePartNameConsumer
61
135
  end
@@ -68,6 +142,60 @@ describe DaemonObjects::Base do
68
142
  end
69
143
  end
70
144
 
145
+ describe '##get_consumer' do
146
+
147
+
148
+ it 'should log exceptions during consumer instantiation' do
149
+ TestConsumer = Class.new(DaemonObjects::ConsumerBase) do
150
+ def initialize(logger)
151
+ super
152
+ raise StandardError.new("Test")
153
+ end
154
+ end
155
+ TestDaemon = Class.new(DaemonObjects::Base) do
156
+ self.logger = MemoryLogger::Logger.new
157
+ end
158
+
159
+ expect {TestDaemon.get_consumer}.to raise_error(StandardError)
160
+ TestDaemon.logger.logged_output =~ /Message: Test/
161
+ end
162
+
163
+ let(:consumer) { MyDaemon.get_consumer }
164
+
165
+ before :each do
166
+ MyConsumer = Class.new(DaemonObjects::ConsumerBase)
167
+ MyDaemon = Class.new(DaemonObjects::Base)
168
+ end
169
+
170
+ after :each do
171
+ Object.send(:remove_const, :MyDaemon)
172
+ Object.send(:remove_const, :MyConsumer)
173
+ end
174
+
175
+ it 'should set environment' do
176
+ def MyDaemon.environment
177
+ "theenv"
178
+ end
179
+
180
+ consumer.environment.should == "theenv"
181
+ end
182
+
183
+ it 'should set app directory' do
184
+ def MyDaemon.app_directory
185
+ "thedir"
186
+ end
187
+
188
+ consumer.app_directory.should == "thedir"
189
+ end
190
+
191
+ it 'should set logger' do
192
+ logger = MemoryLogger::Logger.new
193
+ MyDaemon.stub(:logger).and_return(logger)
194
+
195
+ consumer.logger.should == logger
196
+ end
197
+ end
198
+
71
199
  describe 'AMQP support' do
72
200
  let(:endpoint){ }
73
201
 
@@ -16,11 +16,32 @@ describe DaemonObjects::ConsumerBase do
16
16
  handle_messages_with{|p| payloads_received << p }
17
17
  end
18
18
 
19
- h = Harness.new(StubLogger.new)
19
+ h = Harness.new(:logger => MemoryLogger::Logger.new)
20
20
  h.handle_message({:x => 1})
21
21
 
22
22
  h.payloads_received.should == [{:x => 1}]
23
23
  end
24
24
  end
25
+
26
+ describe '#initialize' do
27
+ let(:logger) { MemoryLogger::Logger.new }
28
+ let(:harness) { Class.new(DaemonObjects::ConsumerBase) }
29
+
30
+ it 'should set logger' do
31
+ h = harness.new(:logger => logger)
32
+ h.logger.should == logger
33
+ end
34
+
35
+ it 'should set app_directory' do
36
+ h = harness.new(:app_directory => 'app_dir')
37
+ h.app_directory.should == 'app_dir'
38
+ end
39
+
40
+ it 'should set environment' do
41
+ h = harness.new(:environment => 'environment')
42
+ h.environment.should == 'environment'
43
+ end
44
+ end
45
+
25
46
 
26
47
  end
@@ -4,12 +4,16 @@ describe DaemonObjects::Logging do
4
4
  let(:harness) do
5
5
  Class.new do
6
6
  extend DaemonObjects::Logging
7
+
8
+ def self.app_directory
9
+ "."
10
+ end
7
11
  end
8
12
  end
9
13
 
10
14
  describe '#logger' do
11
15
  it 'should create a logger at log/log_filename path' do
12
- logger = StubLogger.new
16
+ logger = MemoryLogger::Logger.new
13
17
 
14
18
  Logger.stub(:new).
15
19
  with("#{harness.log_directory}/#{harness.log_filename}").
@@ -17,7 +21,7 @@ describe DaemonObjects::Logging do
17
21
 
18
22
  harness.logger.info("starting consumer")
19
23
 
20
- logger.logged_output.should =~ /starting consumer\n$/
24
+ logger.logged_output.should =~ /starting consumer/
21
25
  end
22
26
  end
23
27
 
@@ -46,29 +50,8 @@ describe DaemonObjects::Logging do
46
50
 
47
51
  describe '#log_directory' do
48
52
  it "should use 'log' for default log path" do
49
- harness.log_directory.to_s.should == "log"
50
- end
51
-
52
- context 'with rails' do
53
- before :each do
54
- unless defined?(Rails)
55
- module Rails
56
- def self.root
57
- "/root"
58
- end
59
- end
60
- end
61
- end
62
-
63
- after :each do
64
- Object.send(:remove_const, :Rails)
65
- end
66
-
67
- it 'should use Rails log path when Rails is defined' do
68
- harness.log_directory.to_s.should == File.join(Rails.root, "log")
69
- end
53
+ harness.log_directory.to_s.should == File.join(harness.app_directory, "log")
70
54
  end
71
-
72
55
  end
73
56
 
74
57
 
data/spec/spec_helper.rb CHANGED
@@ -5,6 +5,8 @@
5
5
  #
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
7
  require 'pry'
8
+ require 'memory_logger'
9
+
8
10
  SPEC_PATH = File.dirname(__FILE__)
9
11
  Dir[File.join(SPEC_PATH, "support/**/*.rb")].each{|f| require f}
10
12
 
@@ -22,4 +24,5 @@ RSpec.configure do |config|
22
24
  config.order = 'random'
23
25
  end
24
26
 
27
+ require 'rake'
25
28
  require File.join(File.dirname(__FILE__), "../lib/daemon_objects.rb")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daemon_objects
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-02-17 00:00:00.000000000 Z
12
+ date: 2014-11-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: daemons
@@ -107,6 +107,22 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: memory_logger
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.0.3
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.0.3
110
126
  description: ! ' A light-weight approach to creating and managing daemons in an object-oriented
111
127
  way. Supports any type of daemon, but provides additional support for consuming
112
128
  AMQP queues. '
@@ -143,7 +159,6 @@ files:
143
159
  - spec/lib/daemon_objects/runner_spec.rb
144
160
  - spec/lib/daemon_objects_spec.rb
145
161
  - spec/spec_helper.rb
146
- - spec/support/stub_logger.rb
147
162
  homepage: http://github.com/craigisrael/daemon_objects
148
163
  licenses:
149
164
  - MIT
@@ -178,4 +193,3 @@ test_files:
178
193
  - spec/lib/daemon_objects/runner_spec.rb
179
194
  - spec/lib/daemon_objects_spec.rb
180
195
  - spec/spec_helper.rb
181
- - spec/support/stub_logger.rb
@@ -1,23 +0,0 @@
1
- class StubLogger
2
-
3
- attr_accessor :logger
4
-
5
- def method_missing(sym, *args, &block)
6
- logger.send(sym, *args, &block)
7
- end
8
-
9
- def initialize
10
- @io = StringIO.new
11
-
12
- require 'logger'
13
- @logger = Logger.new(@io)
14
- end
15
-
16
- def logged_output
17
- @io.string
18
- end
19
-
20
- def messages
21
- logged_output.split("\n")
22
- end
23
- end