droid 0.9.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/bin/conf +31 -0
- data/lib/droid.rb +531 -0
- data/lib/droid/sync.rb +0 -0
- data/lib/heroku_droid.rb +86 -0
- data/lib/local_stats.rb +143 -0
- data/lib/memcache_cluster.rb +127 -0
- data/lib/stats.rb +30 -0
- data/lib/utilization.rb +90 -0
- data/speaker/droid_speaker.rb +79 -0
- data/speaker/speak +33 -0
- data/speaker/test.rb +58 -0
- data/test/base.rb +43 -0
- data/test/droid_test.rb +53 -0
- data/test/heroku_droid_test.rb +42 -0
- data/test/wait_for_port_test.rb +23 -0
- data/vendor/logger_client/Rakefile +53 -0
- data/vendor/logger_client/init.rb +1 -0
- data/vendor/logger_client/lib/logger_client.rb +210 -0
- data/vendor/logger_client/test.rb +18 -0
- metadata +115 -0
| @@ -0,0 +1,79 @@ | |
| 1 | 
            +
            $LOAD_PATH.unshift File.dirname(__FILE__) + '/../vendor/json_pure-1.1.3/lib'
         | 
| 2 | 
            +
            require 'json'
         | 
| 3 | 
            +
            require 'drb'
         | 
| 4 | 
            +
            require 'timeout'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class DroidSpeaker
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            	def self.drb_connect(port)
         | 
| 9 | 
            +
            		DRb.start_service()
         | 
| 10 | 
            +
            		@drb = DRbObject.new(nil,"druby://localhost:#{port}")
         | 
| 11 | 
            +
            	end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            	def self.drb_cast(topic, payload, &p) 
         | 
| 14 | 
            +
            		@drb.publish(topic, payload, &p)
         | 
| 15 | 
            +
            	end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            	def self.drb_call(topic, payload)
         | 
| 18 | 
            +
            		## this is the best stupid trick I could think of the force blocking
         | 
| 19 | 
            +
            		lock = Thread.new { sleep 9999 }
         | 
| 20 | 
            +
            		@r = nil
         | 
| 21 | 
            +
            		@drb.publish(topic, payload) do |result|
         | 
| 22 | 
            +
            			@r = result
         | 
| 23 | 
            +
            			lock.kill
         | 
| 24 | 
            +
            		end
         | 
| 25 | 
            +
            		lock.join
         | 
| 26 | 
            +
            		@r
         | 
| 27 | 
            +
            	end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            	def self.send(topic, payload, &p)
         | 
| 30 | 
            +
            		i = new
         | 
| 31 | 
            +
            		i.send(topic, payload, &p)
         | 
| 32 | 
            +
            		i.flush
         | 
| 33 | 
            +
            	end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            	def self.start
         | 
| 36 | 
            +
            		i = new
         | 
| 37 | 
            +
            		yield(i)
         | 
| 38 | 
            +
            		i.flush
         | 
| 39 | 
            +
            	end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            	def initialize
         | 
| 42 | 
            +
            		@message = nil
         | 
| 43 | 
            +
            		@callback = nil
         | 
| 44 | 
            +
            	end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            	def send(topic, payload, &blk)
         | 
| 47 | 
            +
            		if defined?(Log) and event_id = Log.default_options[:event_id]
         | 
| 48 | 
            +
            			payload.merge!(:event_hash => event_id)
         | 
| 49 | 
            +
            		end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            		attrs = { :topic => topic, :payload => payload }
         | 
| 52 | 
            +
            		if blk # this message should be replied
         | 
| 53 | 
            +
            			attrs.merge!(:has_reply => true)
         | 
| 54 | 
            +
            			@callback = blk
         | 
| 55 | 
            +
            		end
         | 
| 56 | 
            +
            		@message = attrs
         | 
| 57 | 
            +
            	end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            	class MessageError < RuntimeError; end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            	def flush
         | 
