rackamole 0.3.5 → 0.3.6

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 CHANGED
@@ -38,10 +38,11 @@
38
38
  Logging
39
39
  Hitimes
40
40
  mongo + mongo_ext
41
- Twitter4r
42
41
  Chronic
43
42
  Erubis
44
- Pony
43
+ Twitter4r
44
+ Mail
45
+ Growl
45
46
 
46
47
  == INSTALL
47
48
 
data/Rakefile CHANGED
@@ -32,4 +32,5 @@ depend_on "mongo_ext" , ">= 0.17.1"
32
32
  depend_on "chronic" , ">= 0.2.3"
33
33
  depend_on "twitter4r" , ">= 0.3.0"
34
34
  depend_on "erubis" , ">= 2.6.0"
35
- depend_on "pony" , ">= 0.6"
35
+ depend_on "mail" , ">= 2.1.3"
36
+ depend_on "ruby-growl" , ">= 1.0.1"
data/lib/rackamole.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Rackamole
2
2
 
3
3
  # :stopdoc:
4
- VERSION = '0.3.5'
4
+ VERSION = '0.3.6'
5
5
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
6
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
7
  # :startdoc:
@@ -27,7 +27,8 @@ module Rackamole::Alert
27
27
  tmpl = File.join( template_root, %w[alert.erb] )
28
28
  template = Erubis::Eruby.new( IO.read( tmpl ), :trim => true )
29
29
  body = template.result( binding )
30
- subject = "Rackamole <#{alert_type( args )}> #{request_time?( args )}on #{args[:app_name]}.#{args[:host]} for user #{args[:user_name]}"
30
+ timing = request_time( args ) if args[:type] == Rackamole.perf
31
+ subject = "Rackamole <#{alert_type( args )}>#{timing ? " #{timing} " : ' '}on #{args[:app_name]}.#{args[:host]} for user #{args[:user_name]}"
31
32
 
32
33
  mail = Mail.new do
33
34
  from options[:email][:from]
@@ -37,9 +38,9 @@ module Rackamole::Alert
37
38
  end
38
39
  mail.deliver!
39
40
  mail
40
- rescue => boom
41
+ rescue => boom
42
+ logger.error( "Rackamole email alert failed with error `#{boom}" )
41
43
  boom.backtrace.each { |l| logger.error l }
42
- logger.error( "Rackamole email alert failed with error `#{boom}" )
43
44
  end
44
45
 
45
46
  def self.section( title )
@@ -78,8 +79,8 @@ module Rackamole::Alert
78
79
  buff = []
79
80
  value = args[key]
80
81
 
81
- value = request_time?( args ) if key == :request_time
82
-
82
+ value = request_time( args ) if key == :request_time
83
+
83
84
  _spew( buff, '--', (silent ? '' : ' '), key, value, silent )
84
85
  buff.join( "\n" )
85
86
  end
@@ -103,10 +104,12 @@ module Rackamole::Alert
103
104
  when Rackamole.fault
104
105
  buff << spew( :fault ) << spew( :stack ) + "\n"
105
106
  when Rackamole.perf
106
- buff << "#{spew( :request_time )}/#{@options[:perf_threshold]}"
107
+ buff << "#{spew( :request_time )} [#{@options[:perf_threshold]}]"
107
108
  end
108
109
  buff << spew( :user_name) << spew( :url ) << spew( :path ) << spew( :status )
109
- buff << spew( :method ) << spew( :request_time ) << spew( :ip )
110
+ buff << spew( :method )
111
+ buff << spew( :request_time ) unless args[:type] == Rackamole.perf
112
+ buff << spew( :ip )
110
113
  buff.join( "\n" )
111
114
  end
112
115
  def self.server() [ spew( :host ), spew( :software ), spew( :ruby_version ) ]; end
@@ -117,8 +120,9 @@ module Rackamole::Alert
117
120
  def self.headers() [ spew( :headers, true ) ]; end
118
121
 
119
122
  # Dump request time if any...
