amqp_logging 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.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