| 62 | 
            +
            		out = ''
         | 
| 63 | 
            +
            		IO.popen("#{File.dirname(__FILE__)}/../speaker/speak 2>/dev/null", 'w+') do |io|
         | 
| 64 | 
            +
            			io.puts @message.to_json
         | 
| 65 | 
            +
            			out = io.read
         | 
| 66 | 
            +
            		end
         | 
| 67 | 
            +
            		begin
         | 
| 68 | 
            +
            			response = JSON.parse(out)
         | 
| 69 | 
            +
            			response.delete('event_hash')
         | 
| 70 | 
            +
            			@callback.call(response) if @callback
         | 
| 71 | 
            +
            		rescue JSON::ParserError
         | 
| 72 | 
            +
            			if out.include?('Timeout::Error')
         | 
| 73 | 
            +
            				raise Timeout::Error, "Timed out while waiting for a response for #{@message[:topic]}"
         | 
| 74 | 
            +
            			else
         | 
| 75 | 
            +
            				raise MessageError, out
         | 
| 76 | 
            +
            			end
         | 
| 77 | 
            +
            		end
         | 
| 78 | 
            +
            	end
         | 
| 79 | 
            +
            end
         | 
    
        data/speaker/speak
    ADDED
    
    | @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            begin
         | 
| 4 | 
            +
            	$:.unshift '/usr/local/droid/lib'
         | 
| 5 | 
            +
            	require 'heroku_droid'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            	msg = JSON.load(gets)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            	Timeout.timeout(10) do
         | 
| 10 | 
            +
            		HerokuDroid.new('Droid Speaker', :standard_topics => false) do |droid|
         | 
| 11 | 
            +
            			if msg['has_reply']
         | 
| 12 | 
            +
            				@event_hash = msg['payload']['event_hash'] ||= Droid::Basic.new(droid).event_hash
         | 
| 13 | 
            +
            				droid.listen4('event.error') do |d|
         | 
| 14 | 
            +
            					if d.headers[:event_hash] == @event_hash
         | 
| 15 | 
            +
            						puts "Message #{msg['topic']} failed: see event #{@event_hash}"
         | 
| 16 | 
            +
            						Droid.stop_safe
         | 
| 17 | 
            +
            					end
         | 
| 18 | 
            +
            				end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            				droid.publish(msg['topic'], msg['payload']) do |d|
         | 
| 21 | 
            +
            					puts d.params.to_json
         | 
| 22 | 
            +
            					Droid.stop_safe
         | 
| 23 | 
            +
            				end
         | 
| 24 | 
            +
            			else
         | 
| 25 | 
            +
            				droid.publish(msg['topic'], msg['payload'])
         | 
| 26 | 
            +
            				puts({}.to_json)
         | 
| 27 | 
            +
            				Droid.stop_safe
         | 
| 28 | 
            +
            			end
         | 
| 29 | 
            +
            		end
         | 
| 30 | 
            +
            	end
         | 
| 31 | 
            +
            rescue Object => e
         | 
| 32 | 
            +
            	puts "#{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
         | 
| 33 | 
            +
            end
         | 
    
        data/speaker/test.rb
    ADDED
    
    | @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            Thread.new do
         | 
| 2 | 
            +
            	require File.dirname(__FILE__) + '/../lib/heroku_droid'
         | 
| 3 | 
            +
            	HerokuDroid.new('Droid speaker test') do |droid|
         | 
| 4 | 
            +
            		droid.listen4('speaker.test') do |msg|
         | 
| 5 | 
            +
            			if msg['marco'] == 1
         | 
| 6 | 
            +
            				puts "Got 'marco', returning 'polo'..."
         | 
| 7 | 
            +
            				msg.reply('polo' => 1)
         | 
| 8 | 
            +
            			else
         | 
| 9 | 
            +
            				puts "Bad payload"
         | 