120
- def self.request_time?( args )
121
- args[:request_time] ? ("%5.2f " % args[:request_time] ) : ''
123
+ def self.request_time( args )
124
+ return '' unless args[:request_time]
125
+ "%1.2f" % args[:request_time]
122
126
  end
123
127
 
124
128
  # Identify the type of alert...
@@ -0,0 +1,105 @@
1
+ require 'ruby-growl'
2
+
3
+ module Rackamole::Alert
4
+ # Leverages growl as a notification client.
5
+ class Growl
6
+
7
+ # Twitt an alert
8
+ def self.deliver_alert( logger, options, attrs )
9
+ @growl ||= Growl.new( logger, options[:growl][:to] )
10
+ @growl.send_alert( attrs )
11
+ end
12
+
13
+ # Send a growl notification for particular moled feature. A growl will
14
+ # be sent based on the configuration :growl defined on Rack::Mole component
15
+ # This option must specify the to ip addresses and the conditions
16
+ # that will trigger a growl defined by alert_on for the type of
17
+ # moled features to track via email. The notification will be sent via UDP,
18
+ # so you will need to make sure it is properly configured for your domain.
19
+ #
20
+ # NOTE: This is just a notification mechanism. All moled event will be either logged
21
+ # or persisted in the db regardless.
22
+ #
23
+ # === Parameters:
24
+ # options :: Mole options. The :growl key must contains :to addresses.
25
+ # args :: The gathered information from the mole.
26
+ def initialize( logger, recipients )
27
+ raise "You must specify your growl :to addresses" unless recipients
28
+ @recipients = recipients
29
+ @logger = logger
30
+ @growls = {}
31
+ end
32
+
33
+ # Send out a growl notification based of the watched features. A short message will be blasted to your growl
34
+ # client based on information reported by the mole.
35
+ # === Params:
36
+ # args :: The moled info for a given feature.
37
+ def send_alert( args )
38
+ recipients.each do |recipient|
39
+ buff = "#{args[:user_name]}:#{display_feature(args)}"
40
+ title = "#{args[:app_name]}(#{args[:environment]})"
41
+ case args[:type]
42
+ when Rackamole.feature
43
+ type = "Feature"
44
+ title = "[Feat] #{title}"
45
+ msg = buff
46
+ priority = -2
47
+ sticky = false
48
+ when Rackamole.perf
49
+ type = "Perf"
50
+ title = "[Perf] #{title}"
51
+ msg = "#{buff} #{format_time(args[:request_time])} secs"
52
+ priority = 2
53
+ sticky = true
54
+ when Rackamole.fault
55
+ type = "Fault"
56
+ title = "[Fault] #{title}"
57
+ msg = "#{buff}\n#{args[:fault]}"
58
+ priority = 2
59
+ sticky = true
60
+ end
61
+ growl( recipient ).notify( type, title, msg, priority, sticky )
62
+ end
63
+ rescue => boom
64
+ logger.error "Rackamole growl alert failed with error `#{boom}"
65
+ end
66
+
67
+ # =========================================================================
68
+ private
69
+
70
+ attr_reader :logger, :recipients #:nodoc:
71
+
72
+ # Fetch or create growl application...
73
+ def growl( recipient )
74
+ return @growls[recipient[:ip]] if @growls[recipient[:ip]]
75
+ growl = ::Growl.new( recipient[:ip], 'rackamole', %w(Feature Perf Fault), nil, recipient[:password] )
76
+ @growls[recipient[:ip]] = growl
77
+ growl
78
+ end
79
+
80
+ # Display controller/action or path depending on frmk used...
81
+ def display_feature( args )
82
+ return args[:path] unless args[:route_info]
83
+ "#{args[:route_info][:controller]}##{args[:route_info][:action]}"
84
+ end
85
+
86
+ # Format host ie fred@blee.com => fred
87
+ def format_host( host )
88
+ return host.gsub( /@.+/, '' ) if host =~ /@/
89
+ host
90
+ end
91
+
92
+ # Format precision on request time
93
+ def format_time( time )
94
+ ("%4.2f" % time).to_f
95
+ end
96
+
97
+ # Truncate for twitt max size
98
+ # BOZO !! This will be hosed if not 1.9 for multibyte chars
99
+ def truncate(text, length = 140, truncate_string = "...")
100
+ return "" if text.nil?
101
+ l = length - truncate_string.size
102
+ text.size > length ? (text[0...l] + truncate_string).to_s : text
103
+ end
104
+ end
105
+ end
@@ -12,14 +12,14 @@ module Rackamole::Alert
12
12
  end
