rabbit-wq 0.2.0 → 0.3.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.
data/README.md CHANGED
@@ -69,3 +69,24 @@ Auto-scale will set up retries at the following intervals: 1 min, 5 mins, 15 min
69
69
 
70
70
  Once a worker has thrown an exception and no retry attempts are remaining, the worker is placed on
71
71
  the error queue with the exception type, message and backtraces.
72
+
73
+ ### Logging
74
+
75
+ RabbitWQ provides a work logger that is available within all workers. You must send a reference to self
76
+ so that the #object_id may be put into the log message.
77
+
78
+ RabbitWQ.work_logger.info( self, 'Some message' )
79
+
80
+ When the RabbitWQ::Worker module is mixed into a worker you can use the logging convenience methods. You
81
+ do not have to provide a refrence to self in this case.
82
+
83
+ class SomeWorker < Struct.new( :some_variable )
84
+ include RabbitWQ::Worker
85
+
86
+ def call
87
+ info( 'Some message' )
88
+ # do some work
89
+ end
90
+ end
91
+
92
+ The RabbitWQ loggers provide the following log levels: debug, info, warn, error and fatal.
data/bin/rabbit-wq CHANGED
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rabbit_wq/cli'
3
+ RabbitWQ::Cli.new( ARGV ).run
data/lib/ansi.rb ADDED
@@ -0,0 +1,36 @@
1
+ class ANSI
2
+
3
+ def self.resolve_text( color, &block )
4
+ text = nil
5
+ if block_given?
6
+ text = block.call + reset
7
+ end
8
+ "\e[#{chart[color.to_sym]}m#{text}"
9
+ end
10
+
11
+ def self.reset
12
+ "\e[0m"
13
+ end
14
+
15
+ def self.chart
16
+ {
17
+ black: 30,
18
+ red: 31,
19
+ green: 32,
20
+ yellow: 33,
21
+ blue: 34,
22
+ magenta: 35,
23
+ cyan: 36,
24
+ white: 37
25
+ }
26
+ end
27
+
28
+ chart.keys.each do |color|
29
+
30
+ define_singleton_method color do |&block|
31
+ resolve_text color, &block
32
+ end
33
+
34
+ end
35
+
36
+ end
data/lib/rabbit_wq.rb CHANGED
@@ -1,16 +1,12 @@
1
- require "rabbit_wq/version"
1
+ require 'ansi'
2
+ require 'rabbit_wq/version'
2
3
 
3
4
  module RabbitWQ
4
5
 
5
- APP_ID = "rabbit-wq"
6
- APP_NAME = "Rabbit Work Queue"
7
- DELAY_QUEUE_PREFIX = "work-delay" # TODO: Make this configurable (from ENV, or file?)
8
- DELAY_EXCHANGE_PREFIX = "work-delay" # TODO: Make this configurable (from ENV, or file?)
9
- ERROR_QUEUE = "work-error"
10
- INT = "INT"
11
- QUEUE = "work" # TODO: Make this configurable (from ENV, or file?)
6
+ APP_ID = 'rabbit-wq'
7
+ APP_NAME = 'Rabbit Work Queue'
8
+ INT = 'INT'
12
9
  VERSION_COPYRIGHT = "v#{VERSION} \u00A9#{Time.now.year}"
13
- WORK_EXCHANGE = "work" # TODO: Make this configurable (from ENV, or file?)
14
10
 
15
11
  autoload :Command, 'rabbit_wq/command'
16
12
  autoload :Configuration, 'rabbit_wq/configuration'
@@ -18,20 +14,27 @@ module RabbitWQ
18
14
  autoload :Queues, 'rabbit_wq/queues'
19
15
  autoload :MessageHandler, 'rabbit_wq/message_handler'
20
16
  autoload :Server, 'rabbit_wq/server'
17
+ autoload :ServerDaemon, 'rabbit_wq/server_daemon'
21
18
  autoload :ServerLogging, 'rabbit_wq/server_logging'
22
19
  autoload :Work, 'rabbit_wq/work'
23
20
  autoload :Worker, 'rabbit_wq/worker'
21
+ autoload :WorkLogger, 'rabbit_wq/work_logger'
24
22
 
25
23
  def self.configuration
26
24
  @configuration ||= Configuration.new
27
25
  end
28
26
 
27
+ def self.configuration=( configuration )
28
+ @configuration = configuration
29
+ end
30
+
29
31
  def self.configure
30
32
  yield( configuration ) if block_given?
31
33
  end
32
34
 
33
35
  class << self