| 10 | 
            +
            				msg.reply('error' => 1)
         | 
| 11 | 
            +
            			end
         | 
| 12 | 
            +
            		end
         | 
| 13 | 
            +
            		droid.listen4('speaker.raise') do |msg|
         | 
| 14 | 
            +
            			raise "droid raised exception"
         | 
| 15 | 
            +
            		end
         | 
| 16 | 
            +
            	end
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            sleep 2
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            require File.dirname(__FILE__) + '/droid_speaker'
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            puts "\nTest 1: Sending simple message"
         | 
| 24 | 
            +
            DroidSpeaker.send('fire.and.forget', 'val' => 1)
         | 
| 25 | 
            +
            puts "\tSuccess!"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            puts "\nTest 2: Sending and receiving response"
         | 
| 28 | 
            +
            DroidSpeaker.send('speaker.test', 'marco' => 1) do |r|
         | 
| 29 | 
            +
            	if r['polo'] == 1
         | 
| 30 | 
            +
            		puts "\tSuccess!"
         | 
| 31 | 
            +
            	else
         | 
| 32 | 
            +
            		puts "\tFailed, result: #{r.inspect}"
         | 
| 33 | 
            +
            	end
         | 
| 34 | 
            +
            end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            puts "\nTest 3: Exceptions"
         | 
| 37 | 
            +
            begin
         | 
| 38 | 
            +
            	DroidSpeaker.send('speaker.raise', {}) do |r|
         | 
| 39 | 
            +
            		puts "\tFailed, expected exception"
         | 
| 40 | 
            +
            	end
         | 
| 41 | 
            +
            rescue DroidSpeaker::MessageError => e
         | 
| 42 | 
            +
            	if e.message =~ /event \w+/
         | 
| 43 | 
            +
            		puts "\tSuccess!"
         | 
| 44 | 
            +
            	else
         | 
| 45 | 
            +
            		puts "\tFailed, droid speaker raised #{e.message}"
         | 
| 46 | 
            +
            	end
         | 
| 47 | 
            +
            end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            puts "\nTest 4: Timeout"
         | 
| 50 | 
            +
            begin
         | 
| 51 | 
            +
            	DroidSpeaker.send('dont.reply', {}) do |r|
         | 
| 52 | 
            +
            		puts "\tFailed, didnt expect a response"
         | 
| 53 | 
            +
            	end
         | 
| 54 | 
            +
            rescue Timeout::Error
         | 
| 55 | 
            +
            	puts "\tSuccess!"
         | 
| 56 | 
            +
            rescue Exception => e
         | 
| 57 | 
            +
            	puts "\tFailed, expected Timeout::Error, got #{e.class.name}"
         | 
| 58 | 
            +
            end
         | 
    
        data/test/base.rb
    ADDED
    
    | @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            require 'rush'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            def publish(message, payload={})
         | 
| 4 | 
            +
            	Droid.new('Sender', RabbitmqSetup.credentials) do |d|
         | 
| 5 | 
            +
            		d.publish(message, payload)
         | 
| 6 | 
            +
            		Droid.stop_safe
         | 
| 7 | 
            +
            	end
         | 
| 8 | 
            +
            	sleep(0.1)
         | 
| 9 | 
            +
            end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            $exchange = nil
         | 
| 12 | 
            +
            $mutex = Mutex.new
         | 
| 13 | 
            +
            def exchange
         | 
| 14 | 
            +
            	$mutex.synchronize { $exchange }
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
            def save_in_exchange(v)
         | 
| 17 | 
            +
            	$mutex.synchronize { $exchange = v }
         | 
| 18 | 
            +
            end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            module RabbitmqSetup
         | 
| 21 | 
            +
            	extend self
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            	def credentials
         | 