13
13
 
14
14
  # This class is used to send out moled twitter notification. This feature is enabled
15
- # by setting both :twitter_auth and twitt_on options on the Rack::Mole. When a moled
15
+ # by setting the :twitter option on the Rack::Mole. When a moled
16
16
  # feature comes around it will be twitted on your configured account. This allow your
17
17
  # app to twitt about it's status and issues. Currently there are no provisions to throttle
18
18
  # the twitts, hence sending out twitt notifications of every moled features would not be
19
19
  # a very good idea. Whereas sending twitts when your application bogs down or throws exception,
20
20
  # might be more appropriate. Further work will take place to throttle these events...
21
- # Creating a private twitter account and asking folks in your group to follow might be an
22
- # alternative to email.
21
+ # Creating a private twitter account and asking folks in your group to follow might be a
22
+ # nice alternative to email.
23
23
  #
24
24
  # NOTE: This is just an alert mechanism. All moled events will be either logged or persisted in the db
25
25
  # regardless.
@@ -27,6 +27,7 @@ module Rackamole::Alert
27
27
  # === Params:
28
28
  # username :: The name on the twitter account
29
29
  # password :: The password of your twitter account
30
+ # logger :: Instance of the rackamole logger
30
31
  def initialize( logger, username, password )
31
32
  raise "You must specify your twitter account credentials" unless username or password
32
33
  @username = username
@@ -3,8 +3,8 @@ require 'json'
3
3
  require 'mongo'
4
4
  require 'yaml'
5
5
 
6
- # TODO - remove default app and db name
7
6
  # TODO - add recording for response
7
+ # TODO - need plugable archictecture for alerts and stores
8
8
  module Rack
9
9
  class Mole
10
10
 
@@ -45,8 +45,8 @@ module Rack
45
45
  # :user_key => { :session_key => :user_id, :extractor => lambda{ |id| User.find( id ).name} }
46
46
  # ==
47
47
  #
48
- # :mole_excludes :: Exclude some of the paramaters that the mole would normaly include. The list of
49
- # customizable paramaters is: software, ip, browser type, url, path, status, headers and body.
48
+ # :mole_excludes :: Exclude some of the parameters that the mole would normally include. The list of
49
+ # customizable parameters is: software, ip, browser type, url, path, status, headers and body.
50
50
  # This options takes an array of symbols. Defaults to [:body].
51
51
  # :perf_excludes :: Specifies a set of paths that will be excluded when a performance issue is detected.
52
52
  # This is useful when certain requests are known to be slow. When a match is found an
@@ -56,7 +56,7 @@ module Rack
56
56
  # :perf_excludes => [ {:context => '/blee/fred}, {:context => /^\/duh.*/, :threshold => 10 } ]
57
57
  # ==
58
58
  # :excluded_paths:: Exclude paths that you do not wish to mole by specifying an array of regular expresssions.
59
- # :param_excludes:: Exempt certain sensitive request parameters from being logged to the mole. Specify an array of keys as
59
+ # :param_excludes:: Exempt certain sensitive request parameters from being logged to the mole. Specify an array of keys
60
60
  # as symbols ie [:password, :card_number].
61
61
  # :session_excludes:: Exempt session params from being logged by the mole. Specify an array of keys as symbols
62
62
  # ie [:fred, :blee] to exclude session[:fred] and session[:blee] from being stored.
@@ -76,6 +76,12 @@ module Rack
76
76
  # :email => { :from => 'fred@acme.com', :to => ['blee@acme.com', 'doh@acme.com'], :alert_on => [Rackamole.perf, Rackamole.fault] }