34
- attr_accessor :logger
36
+ attr_accessor :logger,
37
+ :work_logger
35
38
  end
36
39
 
37
40
  end
data/lib/rabbit_wq/cli.rb CHANGED
@@ -1,10 +1,13 @@
1
- require 'rubygems'
1
+ require 'rubygems'
2
2
  require 'rabbit_wq'
3
3
  require 'trollop'
4
4
  require 'yell'
5
5
 
6
6
  module RabbitWQ
7
- module Cli
7
+ class Cli
8
+
9
+ attr_reader :cmd,
10
+ :options
8
11
 
9
12
  SUB_COMMANDS = %w(
10
13
  restart
@@ -13,95 +16,114 @@ module RabbitWQ
13
16
  stop
14
17
  )
15
18
 
16
- def self.start( options )
19
+ DEFAULT_CONFIG_PATH = "/etc/#{APP_ID}/#{APP_ID}.conf"
20
+ DEFAULT_LOG_PATH = "/var/log/#{APP_ID}/#{APP_ID}.log"
21
+ DEFAULT_PID_PATH = "/var/run/#{APP_ID}/#{APP_ID}.pid"
22
+
23
+ DEFAULT_NUMBER_OF_THREADS = 1
24
+
25
+ def initialize( args )
26
+ Trollop::options do
27
+ version VERSION_COPYRIGHT
28
+ banner <<-EOS
29
+ #{APP_NAME} #{VERSION_COPYRIGHT}
30
+
31
+ Usage:
32
+ #{APP_ID} [command] [options]
33
+
34
+ commands:
35
+ #{SUB_COMMANDS.map { |sub_cmd| " #{sub_cmd}" }.join( "\n" )}
36
+
37
+ (For help with a command: #{APP_ID} [command] -h)
38
+
39
+ options:
40
+ EOS
41
+ stop_on SUB_COMMANDS
42
+ end
43
+
44
+ # Get the sub-command and its options
45
+ #
46
+ @cmd = ARGV.shift || ''
47
+ @options = case( cmd )
48
+ when "restart"
49
+ Trollop::options do
50
+ opt :config, "The path for the config file", :type => String, :short => '-c', :default => DEFAULT_CONFIG_PATH
51
+ opt :log_level, "The log level", :type => String, :default => 'info'
52
+ opt :log, "The path for the log file", :type => String, :short => '-l', :default => DEFAULT_LOG_PATH
53
+ opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
54
+ opt :threads, "The number of threads", :type => Integer, :default => DEFAULT_NUMBER_OF_THREADS, :short => '-t'
55
+ end
56
+ when "start"
57
+ Trollop::options do
58
+ opt :config, "The path for the config file", :type => String, :short => '-c', :default => DEFAULT_CONFIG_PATH
59
+ opt :interactive, "Execute the server in interactive mode", :short => '-i'
60
+ opt :log_level, "The log level", :type => String, :default => 'info'
61
+ opt :log, "The path for the log file", :type => String, :short => '-l', :default => DEFAULT_LOG_PATH
62
+ opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
63
+ opt :threads, "The number of threads", :type => Integer, :default => DEFAULT_NUMBER_OF_THREADS, :short => '-t'
64
+ end
65
+ when "status"
66
+ Trollop::options do
67
+ opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
68
+ end
69
+ when "stop"
70
+ Trollop::options do
71
+ opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
72
+ end
73
+ else
74
+ Trollop::die "unknown command #{cmd.inspect}"
75
+ end
76
+
77
+ if cmd == 'start'
78
+ unless options[:interactive]
79
+ Trollop::die( :config, "is required when running as daemon" ) unless options[:config]
80
+ Trollop::die( :log, "is required when running as daemon" ) unless options[:log]
81
+ Trollop::die( :pid, "is required when running as daemon" ) unless options[:pid]
82
+ end
83
+ end
84
+
85
+ if %w(restart status stop).include?( cmd )
86
+ Trollop::die( :pid, "is required" ) unless options[:pid]
87
+ end
88
+ end
89
+
90
+ def run
91
+ send( cmd )
92
+ end
93
+
94
+ protected
95
+
96
+ def start
17
97
  if options[:interactive]
18
- start_interactive options
98
+ start_interactive
19
99
  else
20
- start_daemon options
100
+ start_daemon
21
101
  end
22
102
  end
23
103
 
24
- def self.start_interactive( options )
25
- server = RabbitWQ::Server.new( options.merge( log: nil ) )
104
+ def start_interactive
105
+ server = RabbitWQ::Server.new( options.merge( log: nil ))
26
106
  server.start
27
107
  end
28
108
 
29
- def self.start_daemon( options )
109
+ def start_daemon
30
110
  server = RabbitWQ::ServerDaemon.new( options )
31
111
  server.start
32
112
  end
33
113
 
34
- end
35
- end
36
-
37
- DEFAULT_LOG_PATH = "/var/log/rabbit-wq/#{RabbitWQ::APP_ID}.log"
38
- DEFAULT_PID_PATH = "/var/run/rabbit-wq/#{RabbitWQ::APP_ID}.pid"
39
-
40
- global_opts = Trollop::options do
41
- version RabbitWQ::VERSION_COPYRIGHT
42
- banner <<-EOS
43
- #{RabbitWQ::APP_NAME} #{RabbitWQ::VERSION_COPYRIGHT}
44
-
45
- Usage:
46
- #{RabbitWQ::APP_ID} [command] [options]
47
-
48
- commands:
49
- #{RabbitWQ::Cli::SUB_COMMANDS.map { |cmd| " #{cmd}" }.join( "\n" )}
50
-
51
- (For help with a command: #{RabbitWQ::APP_ID} [command] -h)
114
+ def stop
115
+ server = RabbitWQ::ServerDaemon.new( options )
116
+ server.stop
117
+ end
52
118
 
53
- options:
54
- EOS
55
- stop_on RabbitWQ::Cli::SUB_COMMANDS
56
- end
119
+ def restart
120
+ stop
121
+ start_daemon
122
+ end
57
123
 
58
- # Get the sub-command and its options
59
- #
60
- cmd = ARGV.shift || ''
61
- cmd_opts = case( cmd )
62
- #when "restart"
63
- #Trollop::options do
64
- #opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
65
- #end
66
- when "start"
67
- Trollop::options do
68
- opt :interactive, "Execute the server in interactive mode", :short => '-i'
69
- opt :log_level, "The log level", :type => String, :default => 'info'
70
- opt :log, "The path for the log file", :type => String, :short => '-l', :default => DEFAULT_LOG_PATH
71
- opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
124
+ def status
125
+ RabbitWQ::ServerDaemon.new( options ).status
72
126
  end
73
- #when "status"
74
- #Trollop::options do
75
- #opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
76
- #end
77
- #when "stop"
78
- #Trollop::options do
79
- #opt :pid, "The path for the PID file", :type => String, :default => DEFAULT_PID_PATH
80
- #end
81
- else
82
- Trollop::die "unknown command #{cmd.inspect}"
83
- end
84
127
 
85
- if cmd == 'start'
86
- unless cmd_opts[:interactive]
87
- Trollop::die( :log, "is required when running as daemon" ) unless cmd_opts[:log]
88
- Trollop::die( :pid, "is required when running as daemon" ) unless cmd_opts[:pid]
89
128
  end
90
129
  end
91
-
92
- if %w(restart status stop).include?( cmd )
93
- Trollop::die( :pid, "is required" ) unless cmd_opts[:pid]
94
- end
95
-
96
- # Execute the command
97
- #
98
- case cmd
99
- when "restart"
100
- RabbitWQ::ServerDaemon.new( cmd_opts ).restart
101
- when "start"
102
- RabbitWQ::Cli.start cmd_opts
103
- when "status"
104
- RabbitWQ::ServerDaemon.new( cmd_opts ).status
105
- when "stop"
106
- RabbitWQ::ServerDaemon.new( cmd_opts ).stop
107
- end
@@ -1,13 +1,67 @@
1
+ require 'oj'
2
+
1
3
  module RabbitWQ
2
4
  class Configuration
3
5
 
4
- #def authentication_token
5
- #@authentication_token
6
- #end
6
+ def self.attributes
7
+ %w(
8
+ delayed_exchange_prefix
9
+ delayed_queue_prefix
10
+ environment_file_path
11
+ env
12
+ error_queue
13
+ time_zone
14
+ work_exchange
15
+ work_queue
16
+ )
17
+ end
18
+
19
+ attr_accessor( *attributes )
20
+
21
+ def self.from_file( file_path )
22
+ options = Oj.load( File.read( file_path ))
23
+ RabbitWQ.configuration = Configuration.new
24
+
25
+ attributes.each do |c|
26
+ if options[c]
27
+ RabbitWQ.configuration.send( :"#{c}=", options[c] )
28
+ end
29
+ end
30
+ end
31
+
32
+ def delayed_exchange_prefix
33
+ @delayed_exchange_prefix || 'work-delay'
34
+ end
35
+
36
+ def delayed_queue_prefix
37
+ @delayed_queue_prefix || 'work-delay'
38
+ end
39
+
40
+ def env
41
+ @env || 'production'
42
+ end
43
+
44
+ def error_queue
45
+ @error_queue || 'work-error'
46
+ end
47
+
48
+ def time_zone
49
+ @time_zone || 'UTC'
50
+ end
51
+
52
+ def work_exchange
53
+ @work_exchange || 'work'
54
+ end
55
+
56
+ def work_logger( log_level, path )
57
+ return if RabbitWQ.work_logger
58
+
59
+ RabbitWQ.work_logger = WorkLogger.new( log_level, path )
60
+ end
7
61
 
8
- #def authentication_token=( authentication_token )
9
- #@authentication_token = authentication_token
10
- #end
62
+ def work_queue
63
+ @work_queue || 'work'
64
+ end
11
65
 
12
66
  end
13
67
  end
@@ -12,11 +12,19 @@ module RabbitWQ
12
12
  ).each do |level|
13
13
 
14
14
  define_method level do |*messages|
15
+ return unless RabbitWQ.logger
15
16
  messages.each do |message|
16
17
  RabbitWQ.logger.send level, message
17
18
  end
18
19
  end
19
20
 
21
+ define_method "worker_#{level}" do |worker, *messages|
22
+ return unless RabbitWQ.work_logger
23
+ messages.each do |message|
24
+ RabbitWQ.work_logger.send level, worker, message
25
+ end
26
+ end
27
+
20
28
  end
21
29
 
22
30
  end
@@ -11,6 +11,8 @@ module RabbitWQ
11
11
  REQUEUE = true
12
12
 
13
13
  def call( options )
14
+ Time.zone = RabbitWQ.configuration.time_zone
15
+
14
16
  channel = options[:channel]
15
17
  delivery_info = options[:delivery_info]
16
18
  metadata = options[:metadata]
@@ -23,15 +25,18 @@ module RabbitWQ
23
25
  worker.call
24
26
  channel.ack delivery_info.delivery_tag
25
27
  rescue => e
26
- debug e.message
27
- handle_error( e, channel, delivery_info, payload, metadata )
28
+ handle_error( worker, e, channel, delivery_info, payload, metadata )
28
29
  end
29
30
 
30
31
  protected
31
32
 
32
- def handle_error( e, channel, delivery_info, payload, metadata )
33
+ def handle_error( worker, e, channel, delivery_info, payload, metadata )
33
34
  headers = metadata[:headers]
34
35
 
36
+ error_metadata = { type: e.class.name,
37
+ message: e.message,
38
+ backtrace: e.backtrace }
39
+
35
40
  if headers['retry']
36
41
  attempt = headers.fetch( 'attempt', 1 ).to_i
37
42
 
@@ -42,15 +47,15 @@ module RabbitWQ
42
47
  retry_delay = retry_delays( attempt )
43
48
  end
44
49
 
45
- Work.enqueue_payload( payload, headers.merge( delay: retry_delay, attempt: attempt + 1 ))
50
+ Work.enqueue_payload( payload, headers.merge( delay: retry_delay, attempt: attempt + 1 ).
51
+ merge( error: error_metadata ))
46
52
  channel.nack delivery_info.delivery_tag
47
53
  return
48
54
  end
49
55
  end
50
56
 
51
- Work.enqueue_error_payload( payload, error: { type: e.class.name,
52
- message: e.message,
53
- backtrace: e.backtrace } )
57
+ Work.enqueue_error_payload( payload, error: error_metadata )
58
+ worker_error( worker, error_metadata.inspect )
54
59
  channel.nack delivery_info.delivery_tag
55
60
  return
56
61
  end
@@ -10,15 +10,17 @@ module RabbitWQ
10
10
  end
11
11
 
12
12
  def channel
13
- @channel ||= mq.create_channel
13
+ @channel ||= mq.create_channel.tap do |c|
14
+ c.prefetch( 10 )
15
+ end
14
16
  end
15
17
 
16
18
  #def work_exchange
17
- #@work_exchange ||= channel.direct( WORK_EXCHANGE, durable: true )
19
+ #@work_exchange ||= channel.direct( RabbitWQ.configuration.work_exchange, durable: true )
18
20
  #end
19
21
 
20
22
  #def work_queue
21
- #@work_queue ||= channel.queue( QUEUE,
23
+ #@work_queue ||= channel.queue( RabbitWQ.configuration.work_queue,
22
24
  #durable: true ).
23
25
  #bind( work_exchange )
24
26
  #end
@@ -1,4 +1,5 @@
1
1
  require 'bunny'
2
+ require 'celluloid/autostart'
2
3
 
3
4
  module RabbitWQ
4
5
  class Server
@@ -13,7 +14,6 @@ module RabbitWQ
13
14
 
14
15
  def initialize( options )
15
16
  @options = options
16
- options[:pool_size] ||= 2 # TODO move to external configuration
17
17
 
18
18
  configure_server
19
19
  end
@@ -35,32 +35,73 @@ module RabbitWQ
35
35
  end
36
36
 
37
37
  def work_exchange
38
- @work_exchange ||= channel.direct( WORK_EXCHANGE, durable: true )
38
+ @work_exchange ||= channel.direct( config.work_exchange, durable: true )
39
39
  end
40
40
 
41
41
  def work_queue
42
- @work_queue ||= channel.queue( QUEUE,
42
+ @work_queue ||= channel.queue( config.work_queue,
43
43
  durable: true ).
44
44
  bind( work_exchange )
45
45
  end
46
46
 
47
47
  def pool
48
- @pool ||= MessageHandler.pool( size: options[:pool_size] )
48
+ @pool ||= MessageHandler.pool( size: options[:threads] )
49
49
  end
50
50
 
51
51
  def run
52
+ if options[:threads] == 1
53
+ Celluloid::Actor[:message_handler] = MessageHandler.new
54
+ end
55
+
52
56
  @work_consumer = work_queue.subscribe( manual_ack: true ) do |delivery_info, metadata, payload|
53
57
  info "LISTENER RECEIVED #{payload}"
54
58
 
55
- pool.async.call( payload: payload,
56
- delivery_info: delivery_info,
57
- metadata: metadata,
58
- channel: channel )
59
+ if options[:threads] > 1
60
+ pool.async.call( payload: payload,
61
+ delivery_info: delivery_info,
62
+ metadata: metadata,
63
+ channel: channel )
64
+ else
65
+ message_handler.call( payload: payload,
66
+ delivery_info: delivery_info,
67
+ metadata: metadata,
68
+ channel: channel )
69
+ end
59
70
  end
60
71
  end
61
72
 
73
+ def message_handler
74
+ Celluloid::Actor[:message_handler]
75
+ end
76
+
77
+ def config
78
+ RabbitWQ.configuration
79
+ end
80
+
62
81
  def configure_server
82
+ load_configuration
63
83
  initialize_loggers
84
+ load_environment
85
+ end
86
+
87
+ def load_configuration
88
+ if File.exists?( options[:config] )
89
+ options[:config_loaded] = true
90
+ Configuration.from_file( options[:config] )
91
+ end
92
+ end
93
+
94
+ def load_environment
95
+ unless environment_file_path &&
96
+ File.exists?( environment_file_path )
97
+ return
98
+ end
99
+
100
+ require environment_file_path
101
+ end
102
+
103
+ def environment_file_path
104
+ RabbitWQ.configuration.environment_file_path
64
105
  end
65
106
 
66
107
  end
@@ -0,0 +1,144 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'timeout'
4
+
5
+ module RabbitWQ
6
+ class ServerDaemon
7
+
8
+ attr_reader :name,
9
+ :options,
10
+ :pid,
11
+ :pid_path,
12
+ :script,
13
+ :timeout
14
+
15
+ def initialize( options )
16
+ @options = options
17
+ @name = options[:name] || APP_NAME
18
+ @pid_path = options[:pid] || '.'
19
+ @pid = get_pid
20
+ @timeout = options[:timeout] || 10
21
+ end
22
+
23
+ def start
24
+ abort "Process already running!" if process_exists?
25
+
26
+ pid = fork do
27
+ exit if fork
28
+ Process.setsid
29
+ exit if fork
30
+ store_pid( Process.pid )
31
+ File.umask 0000
32
+ redirect_output!
33
+ run
34
+ end
35
+
36
+ Process.waitpid( pid )
37
+ end
38
+
39
+ def run
40
+ Server.new( options ).start
41
+ end
42
+
43
+ def stop
44
+ kill_process
45
+ FileUtils.rm pid_path
46
+ end
47
+
48
+ def status
49
+ out = "#{APP_NAME} "
50
+ if process_exists?
51
+ out << "process running with PID: #{pid}"
52
+ else
53
+ out << "process does not exist"
54
+ end
55
+ $stdout.puts out
56
+ end
57
+
58
+ protected
59
+
60
+ #def create_pid( pid )
61
+ def store_pid( pid )
62
+ File.open( pid_path, 'w' ) do |f|
63
+ f.puts pid
64
+ end
65
+ rescue => e
66
+ $stderr.puts "Unable to open #{pid_path} for writing:\n\t(#{e.class}) #{e.message}"
67
+ exit!
68
+ end
69
+
70
+ def get_pid
71
+ return nil unless File.exists?( pid_path )
72
+ pid = nil
73
+ File.open( @pid_path, 'r' ) do |f|
74
+ pid = f.readline.to_s.gsub( /[^0-9]/, '' )
75
+ end
76
+ pid.to_i
77
+ rescue Errno::ENOENT
78
+ nil
79
+ end
80
+
81
+ def remove_pidfile
82
+ File.unlink( pid_path )
83
+ rescue => e
84
+ $stderr.puts "Unable to unlink #{pid_path}:\n\t(#{e.class}) #{e.message}"
85
+ exit
86
+ end
87
+
88
+ def kill_process
89
+ abort "#{APP_NAME} process is not running" unless process_exists?
90
+ $stdout.write "Attempting to stop #{APP_NAME} process #{pid}..."
91
+ Process.kill INT, pid
92
+ iteration_num = 0
93
+ while process_exists? && iteration_num < 10
94
+ sleep 1
95
+ $stdout.write "."
96
+ iteration_num += 1
97
+ end
98
+ if process_exists?
99
+ $stderr.puts "\nFailed to stop #{APP_NAME} process #{pid}"
100
+ else
101
+ $stdout.puts "\nSuccessfuly stopped #{APP_NAME} process #{pid}"
102
+ end
103
+ rescue Errno::EPERM
104
+ $stderr.puts "No permission to query #{pid}!";
105
+ end
106
+
107
+ def process_exists?
108
+ return false unless pid
109
+ Process.kill( 0, pid )
110
+ true
111
+ rescue Errno::ESRCH, TypeError # "PID is NOT running or is zombied
112
+ false
113
+ rescue Errno::EPERM
114
+ $stderr.puts "No permission to query #{pid}!";
115
+ false
116
+ end
117
+
118
+ def redirect_output!
119
+ if log_path = options[:log]
120
+ #puts "redirecting to log"
121
+ # if the log directory doesn't exist, create it
122
+ FileUtils.mkdir_p( File.dirname( log_path ), :mode => 0755 )
123
+ # touch the log file to create it
124
+ FileUtils.touch( log_path )
125
+ # Set permissions on the log file
126
+ File.chmod( 0644, log_path )
127
+ # Reopen $stdout (NOT +STDOUT+) to start writing to the log file
128
+ $stdout.reopen( log_path, 'a' )
129
+ # Redirect $stderr to $stdout
130
+ $stderr.reopen $stdout
131
+ $stdout.sync = true
132
+ else
133
+ #puts "redirecting to /dev/null"
134
+ # We're not bothering to sync if we're dumping to /dev/null
135
+ # because /dev/null doesn't care about buffered output
136
+ $stdin.reopen '/dev/null'
137
+ $stdout.reopen '/dev/null', 'a'
138
+ $stderr.reopen $stdout
139
+ end
140
+ log_path = options[:log] ? options[:log] : '/dev/null'
141
+ end
142
+
143
+ end
144
+ end
@@ -8,23 +8,16 @@ module RabbitWQ
8
8
  protected
9
9
 
10
10
  def initialize_loggers
11
- if options[:interactive] || options[:log].nil? || options[:log].empty?
12
- RabbitWQ.logger = Yell.new do |l|
13
- l.level = log_level
14
- l.adapter $stdout, :level => [:debug, :info, :warn]
15
- l.adapter $stderr, :level => [:error, :fatal]
16
- end
17
- else
18
- RabbitWQ.logger = Yell.new do |l|
19
- l.level = log_level
20
- l.adapter :file, options[:log]
21
- end
22
-
23
- Celluloid.logger = Yell.new do |l|
24
- l.level = :info
25
- l.adapter :file, File.join( File.dirname( options[:log] ), "#{APP_ID}-celluloid.log" )
26
- end
11
+ RabbitWQ.logger = Yell.new do |l|
12
+ l.level = log_level
13
+ l.adapter $stdout, :level => [:debug, :info, :warn]
14
+ l.adapter $stderr, :level => [:error, :fatal]
27
15
  end
16
+
17
+ #Celluloid.logger = Yell.new do |l|
18
+ #l.level = :info
19
+ #l.adapter :file, File.join( File.dirname( options[:log] ), "#{APP_ID}-celluloid.log" )
20
+ #end
28
21
  end
29
22
 
30
23
  def log_startup
@@ -37,12 +30,16 @@ module RabbitWQ
37
30
  [
38
31
  "",
39
32
  "***",
40
- "* #{RabbitWQ::APP_NAME} started",
33
+ "* #{APP_NAME} started",
41
34
  "*",
42
35
  "* #{VERSION_COPYRIGHT}",
36
+ "*",
37
+ "* Configuration:",
38
+ (options[:config_loaded] ? "* file: #{options[:config]}" : nil),
39
+ RabbitWQ::Configuration.attributes.map { |a| "* #{a}: #{RabbitWQ.configuration.send( a )}" },
40
+ "*",
43
41
  "***",
44
- "",
45
- ]
42
+ ].flatten.reject( &:nil? )
46
43
  end
47
44
 
48
45
  def log_level
@@ -1,3 +1,3 @@
1
1
  module RabbitWQ
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -12,37 +12,80 @@ module RabbitWQ
12
12
  delay = options.delete( :delay )
13
13
  delay = nil if delay && delay < 5000
14
14
 
15
- mq = ::Bunny.new.tap { |bunny| bunny.start }
16
- channel = mq.create_channel
17
-
18
15
  if delay
19
- delay_x = channel.direct( "#{DELAY_EXCHANGE_PREFIX}-#{delay}ms", durable: true )
20
- work_x = channel.direct( WORK_EXCHANGE, durable: true )
16
+ with_channel do |channel|
17
+ delay_x = channel.direct( "#{RabbitWQ.configuration.delayed_exchange_prefix}-#{delay}ms", durable: true )
18
+ work_x = channel.direct( RabbitWQ.configuration.work_exchange, durable: true )
21
19
 
22
- channel.queue( "#{DELAY_QUEUE_PREFIX}-#{delay}ms",
23
- durable: true,
24
- arguments: { "x-dead-letter-exchange" => work_x.name,
25
- "x-message-ttl" => delay } ).
26
- bind( delay_x )
20
+ channel.queue( "#{RabbitWQ.configuration.delayed_queue_prefix}-#{delay}ms",
21
+ durable: true,
22
+ arguments: { "x-dead-letter-exchange" => work_x.name,
23
+ "x-message-ttl" => delay } ).
24
+ bind( delay_x )
25
+
26
+ delay_x.publish( payload, durable: true,
27
+ content_type: 'application/yaml',
28
+ headers: options )
29
+ end
27
30
 
28
- delay_x.publish( payload, durable: true,
29
- headers: options )
30
31
  return
31
32
  end
32
33
 
33
- work_q = channel.queue( QUEUE, durable: true )
34
- work_q.publish( payload, durable: true,
35
- headers: options )
34
+ with_work_exchange do |work_x, work_q|
35
+ work_x.publish( payload, durable: true,
36
+ content_type: 'application/yaml',
37
+ headers: options )
38
+ end
36
39
  end
37
40
 
38
41
  def self.enqueue_error_payload( payload, options={} )
39
- mq = ::Bunny.new.tap { |bunny| bunny.start }
40
- channel = mq.create_channel
42
+ with_channel do |channel|
43
+ error_q = channel.queue( RabbitWQ.configuration.error_queue, durable: true )
44
+ error_q.publish( payload, durable: true,
45
+ content_type: 'application/yaml',
46
+ headers: options )
47
+ end
48
+ end
49
+
50
+ def self.with_work_exchange
51
+ with_channel do |channel|
52
+ begin
53
+ exchange = channel.direct( RabbitWQ.configuration.work_exchange, durable: true )
54
+ channel.queue( RabbitWQ.configuration.work_queue, durable: true ).tap do |q|
55
+ q.bind( exchange )
56
+ yield exchange, q
57
+ end
58
+ ensure
59
+ end
60
+ end
61
+ end
41
62
 
42
- error_q = channel.queue( ERROR_QUEUE, durable: true )
43
- error_q.publish( payload, durable: true,
44
- headers: options )
63
+ def self.with_channel
64
+ Bunny.new.tap do |b|
65
+ b.start
66
+ begin
67
+ b.create_channel.tap do |c|
68
+ yield c
69
+ end
70
+ ensure
71
+ b.stop
72
+ end
73
+ end
45
74
  end
46
75
 
76
+ #def self.with_exchange
77
+ #Bunny.new.tap do |b|
78
+ #b.start
79
+ #begin
80
+ #b.create_channel.tap do |c|
81
+ #queue = c.queue( 'replication', durable: true )
82
+ #yield c.default_exchange, queue.name
83
+ #end
84
+ #ensure
85
+ #b.stop
86
+ #end
87
+ #end
88
+ #end
89
+
47
90
  end
48
91
  end
@@ -0,0 +1,47 @@
1
+ require 'ansi'
2
+ require 'yell'
3
+
4
+ module RabbitWQ
5
+ class WorkLogger
6
+
7
+ def initialize( log_level, path )
8
+ @logger = Yell.new do |l|
9
+ l.level = log_level
10
+ l.adapter :file, path
11
+ end
12
+ end
13
+
14
+ %w(
15
+ debug
16
+ error
17
+ fatal
18
+ info
19
+ warn
20
+ ).each do |level|
21
+
22
+ define_method level do |*args|
23
+ worker, message = nil, nil
24
+ if args.size > 1
25
+ worker, message = *args
26
+ else
27
+ message = args.first
28
+ end
29
+
30
+ if worker
31
+ logger.send( level, "[" + ANSI.cyan { "#{worker.class.name}:#{worker.object_id}" } + "] #{message}" )
32
+ else
33
+ logger.send( level, message )
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ def level=( l )
40
+ end
41
+
42
+ protected
43
+
44
+ attr_reader :logger
45
+
46
+ end
47
+ end
@@ -1,6 +1,27 @@
1
1
  module RabbitWQ
2
2
  module Worker
3
3
 
4
+ def self.included( other_module )
5
+ other_module.class_eval do
6
+ %w(
7
+ debug
8
+ error
9
+ fatal
10
+ info
11
+ warn
12
+ ).each do |level|
13
+
14
+ define_method level do |*messages|
15
+ return unless RabbitWQ.logger
16
+ messages.each do |message|
17
+ RabbitWQ.work_logger.send level, self, message
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
4
25
  def work( options={} )
5
26
  RabbitWQ::Work.enqueue( self, options )
6
27
  self
data/rabbit-wq.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_dependency "bunny", "~> 1"
25
25
  spec.add_dependency "celluloid", "~> 0"
26
+ spec.add_dependency "oj", "~> 2"
26
27
  spec.add_dependency "trollop", "~> 2"
27
28
  spec.add_dependency "yell", "~> 1"
28
29
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabbit-wq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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: 2013-11-29 00:00:00.000000000 Z
12
+ date: 2013-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -75,6 +75,22 @@ dependencies:
75
75
  - - ~>
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: oj
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '2'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '2'
78
94
  - !ruby/object:Gem::Dependency
79
95
  name: trollop
80
96
  requirement: !ruby/object:Gem::Requirement
@@ -118,11 +134,11 @@ files:
118
134
  - .gitignore
119
135
  - .ruby-gemset
120
136
  - .ruby-version
121
- - Gemfile
122
137
  - LICENSE.txt
123
138
  - README.md
124
139
  - Rakefile
125
140
  - bin/rabbit-wq
141
+ - lib/ansi.rb
126
142
  - lib/rabbit-wq.rb
127
143
  - lib/rabbit_wq.rb
128
144
  - lib/rabbit_wq/cli.rb
@@ -132,9 +148,11 @@ files:
132
148
  - lib/rabbit_wq/message_handler.rb
133
149
  - lib/rabbit_wq/queues.rb
134
150
  - lib/rabbit_wq/server.rb
151
+ - lib/rabbit_wq/server_daemon.rb
135
152
  - lib/rabbit_wq/server_logging.rb
136
153
  - lib/rabbit_wq/version.rb
137
154
  - lib/rabbit_wq/work.rb
155
+ - lib/rabbit_wq/work_logger.rb
138
156
  - lib/rabbit_wq/worker.rb
139
157
  - rabbit-wq.gemspec
140
158
  homepage: ''
@@ -150,18 +168,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
168
  - - ! '>='
151
169
  - !ruby/object:Gem::Version
152
170
  version: '0'
153
- segments:
154
- - 0
155
- hash: -1504652558847638442
156
171
  required_rubygems_version: !ruby/object:Gem::Requirement
157
172
  none: false
158
173
  requirements:
159
174
  - - ! '>='
160
175
  - !ruby/object:Gem::Version
161
176
  version: '0'
162
- segments:
163
- - 0
164
- hash: -1504652558847638442
165
177
  requirements: []
166
178
  rubyforge_project:
167
179
  rubygems_version: 1.8.25
@@ -169,3 +181,4 @@ signing_key:
169
181
  specification_version: 3
170
182
  summary: A work queue built on RabbitMQ and Celluloid.
171
183
  test_files: []
184
+ has_rdoc:
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in brer_rabbit.gemspec
4
- gemspec