| 24 | 
            +
            		{
         | 
| 25 | 
            +
            			:vhost => '/test',
         | 
| 26 | 
            +
            			:host	=> 'localhost',
         | 
| 27 | 
            +
            			:user	=> 'test',
         | 
| 28 | 
            +
            			:pass	=> 'test'
         | 
| 29 | 
            +
            		}
         | 
| 30 | 
            +
            	end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            	def run_rabbitmqctl(command)
         | 
| 33 | 
            +
            		Rush::Box.new.bash "rabbitmqctl #{command}"
         | 
| 34 | 
            +
            	rescue Rush::BashFailed => e
         | 
| 35 | 
            +
            		raise unless e.message.match(/already_exists/)
         | 
| 36 | 
            +
            	end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            	def run
         | 
| 39 | 
            +
            		run_rabbitmqctl("add_vhost #{credentials[:vhost]}")
         | 
| 40 | 
            +
            		run_rabbitmqctl("add_user #{credentials[:user]} #{credentials[:pass]}")
         | 
| 41 | 
            +
            		run_rabbitmqctl("map_user_vhost #{credentials[:user]} #{credentials[:vhost]}")
         | 
| 42 | 
            +
            	end
         | 
| 43 | 
            +
            end
         | 
    
        data/test/droid_test.rb
    ADDED
    
    | @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/../lib/droid'
         | 
| 2 | 
            +
            require File.dirname(__FILE__) + '/base'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'rubygems'
         | 
| 5 | 
            +
            require 'thread'
         | 
| 6 | 
            +
            require 'bacon'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            RabbitmqSetup.run
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            Bacon.summary_on_exit
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Thread.new do
         | 
| 13 | 
            +
            	Droid.new('Test Droid', RabbitmqSetup.credentials) do |d|
         | 
| 14 | 
            +
            		d.on_error do |msg, e|
         | 
| 15 | 
            +
            			save_in_exchange(e)
         | 
| 16 | 
            +
            		end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            		d.before_filter do |msg|
         | 
| 19 | 
            +
            			if to_save = msg[:save_in_before_filter]
         | 
| 20 | 
            +
            				save_in_exchange(to_save)
         | 
| 21 | 
            +
            			end
         | 
| 22 | 
            +
            		end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            		d.listen4('save.msg') do |msg|
         | 
| 25 | 
            +
            			save_in_exchange(msg)
         | 
| 26 | 
            +
            		end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            		d.listen4('do.nothing') do |msg|
         | 
| 29 | 
            +
            		end
         | 
| 30 | 
            +
            	end
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            sleep(2) # wait until droid is up
         | 
| 34 | 
            +
            describe Droid do
         | 
| 35 | 
            +
            	before do
         | 
| 36 | 
            +
            		save_in_exchange(nil)
         | 
| 37 | 
            +
            	end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            	it "sends and receives simple messages" do
         | 
| 40 | 
            +
            		publish('save.msg')
         | 
| 41 | 
            +
            		exchange.should.not == nil
         | 
| 42 | 
            +
            	end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            	it "sends messages with params" do
         | 
| 45 | 
            +
            		publish('save.msg', :test => 1)
         | 
| 46 | 
            +
            		exchange[:test].should == 1
         | 
| 47 | 
            +
            	end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            	it "accepts before filters" do
         | 
| 50 | 
            +
            		publish('do.nothing', :save_in_before_filter => 42)
         | 
| 51 | 
            +
            		exchange.should == 42
         | 
| 52 | 
            +
            	end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/../lib/heroku_droid'
         | 
| 2 | 
            +
            require File.dirname(__FILE__) + '/base'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'rubygems'
         | 
| 5 | 
            +
            require 'thread'
         | 
| 6 | 
            +
            require 'bacon'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            Bacon.summary_on_exit
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            Thread.new do
         | 
| 11 | 
            +
            	HerokuDroid.new('Test Heroku Droid') do |d|
         | 
| 12 | 
            +
            		d.stats do
         | 
| 13 | 
            +
            			"some stats"
         | 