77
77
  # ==
78
78
  #
79
+ # :growl :: You can set up rackamole to send growl notifications when certain features
80
+ # are encounters in the same way email and twitt alerts works. The :to options
81
+ # describes a collection of hash values with the ip and optional password to the recipients.
82
+ # ==
83
+ # :growl => { :to => [{ :ip => '1.1.1.1' }], :alert_on => [Rackamole.perf, Rackamole.fault] }
84
+ # ==
79
85
  def initialize( app, opts={} )
80
86
  @app = app
81
87
  init_options( opts )
@@ -118,7 +124,7 @@ module Rack
118
124
  raise "Unable to find rackamole config file #{opts[:config_file]}" unless ::File.exists?( opts[:config_file] )
119
125
  begin
120
126
  opts = YAML.load( ERB.new( IO.read( opts[:config_file] ) ).result( binding ) )[env]
121
- opts[:environment] = env
127
+ opts[:environment] = env
122
128
  rescue => boom
123
129
  raise "Unable to parse Rackamole config file #{boom}"
124
130
  end
@@ -151,6 +157,7 @@ module Rack
151
157
  # Barf early if something is wrong in the configuration
152
158
  configured?( :twitter, [:username, :password, :alert_on], true )
153
159
  configured?( :email , [:from, :to, :alert_on], true )
160
+ configured?( :growl , [:to, :alert_on], true )
154
161
  end
155
162
 
156
163
  # Send moled info to store and potentially send out alerts...
@@ -172,6 +179,12 @@ module Rack
172
179
  logger.debug ">>> Sending out email on mole type #{attrs[:type]} from #{options[:email][:from]} to #{options[:email][:to].join( ", ")}"
173
180
  Rackamole::Alert::Emole.deliver_alert( logger, options, attrs )
174
181
  end
182
+
183
+ # send growl alert ?
184
+ if alertable?( :growl, attrs[:type] )
185
+ logger.debug ">>> Sending out growl on mole type #{attrs[:type]} to @#{options[:growl][:to].inspect}"
186
+ Rackamole::Alert::Growl.deliver_alert( logger, options, attrs )
187
+ end
175
188
 
176
189
  # send twitter alert ?
177
190
  if alertable?( :twitter, attrs[:type] )
@@ -48,11 +48,11 @@ module Rackamole::Store
48
48
  log.info "-"*100
49
49
  log.info case args[:type]
50
50
  when Rackamole.feature
51
- "FEATURE m()le"
51
+ "FEATURE"
52
52
  when Rackamole.fault
53
- "FAULT m()le"
53
+ "FAULT"
54
54
  when Rackamole.perf
55
- "PERFORMANCE m()le"
55
+ "PERFORMANCE"
56
56
  end
57
57
  end
58
58
 
@@ -0,0 +1,7 @@
1
+ # BOZO !! Rake task
2
+ require 'rubygems'
3
+ require 'mongo'
4
+
5
+ con = Mongo::Connection.new( 'localhost' )
6
+ db = con.db( 'sec_app_test_mdb' )
7
+ db.add_user( 'fred', 'letmein' )
@@ -1,5 +1,5 @@
1
1
  ----------------------------------------------------------------------------------------------------
2
- FAULT m()le
2
+ FAULT
3
3
  Type : 2
4
4
  App_name : "Test app"
5
5
  Environment : :test
@@ -1,5 +1,5 @@
1
1
  ----------------------------------------------------------------------------------------------------
2
- FEATURE m()le
2
+ FEATURE
3
3
  Type : 0
4
4
  App_name : "Test app"
5
5
  Environment : :test
@@ -1,5 +1,5 @@
1
1
  ----------------------------------------------------------------------------------------------------
2
- PERFORMANCE m()le
2
+ PERFORMANCE
3
3
  Type : 1
4
4
  App_name : "Test app"
5
5
  Environment : :test
@@ -55,10 +55,10 @@ describe Rackamole::Alert::Emole do
55
55
 
