amqp_logging 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,44 @@
1
+ =AMQPLogging
2
+
3
+
4
+ A ruby logger class that logs to an {AMQP}[http://www.amqp.org/] exchange in addition to your default log device.
5
+
6
+ ==Basic Configuration
7
+ By default the logs are routed to the host, exchange and key specified in DEFAULT_OPTIONS.
8
+ You can change the configuration when creating the logger object by specifying an argument hash:
9
+
10
+ require 'amqp_logging'
11
+
12
+ logging_config = { :routing_key => "applogging",
13
+ :host => AppConfig.amqp_logging.host,
14
+ :exchange => AppConfig.amqp_logging.exchange }
15
+
16
+ logger = AMQPLogging::Logger.new(config.log_path, logging_config)
17
+ config.logger = logger
18
+
19
+ ==Routing Keys
20
+
21
+ You can set the routing key with a string or use a generator that respondes to call,
22
+ receives the logline as the first argument and returns the routing key.
23
+
24
+ Example:
25
+
26
+ # You can use a lambda or whatever responds to #call as the routing key generator
27
+ AMQPRoutingKeyGenerator = lambda do |logline|
28
+ if logline =~ /(?:engine\[([^\]]*)\])\: (Completed in|Processing|Session ID)?/
29
+ key = "logs.app.#{$1}"
30
+ key << ".statistics" unless $2.nil?
31
+ else
32
+ key = "logs.app.system"
33
+ end
34
+ key
35
+ end
36
+
37
+ AMQPLogging::Logger.new($stdout, :routing_key => AMQPRoutingKeyGenerator)
38
+
39
+ ==License
40
+
41
+ Copyright © 2010 Pascal Friederich
42
+ Copyright © 2010 XING AG
43
+
44
+ See LICENSES for details.
@@ -0,0 +1,84 @@
1
+ require 'bunny'
2
+ require 'active_support'
3
+
4
+ module AMQPLogging
5
+
6
+ DEFAULT_OPTIONS = {
7
+ :shift_age => 0,
8
+ :shift_size => 1048576,
9
+ :host => "localhost",
10
+ :exchange => "logging_exchange",
11
+ :queue => "logging_queue",
12
+ :routing_key => "logs"
13
+ }
14
+
15
+ RETRY_AFTER = 10.seconds
16
+
17
+ class Logger < ::Logger
18
+ attr_accessor :extra_attributes
19
+ attr_accessor :errback
20
+
21
+ def initialize(logdev, *args)
22
+ options = args.first.is_a?(Hash) ? DEFAULT_OPTIONS.merge(args.first) : DEFAULT_OPTIONS
23
+ super(logdev, options[:shift_age], options[:shift_size])
24
+ @logdev = AMQPLogDevice.new(@logdev, options)
25
+ @logdev.logger = self
26
+ end
27
+ end
28
+
29
+ class AMQPLogDevice
30
+ attr_reader :exchange, :configuration
31
+ attr_accessor :logger
32
+
33
+ def initialize(dev, opts = {})
34
+ @configuration = opts
35
+ @fallback_logdev = dev
36
+ end
37
+
38
+ def write(msg)
39
+ begin
40
+ if !@paused || @paused <= RETRY_AFTER.ago
41
+ routing_key = configuration[:routing_key].respond_to?(:call) ? configuration[:routing_key].call(msg).to_s : configuration[:routing_key]
42
+ exchange.publish(msg, :key => routing_key)
43
+ end
44
+ rescue Exception => exception
45
+ pause_amqp_logging(exception)
46
+ ensure
47
+ @fallback_logdev.write(msg)
48
+ end
49
+ end
50
+
51
+ def close
52
+ @fallback_logdev.close
53
+ end
54
+
55
+ private
56
+ def pause_amqp_logging(exception)
57
+ @paused = Time.now
58
+ reset_amqp
59
+ logger.errback.call(exception) if logger.errback && logger.errback.respond_to?(:call)
60
+ end
61
+
62
+ def reset_amqp
63
+ begin
64
+ bunny.stop if bunny.connected?
65
+ rescue
66
+ # if bunny throws an exception here, its not usable anymore anyway
67
+ ensure
68
+ @exchange = @bunny = nil
69
+ end
70
+ end
71
+
72
+ def exchange
73
+ bunny.start unless bunny.connected?
74
+ @exchange ||= bunny.exchange(configuration[:exchange], :type => :topic)
75
+ end
76
+
77
+ def bunny
78
+ @bunny ||= Bunny.new(:host => @configuration[:host])
79
+ @bunny
80
+ end
81
+ end
82
+
83
+ end
84
+
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+
4
+ class TheAMQPLoggerTest < Test::Unit::TestCase
5
+ def setup
6
+ @io = StringIO.new
7
+ end
8
+
9
+ test "should be instanciated like a normal logger" do
10
+ assert_nothing_raised { AMQPLogging::Logger.new(@io) }
11
+ assert_nothing_raised { AMQPLogging::Logger.new(@io, 2) }
12
+ assert_nothing_raised { AMQPLogging::Logger.new(@io, 2, 1048576) }
13
+ end
14
+
15
+ test "should be instanciated with an amqp configuration hash" do
16
+ config = { :queue => "testqueue", :exchange => "testexchange", :host => "testhost", :shift_age => 4, :shift_size => 1338, :routing_key => "foobar" }
17
+ AMQPLogging::AMQPLogDevice.expects(:new).with(anything, config).returns(stub_everything)
18
+
19
+ logger = AMQPLogging::Logger.new(@io, config)
20
+ end
21
+
22
+ test "should write to the default io" do
23
+ AMQPLogging::AMQPLogDevice.any_instance.stubs(:exchange).returns(stub_everything('test_exchange'))
24
+ logger = AMQPLogging::Logger.new(@io)
25
+ logger.debug "logging something"
26
+ assert_match "logging something", @io.string
27
+ end
28
+
29
+ test "should pause AMQP logging if exceptions during logging occure" do
30
+ # in case you ask why not just using mocha expectations here: the rescue in the tested code also rescues the mocha exception
31
+ # this fake exchange object increases a counter everytime publish is executed so we can check the number of executions
32
+ class TestExchange; attr_reader :counter; def publish(*args); @counter ||= 0; @counter += 1; raise 'Foo'; end; end
33
+ logger = AMQPLogging::Logger.new(@io)
34
+ exchange = TestExchange.new
35
+ AMQPLogging::AMQPLogDevice.any_instance.stubs(:exchange).returns(exchange)
36
+ AMQPLogging::AMQPLogDevice.any_instance.stubs(:bunny).returns(stub_everything("bunny stub"))
37
+ 2.times { logger.debug "This will raise" }
38
+
39
+ assert_equal 1, exchange.counter
40
+ end
41
+
42
+ test "should call the logger errback with the exception that occured if one is set" do
43
+ class FooBarException < Exception; end
44
+ @called = false
45
+
46
+ errback = lambda {|exception| begin raise exception; rescue FooBarException; @called = true; end}
47
+ raising_exchange = mock("mocked exchange")
48
+ raising_exchange.expects(:publish).raises(FooBarException)
49
+ AMQPLogging::AMQPLogDevice.any_instance.stubs(:exchange).returns(raising_exchange)
50
+ logger = AMQPLogging::Logger.new(@io)
51
+ logger.errback = errback
52
+
53
+ assert_nothing_raised do
54
+ logger.debug("this will raise")
55
+ end
56
+ assert @called
57
+ end
58
+
59
+ test "should reset the bunny and exchange instance if a exception occures" do
60
+ raising_exchange = mock("mocked exchange")
61
+ raising_exchange.expects(:publish).raises("FFFFFFFFUUUUUUUUUU")
62
+ AMQPLogging::AMQPLogDevice.any_instance.stubs(:exchange).returns(raising_exchange)
63
+ logger = AMQPLogging::Logger.new(@io)
64
+
65
+ AMQPLogging::AMQPLogDevice.any_instance.expects(:reset_amqp)
66
+ logger.debug("This will raise and send a notification")
67
+ end
68
+
69
+ end
70
+
71
+ class TheLogDeviceTest < Test::Unit::TestCase
72
+ test "should initialize the AMQP components correctly" do
73
+ config = { :queue => "testqueue", :exchange => "testexchange", :host => "testhost", :shift_age => 4, :shift_size => 1338 }
74
+ bunny_stub = stub_everything("bunny_stub")
75
+ Bunny.expects(:new).with(:host => "testhost").returns(bunny_stub)
76
+ bunny_stub.expects(:exchange).with(config[:exchange], :type => :topic).returns(stub("exchange stub"))
77
+
78
+ logger = AMQPLogging::Logger.new(StringIO.new, config)
79
+ logger.debug("foobar")
80
+ end
81
+
82
+ test "should publish the messages with the default routing key" do
83
+ exchange = mock()
84
+ exchange.expects(:publish).with("msg\n", :key => "a_routing_key")
85
+ AMQPLogging::AMQPLogDevice.any_instance.stubs(:exchange).returns(exchange)
86
+ AMQPLogging::Logger.new(StringIO.new, {:routing_key => "a_routing_key"}).debug("msg")
87
+ end
88
+
89
+ test "should take a proc argument to generate the routing key" do
90
+ key_generator = lambda {|msg| msg == "a message\n" }
91
+ exchange = mock()
92
+ exchange.expects(:publish).with("a message\n", :key => "true")
93
+ AMQPLogging::AMQPLogDevice.any_instance.stubs(:exchange).returns(exchange)
94
+ AMQPLogging::Logger.new(StringIO.new, {:routing_key => key_generator}).debug("a message")
95
+ end
96
+ end
97
+
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'mocha'
4
+ require 'active_support/testing/declarative'
5
+
6
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/amqp_logging')
7
+
8
+ begin
9
+ require 'redgreen' unless ENV['TM_FILENAME']
10
+ rescue LoadError
11
+ end
12
+
13
+ class Test::Unit::TestCase
14
+ extend ActiveSupport::Testing::Declarative
15
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amqp_logging
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
+ platform: ruby
12
+ authors:
13
+ - Pascal Friederich
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-11 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bunny
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 0
32
+ - 6
33
+ - 0
34
+ version: 0.6.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: activesupport
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 2
48
+ - 3
49
+ - 0
50
+ version: 2.3.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: mocha
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ type: :development
66
+ version_requirements: *id003
67
+ description:
68
+ email:
69
+ - pascal.friederich@xing.com
70
+ executables: []
71
+
72
+ extensions: []
73
+
74
+ extra_rdoc_files:
75
+ - README.rdoc
76
+ files:
77
+ - lib/amqp_logging.rb
78
+ - README.rdoc
79
+ - test/amqp_logger_test.rb
80
+ - test/test_helper.rb
81
+ has_rdoc: true
82
+ homepage: http://github.com/paukul/amqp-logging
83
+ licenses: []
84
+
85
+ post_install_message:
86
+ rdoc_options:
87
+ - --charset=UTF-8
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 25
105
+ segments:
106
+ - 1
107
+ - 3
108
+ - 1
109
+ version: 1.3.1
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.3.7
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: A ruby logger class that logs to an AMQP exchange in addition to your default log device.
117
+ test_files:
118
+ - test/amqp_logger_test.rb
119
+ - test/test_helper.rb