| 14 | 
            +
            		end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            		d.listen4('pong') do |msg|
         | 
| 17 | 
            +
            			save_in_exchange('worked') if msg['notes'] == 'some stats'
         | 
| 18 | 
            +
            		end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            		d.listen4('save.msg') do |msg|
         | 
| 21 | 
            +
            			save_in_exchange(msg)
         | 
| 22 | 
            +
            		end
         | 
| 23 | 
            +
            	end
         | 
| 24 | 
            +
            end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            sleep(2) # wait until droid is up
         | 
| 27 | 
            +
            describe HerokuDroid do
         | 
| 28 | 
            +
            	before do
         | 
| 29 | 
            +
            		save_in_exchange(nil)
         | 
| 30 | 
            +
            	end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            	it "sends and receives simple messages" do
         | 
| 33 | 
            +
            		publish('save.msg')
         | 
| 34 | 
            +
            		exchange.should.not == nil
         | 
| 35 | 
            +
            	end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            	it "responds to ping with a pong message and instances/droid stats filled in" do
         | 
| 38 | 
            +
            		publish('ping')
         | 
| 39 | 
            +
            		sleep 3
         | 
| 40 | 
            +
            		exchange.should == 'worked'
         | 
| 41 | 
            +
            	end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/../lib/droid'
         | 
| 2 | 
            +
            require File.dirname(__FILE__) + '/base'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'rubygems'
         | 
| 5 | 
            +
            require 'thread'
         | 
| 6 | 
            +
            require 'bacon'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            Bacon.summary_on_exit
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            Thread.new do
         | 
| 11 | 
            +
            	sleep 2
         | 
| 12 | 
            +
            	TCPServer.new('localhost', 20_001).accept.close
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            describe Droid do
         | 
| 16 | 
            +
            	it "waits for the rabbitmq server to come up before opening the amqp connection" do
         | 
| 17 | 
            +
            		start = Time.now
         | 
| 18 | 
            +
            		Droid.wait_for_tcp_port('localhost', 20_001)
         | 
| 19 | 
            +
            		finish = Time.now
         | 
| 20 | 
            +
            		(finish - start).should > 1
         | 
| 21 | 
            +
            		(finish - start).should < 3
         | 
| 22 | 
            +
            	end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            require 'rake'
         | 
| 2 | 
            +
            require 'spec/rake/spectask'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            desc "Run all specs"
         | 
| 5 | 
            +
            Spec::Rake::SpecTask.new('spec') do |t|
         | 
| 6 | 
            +
            	t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
         | 
| 7 | 
            +
            	t.spec_files = FileList['spec/*_spec.rb']
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            task :default => :spec
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            ###
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require 'rake'
         | 
| 15 | 
            +
            require 'rake/testtask'
         | 
| 16 | 
            +
            require 'rake/clean'
         | 
| 17 | 
            +
            require 'rake/gempackagetask'
         | 
| 18 | 
            +
            require 'rake/rdoctask'
         | 
| 19 | 
            +
            require 'fileutils'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            version = "0.1"
         | 
| 22 | 
            +
            name = "logger_client"
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            spec = Gem::Specification.new do |s|
         | 
| 25 | 
            +
            	s.name = name
         | 
| 26 | 
            +
            	s.version = version
         | 
| 27 | 
            +
            	s.summary = "Client for Heroku logger"
         | 
| 28 | 
            +
            	s.author = "Pedro Belo"
         | 
| 29 | 
            +
            	s.email = "pedro@heroku.com"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            	s.platform = Gem::Platform::RUBY
         | 
| 32 | 
            +
            	s.has_rdoc = false
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            	s.files = %w(Rakefile init.rb) + Dir.glob("{lib,spec}/**/*")
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            	s.require_path = "lib"
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            	s.add_dependency('rest-client', '>=0.6.2')
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            Rake::GemPackageTask.new(spec) do |p|
         | 