56
56
  it "should send a feature email correctly" do
57
57
  alert = Rackamole::Alert::Emole.deliver_alert( nil, @options, @args )
58
- alert.body.should == feature_body
59
- alert.subject.should == "Rackamole <Feature> on Test.Fred for user Fernand"
60
- alert.from.should == ["fernand"]
61
- alert.to.should == ["fernand", 'bobo', 'blee']
58
+ alert.body.to_s.should == feature_body
59
+ alert.subject.should == "Rackamole <Feature> on Test.Fred for user Fernand"
60
+ alert.from.should == ["fernand"]
61
+ alert.to.should == ["fernand", 'bobo', 'blee']
62
62
  end
63
63
 
64
64
  it "should send a perf email correctly" do
@@ -160,13 +160,12 @@ Performance alert on application Test on host Fred
160
160
  ----------------------------------------
161
161
  o What
162
162
 
163
- request_time: 15.2/10
163
+ request_time: 15.20 [10]
164
164
  user_name: Fernand
165
165
  url: http://bumblebeetuna/fred/blee
166
166
  path: /fred/blee
167
167
  status: 200
168
168
  method: POST
169
- request_time: 15.2
170
169
  ip:
171
170
 
172
171
  ----------------------------------------
@@ -0,0 +1,95 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+ require 'chronic'
3
+
4
+ describe Rackamole::Alert::Growl do
5
+ before( :each ) do
6
+ @recipients = [ {:ip => '1.1.1.1', :password => 'blee' } ]
7
+ @alert = Rackamole::Alert::Growl.new( nil, :growl => { :to => @recipients } )
8
+ end
9
+
10
+ describe '#display_feature' do
11
+ it "should display a rails feature correctly" do
12
+ @alert.send( :display_feature, :route_info => { :controller => 'fred', :action => 'blee'} ).should == "fred#blee"
13
+ end
14
+
15
+ it "should display a path feature for other rack framword" do
16
+ @alert.send( :display_feature, :path => '/fred/blee' ).should == "/fred/blee"
17
+ end
18
+ end
19
+
20
+ describe '#send_alert' do
21
+ before( :each ) do
22
+ @args = OrderedHash.new
23
+ @args[:type] = Rackamole.feature
24
+ @args[:app_name] = 'Test'
25
+ @args[:host] = 'Fred'
26
+ @args[:user_name] = 'Fernand'
27
+ @args[:path] = '/blee/fred'
28
+ @args[:created_at] = Chronic.parse( "2009/11/19" )
29
+ end
30
+
31
+ it "should growl a feature alert using class method correctly" do
32
+ growl = mock( Rackamole::Alert::Growl )
33
+ client = Growl.stub!( :new )
34
+
35
+ Rackamole::Alert::Growl.should_receive( :new ).with( nil, @recipients ).once.and_return( growl )
36
+ growl.should_receive( :send_alert ).with( @args ).once.and_return( "yeah" )
37
+
38
+ Rackamole::Alert::Growl.deliver_alert( nil, { :growl => { :to => @recipients } }, @args )
39
+ end
40
+
41
+ it "should growl a feature alert correctly" do
42
+ client = stub( Growl )
43
+
44
+ @alert.should_receive( :growl ).once.and_return( client )
45
+ client.should_receive( :notify ).once
46
+
47
+ @alert.send_alert( @args )
48
+ end
49
+
50
+ it "should growl a perf alert correctly" do
51
+ @args[:type] = Rackamole.perf
52
+ @args[:request_time] = 10.0
53
+
54
+ client = stub( Growl )
55
+
56
+ @alert.should_receive( :growl ).once.and_return( client )
57
+ client.should_receive( :notify ).once
58
+
59
+ @alert.send_alert( @args )
60
+ end
61
+
62
+ it "should twitt a perf alert correctly" do
63
+ @args[:type] = Rackamole.fault
64
+ @args[:fault] = 'Oh snap!'
65
+
66
+ client = stub( Growl )
67
+
68
+ @alert.should_receive( :growl ).once.and_return( client )
69
+ client.should_receive( :notify ).once
70
+
71
+ @alert.send_alert( @args )
72
+ end
73
+ end
74
+
75
+ describe "#format_time" do
76
+ it "should format a request time correctly" do
77
+ @alert.send( :format_time, 12.1234455 ).should == 12.12
78
+ end
79
+ end
80
+
81
+ describe "#format_host" do
82
+ it "should format a host with domain name correctly" do
83
+ @alert.send( :format_host, 'blee@acme.com' ).should == 'blee'
84
+ end
85
+
86
+ it "should deal with ip host" do
87
+ @alert.send( :format_host, '1.1.1.1' ).should == '1.1.1.1'
88
+ end
89
+
90
+ it "should deal with aliases" do
91
+ @alert.send( :format_host, 'fred' ).should == 'fred'
92
+ end
93
+ end
94
+
95
+ end
@@ -170,7 +170,7 @@ describe Rackamole::Store::MongoDb do
170
170
  store = Rackamole::Store::MongoDb.new(
171
171
  :host => 'localhost',
172
172
  :port => 27017,
173
- :db_name => 'mole_sec_app_test_mdb',
173
+ :db_name => 'sec_app_test_mdb',
174
174
  :username => 'fred',
175
175
  :password => 'letmein',
176
176
  :logger => Rackamole::Logger.new( :file_name => $stdout, :log_level => 'info' ) )