| 42 | 
            +
            	p.need_tar = true if RUBY_PLATFORM !~ /mswin/
         | 
| 43 | 
            +
            end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            task :install => [ :package ] do
         | 
| 46 | 
            +
            	sh %{sudo gem install pkg/#{name}-#{version}.gem}
         | 
| 47 | 
            +
            end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            task :uninstall => [ :clean ] do
         | 
| 50 | 
            +
            	sh %{sudo gem uninstall #{name}}
         | 
| 51 | 
            +
            end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            CLEAN.include [ 'pkg', '*.gem', '.config' ]
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/lib/logger_client'
         | 
| @@ -0,0 +1,210 @@ | |
| 1 | 
            +
            # Logger Client
         | 
| 2 | 
            +
            # Use as a gem or Rails plugin. Example configuration:
         | 
| 3 | 
            +
            # 
         | 
| 4 | 
            +
            # Log.configure do |config|
         | 
| 5 | 
            +
            #   config.component = 'core'
         | 
| 6 | 
            +
            #   config.instance = 'userapps.123'
         | 
| 7 | 
            +
            #   config.failsafe = :file
         | 
| 8 | 
            +
            # end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require 'rubygems'
         | 
| 11 | 
            +
            require 'time'
         | 
| 12 | 
            +
            require 'syslog'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            module Log; end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            class Log::InvalidConfiguration < RuntimeError
         | 
| 17 | 
            +
            	def message
         | 
| 18 | 
            +
            		"Invalid component. Configure with Log.configure { |c| c.component = 'myComponent' }"
         | 
| 19 | 
            +
            	end
         | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            class Log::Config
         | 
| 23 | 
            +
            	def initialize
         | 
| 24 | 
            +
            		@contents = { :console_log => true }
         | 
| 25 | 
            +
            	end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            	def method_missing(method, value)
         | 
| 28 | 
            +
            		@contents[method.to_s.gsub(/=$/, '').to_sym] = value
         | 
| 29 | 
            +
            	end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            	def to_hash
         | 
| 32 | 
            +
            		@contents
         | 
| 33 | 
            +
            	end
         | 
| 34 | 
            +
            end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            module Log
         | 
| 37 | 
            +
            	extend self
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            	unless defined? SyslogConvertion
         | 
| 40 | 
            +
            		SyslogConvertion = {
         | 
| 41 | 
            +
            			'error'   => 3,
         | 
| 42 | 
            +
            			'warning' => 4,
         | 
| 43 | 
            +
            			'notice'  => 5,
         | 
| 44 | 
            +
            			'debug'   => 7,
         | 
| 45 | 
            +
            		}
         | 
| 46 | 
            +
            	end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            	def debug(msg, options={})
         | 
| 49 | 
            +
            		log msg, options.merge(:level => 'debug')
         | 
| 50 | 
            +
            	end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            	def notice(msg, options={})
         | 
| 53 | 
            +
            		log msg, options.merge(:level => 'notice')
         | 
| 54 | 
            +
            	end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            	def warning(msg, options={})
         | 
| 57 | 
            +
            		log msg, options.merge(:level => 'warning')
         | 
| 58 | 
            +
            	end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            	def error(msg, options={})
         | 
| 61 | 
            +
            		# Fake an exception for error messages with no exception object
         | 
| 62 | 
            +
            		# so that we get a backtrace.
         | 
| 63 | 
            +
            		if options[:exception].nil?
         | 
| 64 | 
            +
            			begin
         | 
| 65 | 
            +
            				raise StandardError, msg
         | 
| 66 | 
            +
            			rescue => error
         | 
| 67 | 
            +
            				error.backtrace.shift
         | 
| 68 | 
            +
            				options[:exception] = error
         | 
| 69 | 
            +
            			end
         | 
| 70 | 
            +
            		end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            		log msg, options.merge(:level => 'error')
         | 
| 73 | 
            +
            	end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            	##########
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            	def log(msg, options={})
         | 
| 78 | 
            +
            		console_log(msg)
         | 
| 79 | 
            +
            		syslog(msg, options)
         | 
| 80 | 
            +
            	rescue InvalidConfiguration => e
         | 
| 81 | 
            +
            		raise e
         | 
| 82 | 
            +
            	end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            	def default_error(e)
         | 
| 85 | 
            +
            		# avoid backtrace in /usr or vendor if possible
         | 
| 86 | 
            +
            		system, app = e.backtrace.partition { |b| b =~ /(^\/usr\/|vendor)/ }
         | 
| 87 | 
            +
            		reordered_backtrace = app + system
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            		# avoid "/" as the method name (we want the controller action)
         | 
| 90 | 
            +
            		row = 0
         | 
| 91 | 
            +
            		row = 1 if reordered_backtrace[row].match(/in `\/'$/)
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            		# get file and method name
         | 
| 94 | 
            +
            		begin
         | 
| 95 | 
            +
            			file, method = reordered_backtrace[row].match(/(.*):in `(.*)'$/)[1..2]
         | 
| 96 | 
            +
            			file.gsub!(/.*\//, '')
         | 
| 97 | 
            +
            			self.log "#{e.class} in #{file} #{method}: #{e.message}", :exception => e, :level => 'error'
         | 
| 98 | 
            +
            		rescue
         | 
| 99 | 
            +
            			self.log "#{e.class} in #{e.backtrace.first}: #{e.message}", :exception => e, :level => 'error'
         | 
| 100 | 
            +
            		end
         | 
| 101 | 
            +
            	end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            	def web_error(args)
         | 
| 104 | 
            +
            		e = args[:exception]
         | 
| 105 | 
            +
            		summary = "#{e.class} processing #{args[:url]}"
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            		body = []
         | 
| 108 | 
            +
            		body << "\tURL: #{args[:url]}"
         | 
| 109 | 
            +
            		body << "\tParams: #{args[:params].inspect}"
         | 
| 110 | 
            +
            		body << "\tUser: #{args[:user]}" if args[:user]
         | 
| 111 | 
            +
            		body << "\tBacktrace:"
         | 
| 112 | 
            +
            		body << "\t\t#{e.class} (#{e.message}):"
         | 
| 113 | 
            +
            		body += e.backtrace.map { |l| "\t\t#{l}" }
         | 
| 114 | 
            +
            		body = body.join("\n")
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            		log "#{summary}\n#{body}", :level => 'error'
         | 
| 117 | 
            +
            	end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            	def exception(e)
         | 
| 120 | 
            +
            		msg = "Exception #{e.class} -> #{e.message}\n"
         | 
| 121 | 
            +
            		msg += filtered_backtrace(e.backtrace)
         | 
| 122 | 
            +
            		STDERR.puts msg
         | 
| 123 | 
            +
            		Log.error e.message, :exception => e
         | 
| 124 | 
            +
            	end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            	def filtered_backtrace(backtrace)
         | 
| 127 | 
            +
            		backtrace.select do |line|
         | 
| 128 | 
            +
            			!line.match(/^\/usr/)
         | 
| 129 | 
            +
            		end.map do |line|
         | 
| 130 | 
            +
            			"   #{line}\n"
         | 
| 131 | 
            +
            		end.join
         | 
| 132 | 
            +
            	end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            	def event(name, options = {})
         | 
| 135 | 
            +
            		console_log("EVENT: #{name} begins")
         | 
| 136 | 
            +
            		result = yield
         | 
| 137 | 
            +
            		console_log("EVENT: #{name} complete")
         | 
| 138 | 
            +
            		result
         | 
| 139 | 
            +
            	end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            	def context(options)
         | 
| 142 | 
            +
            		prev_options = default_options.dup
         | 
| 143 | 
            +
            		default_options.merge!(options)
         | 
| 144 | 
            +
            		yield
         | 
| 145 | 
            +
            		@@default_options = prev_options
         | 
| 146 | 
            +
            	end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            	def configure
         | 
| 149 | 
            +
            		config = Config.new
         | 
| 150 | 
            +
            		yield(config)
         | 
| 151 | 
            +
            		set_default_options(config.to_hash)
         | 
| 152 | 
            +
            	end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
            	def set_default_options(options)
         | 
| 155 | 
            +
            		default_options.merge!(options)
         | 
| 156 | 
            +
            	end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
            	def default_options
         | 
| 159 | 
            +
            		@@default_options ||= {}
         | 
| 160 | 
            +
            	end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
            	def failsafe(params)
         | 
| 163 | 
            +
            		case default_options[:failsafe]
         | 
| 164 | 
            +
            			when :file
         | 
| 165 | 
            +
            				dir = defined?(RAILS_ROOT) ? RAILS_ROOT : '.'
         | 
| 166 | 
            +
            				File.open("#{dir}/failsafe.log", "a") do |f|
         | 
| 167 | 
            +
            					f.puts "#{Time.now} #{params[:log][:level]} : #{params[:log][:message]}"
         | 
| 168 | 
            +
            					f.puts params[:log][:addendum] if params[:log][:addendum]
         | 
| 169 | 
            +
            				end
         | 
| 170 | 
            +
            			when :console, nil
         | 
| 171 | 
            +
            				console_log(params[:log][:message])
         | 
| 172 | 
            +
            		end
         | 
| 173 | 
            +
            	end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            	def console_log(msg)
         | 
| 176 | 
            +
            		STDERR.puts "#{Time.now.iso8601} #{msg}" if default_options[:console_log]
         | 
| 177 | 
            +
            	end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
            	def syslog(msg, opts = {})
         | 
| 180 | 
            +
            		@retried = false
         | 
| 181 | 
            +
            		begin
         | 
| 182 | 
            +
            			level = SyslogConvertion[opts[:level]]
         | 
| 183 | 
            +
            			if opts[:exception]
         | 
| 184 | 
            +
            				msg += "\n" + format_syslog_exception(opts[:exception])
         | 
| 185 | 
            +
            			end
         | 
| 186 | 
            +
            			syslog_resource.log(level, '%s', msg)
         | 
| 187 | 
            +
            		rescue Exception => e
         | 
| 188 | 
            +
            			failsafe(:log => { :level => 'error', :message => "could not log to syslog: #{e.class.name} #{e.message}"})
         | 
| 189 | 
            +
            			unless @retried
         | 
| 190 | 
            +
            				@retried = true
         | 
| 191 | 
            +
            				@@syslog.close rescue nil
         | 
| 192 | 
            +
            				@@syslog = Syslog.open(default_options[:component]) rescue nil
         | 
| 193 | 
            +
            				retry
         | 
| 194 | 
            +
            			end
         | 
| 195 | 
            +
            		end
         | 
| 196 | 
            +
            	end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            	def format_syslog_exception(e)
         | 
| 199 | 
            +
            		if e.respond_to?(:backtrace)
         | 
| 200 | 
            +
            			"\t#{e.class}: #{e.message}\n" + e.backtrace.map { |t| "\t#{t}" }.join("\n")
         | 
| 201 | 
            +
            		else
         | 
| 202 | 
            +
            			"\t#{e.class}: #{e.message}"
         | 
| 203 | 
            +
            		end
         | 
| 204 | 
            +
            	end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            	def syslog_resource
         | 
| 207 | 
            +
            		@@syslog ||= Syslog.open(default_options[:component].to_s, Syslog::LOG_PID | Syslog::LOG_CONS, default_options[:syslog_facility] || Syslog::LOG_USER)
         | 
| 208 | 
            +
            	end
         | 
| 209 | 
            +
            end
         | 
| 210 | 
            +
             |