@@ -181,7 +181,7 @@ describe Rackamole::Store::MongoDb do
181
181
  store = Rackamole::Store::MongoDb.new(
182
182
  :host => 'localhost',
183
183
  :port => 27017,
184
- :db_name => 'mole_sec_app_test_mdb',
184
+ :db_name => 'sec_app_test_mdb',
185
185
  :username => 'fred',
186
186
  :password => 'hoy',
187
187
  :logger => Rackamole::Logger.new( :file_name => $stdout, :log_level => 'info' ) )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rackamole
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fernand Galiana
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-03-17 00:00:00 -06:00
12
+ date: 2010-03-22 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -83,14 +83,24 @@ dependencies:
83
83
  version: 2.6.0
84
84
  version:
85
85
  - !ruby/object:Gem::Dependency
86
- name: pony
86
+ name: mail
87
87
  type: :runtime
88
88
  version_requirement:
89
89
  version_requirements: !ruby/object:Gem::Requirement
90
90
  requirements:
91
91
  - - ">="
92
92
  - !ruby/object:Gem::Version
93
- version: "0.6"
93
+ version: 2.1.3
94
+ version:
95
+ - !ruby/object:Gem::Dependency
96
+ name: ruby-growl
97
+ type: :runtime
98
+ version_requirement:
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 1.0.1
94
104
  version:
95
105
  - !ruby/object:Gem::Dependency
96
106
  name: bones
@@ -118,6 +128,7 @@ files:
118
128
  - Rakefile
119
129
  - lib/rackamole.rb
120
130
  - lib/rackamole/alert/emole.rb
131
+ - lib/rackamole/alert/growl.rb
121
132
  - lib/rackamole/alert/templates/alert.erb
122
133
  - lib/rackamole/alert/twitt.rb
123
134
  - lib/rackamole/interceptor.rb
@@ -131,10 +142,12 @@ files:
131
142
  - lib/rackamole/store/log.rb
132
143
  - lib/rackamole/store/mongo_db.rb
133
144
  - lib/rackamole/utils/agent_detect.rb
145
+ - spec/data/create_secure_db.rb
134
146
  - spec/expected_results/mole_exception.log
135
147
  - spec/expected_results/mole_feature.log
136
148
  - spec/expected_results/mole_perf.log
137
149
  - spec/rackamole/alert/emole_spec.rb
150
+ - spec/rackamole/alert/growl_spec.rb
138
151
  - spec/rackamole/alert/twitt_spec.rb
139
152
  - spec/rackamole/interceptor_spec.rb
140
153
  - spec/rackamole/logger_spec.rb