scout 5.3.5 → 5.4.4.alpha

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.
Files changed (66) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG +0 -12
  3. data/Gemfile +4 -0
  4. data/README +8 -0
  5. data/Rakefile +6 -108
  6. data/bin/scout +1 -0
  7. data/lib/scout.rb +5 -4
  8. data/lib/scout/command.rb +11 -12
  9. data/lib/scout/command/install.rb +1 -1
  10. data/lib/scout/command/run.rb +13 -1
  11. data/lib/scout/command/sign.rb +2 -8
  12. data/lib/scout/command/stream.rb +50 -0
  13. data/lib/scout/command/test.rb +1 -1
  14. data/lib/scout/daemon_spawn.rb +215 -0
  15. data/lib/scout/plugin.rb +20 -1
  16. data/lib/scout/server.rb +16 -111
  17. data/lib/scout/server_base.rb +100 -0
  18. data/lib/scout/streamer.rb +162 -0
  19. data/lib/scout/streamer_control.rb +43 -0
  20. data/lib/scout/version.rb +3 -0
  21. data/scout.gemspec +27 -0
  22. data/test/plugins/disk_usage.rb +86 -0
  23. data/test/scout_test.rb +598 -0
  24. data/vendor/pusher-gem/Gemfile +2 -0
  25. data/vendor/pusher-gem/LICENSE +20 -0
  26. data/vendor/pusher-gem/README.md +80 -0
  27. data/vendor/pusher-gem/Rakefile +11 -0
  28. data/vendor/pusher-gem/examples/async_message.rb +28 -0
  29. data/vendor/pusher-gem/lib/pusher.rb +107 -0
  30. data/vendor/pusher-gem/lib/pusher/channel.rb +154 -0
  31. data/vendor/pusher-gem/lib/pusher/request.rb +107 -0
  32. data/vendor/pusher-gem/pusher.gemspec +28 -0
  33. data/vendor/pusher-gem/spec/channel_spec.rb +274 -0
  34. data/vendor/pusher-gem/spec/pusher_spec.rb +87 -0
  35. data/vendor/pusher-gem/spec/spec_helper.rb +13 -0
  36. data/vendor/ruby-hmac/History.txt +15 -0
  37. data/vendor/ruby-hmac/Manifest.txt +11 -0
  38. data/vendor/ruby-hmac/README.md +41 -0
  39. data/vendor/ruby-hmac/Rakefile +23 -0
  40. data/vendor/ruby-hmac/lib/hmac-md5.rb +11 -0
  41. data/vendor/ruby-hmac/lib/hmac-rmd160.rb +11 -0
  42. data/vendor/ruby-hmac/lib/hmac-sha1.rb +11 -0
  43. data/vendor/ruby-hmac/lib/hmac-sha2.rb +25 -0
  44. data/vendor/ruby-hmac/lib/hmac.rb +118 -0
  45. data/vendor/ruby-hmac/lib/ruby_hmac.rb +2 -0
  46. data/vendor/ruby-hmac/ruby-hmac.gemspec +33 -0
  47. data/vendor/ruby-hmac/test/test_hmac.rb +89 -0
  48. data/vendor/signature/.document +5 -0
  49. data/vendor/signature/.gitignore +21 -0
  50. data/vendor/signature/Gemfile +3 -0
  51. data/vendor/signature/Gemfile.lock +29 -0
  52. data/vendor/signature/LICENSE +20 -0
  53. data/vendor/signature/README.md +55 -0
  54. data/vendor/signature/Rakefile +2 -0
  55. data/vendor/signature/VERSION +1 -0
  56. data/vendor/signature/lib/signature.rb +142 -0
  57. data/vendor/signature/lib/signature/version.rb +3 -0
  58. data/vendor/signature/signature.gemspec +22 -0
  59. data/vendor/signature/spec/signature_spec.rb +176 -0
  60. data/vendor/signature/spec/spec_helper.rb +10 -0
  61. data/vendor/util/lib/core_extensions.rb +60 -0
  62. metadata +120 -84
  63. data/AUTHORS +0 -4
  64. data/COPYING +0 -340
  65. data/INSTALL +0 -18
  66. data/TODO +0 -6
data/lib/scout/plugin.rb CHANGED
@@ -49,6 +49,16 @@ module Scout
49
49
  code =~ EMBEDDED_OPTIONS_REGEX
50
50
  return $2
51
51
  end
52
+
53
+ def extract_code_class(code)
54
+ match = /class\s\b(\w*)\s+?<\s+Scout::Plugin/.match(code)
55
+
56
+ if match
57
+ return match[1]
58
+ else
59
+ raise ArgumentError, "can't identify plugin class"
60
+ end
61
+ end
52
62
  end
53
63
 
54
64
  # Creates a new Scout Plugin to run.
@@ -122,7 +132,16 @@ module Scout
122
132
  alias_method :add_#{kind}, :#{kind}
123
133
  END
124
134
  end
125
-
135
+
136
+ # resets everything except memory. Memory stays intact. This is used for real-time reporting
137
+ def reset!
138
+ @data_for_server = { :reports => [ ],
139
+ :alerts => [ ],
140
+ :errors => [ ],
141
+ :summaries => [ ],
142
+ :memory => @memory }
143
+ end
144
+
126
145
  #
127
146
  # Usage:
128
147
  #
data/lib/scout/server.rb CHANGED
@@ -1,29 +1,13 @@
1
- #!/usr/bin/env ruby -wKU
2
-
3
- require "net/https"
4
- require "uri"
5
- require "yaml"
6
- require "timeout"
7
- require "stringio"
8
- require "zlib"
9
- require "socket"
10
- require "base64"
11
-
12
- $LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. .. vendor json_pure lib])
1
+
2
+ Dir.glob(File.join(File.dirname(__FILE__), *%w[.. .. vendor *])).each do |dir|
3
+ $LOAD_PATH << File.join(dir,"lib")
4
+ end
5
+
13
6
  require "json"
7
+ require "pusher"
14
8
 
15
9
  module Scout
16
- class Server
17
- # A new class for plugin Timeout errors.
18
- class PluginTimeoutError < RuntimeError; end
19
- # A new class for API Timeout errors.
20
- class APITimeoutError < RuntimeError; end
21
-
22
- # Headers passed up with all API requests.
23
- HTTP_HEADERS = { "Client-Version" => Scout::VERSION,
24
- "Client-Hostname" => Socket.gethostname,
25
- "Accept-Encoding" => "gzip" }
26
-
10
+ class Server < Scout::ServerBase
27
11
  #
28
12
  # A plugin cannot take more than DEFAULT_PLUGIN_TIMEOUT seconds to execute,
29
13
  # otherwise, a timeout error is generated. This can be overriden by
@@ -39,20 +23,20 @@ module Scout
39
23
  attr_reader :new_plan
40
24
  attr_reader :directives
41
25
  attr_reader :plugin_config
26
+ attr_reader :streamer_command
42
27
 
43
28
  # Creates a new Scout Server connection.
44
- def initialize(server, client_key, history_file, logger = nil, server_name=nil, http_proxy='', https_proxy='')
29
+ def initialize(server, client_key, history_file, logger = nil, server_name=nil)
45
30
  @server = server
46
31
  @client_key = client_key
47
32
  @history_file = history_file
48
33
  @history = Hash.new
49
34
  @logger = logger
50
35
  @server_name = server_name
51
- @http_proxy = http_proxy
52
- @https_proxy = https_proxy
53
36
  @plugin_plan = []
54
37
  @plugins_with_signature_errors = []
55
38
  @directives = {} # take_snapshots, interval, sleep_interval
39
+ @streamer_command = nil
56
40
  @new_plan = false
57
41
  @local_plugin_path = File.dirname(history_file) # just put overrides and ad-hoc plugins in same directory as history file.
58
42
  @plugin_config_path = File.join(@local_plugin_path, "plugins.properties")
@@ -68,6 +52,7 @@ module Scout
68
52
  end
69
53
 
70
54
  def refresh?
55
+ #info "called refresh: ping_key=#{ping_key}"
71
56
  return true if !ping_key or account_public_key_changed? # fetch the plan again if the account key is modified/created
72
57
 
73
58
  url=URI.join( @server.sub("https://","http://"), "/clients/#{ping_key}/ping.scout")
@@ -77,6 +62,8 @@ module Scout
77
62
  headers["If-Modified-Since"] = @history["plan_last_modified"]
78
63
  end
79
64
  get(url, "Could not ping #{url} for refresh info", headers) do |res|
65
+ info "inside 'refresh?' #{res.to_hash.to_json}"
66
+ @streamer_command = res["x-streamer-command"] # usually will be nil, but can be [start,abcd,1234,5678|stop]
80
67
  if res.is_a?(Net::HTTPNotModified)
81
68
  return false
82
69
  else
@@ -107,7 +94,6 @@ module Scout
107
94
  if res["Content-Encoding"] == "gzip" and body and not body.empty?
108
95
  body = Zlib::GzipReader.new(StringIO.new(body)).read
109
96
  end
110
-
111
97
  body_as_hash = JSON.parse(body)
112
98
 
113
99
  temp_plugins=Array(body_as_hash["plugins"])
@@ -152,7 +138,6 @@ module Scout
152
138
 
153
139
  @new_plan = true # used in determination if we should checkin this time or not
154
140
 
155
-
156
141
  # Add local plugins to the plan.
157
142
  @plugin_plan += get_local_plugins
158
143
  rescue Exception =>e
@@ -165,6 +150,7 @@ module Scout
165
150
  @plugin_plan = Array(@history["old_plugins"])
166
151
  @plugin_plan += get_local_plugins
167
152
  @directives = @history["directives"] || Hash.new
153
+
168
154
  end
169
155
  @plugin_plan.reject! { |p| p['code'].nil? }
170
156
  end
@@ -245,7 +231,7 @@ module Scout
245
231
  def time_to_checkin?
246
232
  @history['last_checkin'] == nil ||
247
233
  @directives['interval'] == nil ||
248
- (Time.now.to_i - Time.at(@history['last_checkin']).to_i).abs+15+sleep_interval > @directives['interval'].to_i*60
234
+ (Time.now.to_i - Time.at(@history['last_checkin']).to_i).abs+15 > @directives['interval'].to_i*60
249
235
  rescue
250
236
  debug "Failed to calculate time_to_checkin. @history['last_checkin']=#{@history['last_checkin']}. "+
251
237
  "@directives['interval']=#{@directives['interval']}. Time.now.to_i=#{Time.now.to_i}"
@@ -495,7 +481,7 @@ module Scout
495
481
  contents=File.read(@history_file)
496
482
  begin
497
483
  @history = YAML.load(contents)
498
- rescue
484
+ rescue => e
499
485
  backup_path=File.join(File.dirname(@history_file), "history.corrupt")
500
486
  info "Couldn't parse the history file. Deleting it and resetting to an empty history file. Keeping a backup at #{backup_path}"
501
487
  File.open(backup_path,"w"){|f|f.write contents}
@@ -540,70 +526,6 @@ module Scout
540
526
  }
541
527
  end
542
528
 
543
- def urlify(url_name, options = Hash.new)
544
- return unless @server
545
- options.merge!(:client_version => Scout::VERSION)
546
- URI.join( @server,
547
- "/clients/CLIENT_KEY/#{url_name}.scout".
548
- gsub(/\bCLIENT_KEY\b/, @client_key).
549
- gsub(/\b[A-Z_]+\b/) { |k| options[k.downcase.to_sym] || k } )
550
- end
551
-
552
- def post(url, error, body, headers = Hash.new, &response_handler)
553
- return unless url
554
- request(url, response_handler, error) do |connection|
555
- post = Net::HTTP::Post.new( url.path +
556
- (url.query ? ('?' + url.query) : ''),
557
- HTTP_HEADERS.merge(headers) )
558
- post.body = body
559
- connection.request(post)
560
- end
561
- end
562
-
563
- def get(url, error, headers = Hash.new, &response_handler)
564
- return unless url
565
- request(url, response_handler, error) do |connection|
566
- connection.get( url.path + (url.query ? ('?' + url.query) : ''),
567
- HTTP_HEADERS.merge(headers) )
568
- end
569
- end
570
-
571
- def request(url, response_handler, error, &connector)
572
- response = nil
573
- Timeout.timeout(5 * 60, APITimeoutError) do
574
-
575
- # take care of http/https proxy, if specified in command line options
576
- # Given a blank string, the proxy_uri URI instance's host/port/user/pass will be nil
577
- # Net::HTTP::Proxy returns a regular Net::HTTP class if the first argument (host) is nil
578
- proxy_uri = URI.parse(url.is_a?(URI::HTTPS) ? @https_proxy : @http_proxy)
579
- http=Net::HTTP::Proxy(proxy_uri.host,proxy_uri.port,proxy_uri.user,proxy_uri.port).new(url.host, url.port)
580
-
581
- if url.is_a? URI::HTTPS
582
- http.use_ssl = true
583
- http.ca_file = File.join( File.dirname(__FILE__),
584
- *%w[.. .. data cacert.pem] )
585
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER |
586
- OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
587
- end
588
- response = no_warnings { http.start(&connector) }
589
- end
590
- case response
591
- when Net::HTTPSuccess, Net::HTTPNotModified
592
- response_handler[response] unless response_handler.nil?
593
- else
594
- error = "Server says: #{response['x-scout-msg']}" if response['x-scout-msg']
595
- fatal error
596
- raise SystemExit.new(error)
597
- end
598
- rescue Timeout::Error
599
- fatal "Request timed out."
600
- exit
601
- rescue Exception
602
- raise if $!.is_a? SystemExit
603
- fatal "An HTTP error occurred: #{$!.message}"
604
- exit
605
- end
606
-
607
529
  def checkin
608
530
  debug """
609
531
  #{PP.pp(@checkin, '')}
@@ -626,23 +548,6 @@ module Scout
626
548
  end
627
549
 
628
550
 
629
- def no_warnings
630
- old_verbose = $VERBOSE
631
- $VERBOSE = false
632
- yield
633
- ensure
634
- $VERBOSE = old_verbose
635
- end
636
-
637
- # Forward Logger methods to an active instance, when there is one.
638
- def method_missing(meth, *args, &block)
639
- if (Logger::SEV_LABEL - %w[ANY]).include? meth.to_s.upcase
640
- @logger.send(meth, *args, &block) unless @logger.nil?
641
- else
642
- super
643
- end
644
- end
645
-
646
551
  private
647
552
 
648
553
  # Called during initialization; loads the plugin_configs (local plugin configurations for passwords, etc)
@@ -0,0 +1,100 @@
1
+ require "net/https"
2
+ require "uri"
3
+ require "yaml"
4
+ require "timeout"
5
+ require "stringio"
6
+ require "zlib"
7
+ require "socket"
8
+ require "base64"
9
+
10
+ module Scout
11
+ class ServerBase
12
+ # A new class for plugin Timeout errors.
13
+ class PluginTimeoutError < RuntimeError; end
14
+ # A new class for API Timeout errors.
15
+ class APITimeoutError < RuntimeError; end
16
+
17
+ # Headers passed up with all API requests.
18
+ HTTP_HEADERS = { "Client-Version" => Scout::VERSION,
19
+ "Client-Hostname" => Socket.gethostname,
20
+ "Accept-Encoding" => "gzip" }
21
+
22
+
23
+ private
24
+
25
+ def urlify(url_name, options = Hash.new)
26
+ return unless @server
27
+ options.merge!(:client_version => Scout::VERSION)
28
+ URI.join(@server,
29
+ "/clients/CLIENT_KEY/#{url_name}.scout".
30
+ gsub(/\bCLIENT_KEY\b/, @client_key).
31
+ gsub(/\b[A-Z_]+\b/) { |k| options[k.downcase.to_sym] || k })
32
+ end
33
+
34
+ def post(url, error, body, headers = Hash.new, &response_handler)
35
+ return unless url
36
+ request(url, response_handler, error) do |connection|
37
+ post = Net::HTTP::Post.new(url.path +
38
+ (url.query ? ('?' + url.query) : ''),
39
+ HTTP_HEADERS.merge(headers))
40
+ post.body = body
41
+ connection.request(post)
42
+ end
43
+ end
44
+
45
+ def get(url, error, headers = Hash.new, &response_handler)
46
+ return unless url
47
+ request(url, response_handler, error) do |connection|
48
+ connection.get(url.path + (url.query ? ('?' + url.query) : ''),
49
+ HTTP_HEADERS.merge(headers))
50
+ end
51
+ end
52
+
53
+ def request(url, response_handler, error, &connector)
54
+ response = nil
55
+ Timeout.timeout(5 * 60, APITimeoutError) do
56
+ http = Net::HTTP.new(url.host, url.port)
57
+ if url.is_a? URI::HTTPS
58
+ http.use_ssl = true
59
+ http.ca_file = File.join(File.dirname(__FILE__),
60
+ *%w[.. .. data cacert.pem])
61
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER |
62
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
63
+ end
64
+ response = no_warnings { http.start(&connector) }
65
+ end
66
+ case response
67
+ when Net::HTTPSuccess, Net::HTTPNotModified
68
+ response_handler[response] unless response_handler.nil?
69
+ else
70
+ error = "Server says: #{response['x-scout-msg']}" if response['x-scout-msg']
71
+ fatal error
72
+ raise SystemExit.new(error)
73
+ end
74
+ rescue Timeout::Error
75
+ fatal "Request timed out."
76
+ exit
77
+ rescue Exception
78
+ raise if $!.is_a? SystemExit
79
+ fatal "An HTTP error occurred: #{$!.message}"
80
+ exit
81
+ end
82
+
83
+ def no_warnings
84
+ old_verbose = $VERBOSE
85
+ $VERBOSE = false
86
+ yield
87
+ ensure
88
+ $VERBOSE = old_verbose
89
+ end
90
+
91
+ # Forward Logger methods to an active instance, when there is one.
92
+ def method_missing(meth, *args, &block)
93
+ if (Logger::SEV_LABEL - %w[ANY]).include? meth.to_s.upcase
94
+ @logger.send(meth, *args, &block) unless @logger.nil?
95
+ else
96
+ super
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,162 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+
4
+ module Scout
5
+ class Streamer < Scout::ServerBase
6
+ MAX_DURATION = 60*30 # will shut down automatically after this many seconds
7
+ SLEEP = 1
8
+
9
+ # * history_file is the *path* to the history file
10
+ # * plugin_ids is an array of integers
11
+ def initialize(server, client_key, history_file, plugin_ids, streaming_key, logger = nil)
12
+ @server = server
13
+ @client_key = client_key
14
+ @history_file = history_file
15
+ @history = Hash.new
16
+ @logger = logger
17
+
18
+ @plugins = []
19
+
20
+ Pusher.app_id = '11495'
21
+ Pusher.key = 'a95aa7293cd158100246'
22
+ Pusher.secret = '9c13ccfe325fe3ae682d'
23
+
24
+ debug "plugin_ids = #{plugin_ids.inspect}"
25
+ debug "streaming_key = #{streaming_key}"
26
+
27
+ streamer_start_time = Time.now
28
+
29
+ hostname=Socket.gethostname
30
+ # load history
31
+ load_history
32
+
33
+ # get the array of plugins, AKA the plugin plan
34
+ @plugin_plan = Array(@history["old_plugins"])
35
+
36
+ # iterate through the plan and compile each plugin. We only compile plugins once at the beginning of the run
37
+ @plugin_plan.each do |plugin|
38
+ begin
39
+ compile_plugin(plugin) # this is what adds to the @plugin array
40
+ rescue Exception
41
+ error("Encountered an error: #{$!.message}")
42
+ puts $!.backtrace.join('\n')
43
+ end
44
+ end
45
+
46
+ # main loop. Continue running until global $continue_streaming is set to false OR we've been running for MAX DURATION
47
+ while(streamer_start_time+MAX_DURATION > Time.now && $continue_streaming) do
48
+ plugins=[]
49
+ @plugins.each_with_index do |plugin,i|
50
+ # ignore plugins whose ids are not in the plugin_ids array -- this also ignores local plugins
51
+ next if !(@plugin_plan[i]['id'] && plugin_ids.include?(@plugin_plan[i]['id'].to_i))
52
+ start_time=Time.now
53
+ plugin.reset!
54
+ plugin.run
55
+ duration=((Time.now-start_time)*1000).to_i
56
+
57
+ plugins << {:duration=>duration,
58
+ :fields=>plugin.reports.inject{|memo,hash|memo.merge(hash)},
59
+ :name=>@plugin_plan[i]["name"],
60
+ :id=>@plugin_plan[i]["id"]}
61
+ end
62
+
63
+ bundle={:hostname=>hostname,
64
+ :server_time=>Time.now.strftime("%I:%M:%S %p"),
65
+ :num_processes=>`ps -e | wc -l`.chomp.to_i,
66
+ :plugins=>plugins }
67
+
68
+ begin
69
+ Pusher[streaming_key].trigger!('server_data', bundle)
70
+ rescue Pusher::Error => e
71
+ # (Pusher::AuthenticationError, Pusher::HTTPError, or Pusher::Error)
72
+ error "Error pushing data: #{e.message}"
73
+ end
74
+
75
+ if false
76
+ # debugging
77
+ File.open(File.join(File.dirname(@history_file),"debug.txt"),"w") do |f|
78
+ f.puts "... sleeping @ #{Time.now.strftime("%I:%M:%S %p")}..."
79
+ f.puts bundle.to_yaml
80
+ end
81
+ end
82
+
83
+ sleep(SLEEP)
84
+ end
85
+ end
86
+
87
+
88
+ private
89
+
90
+ #def post_bundle(bundle)
91
+ # post( urlify(:stream),
92
+ # "Unable to stream to server.",
93
+ # bundle.to_json,
94
+ # "Content-Type" => "application/json")
95
+ #rescue Exception
96
+ # error "Unable to stream to server."
97
+ # debug $!.class.to_s
98
+ # debug $!.message
99
+ # debug $!.backtrace.join("\n")
100
+ #end
101
+
102
+ # sets up the @plugins array
103
+ def compile_plugin(plugin)
104
+ plugin_id = plugin['id']
105
+
106
+ # take care of plugin overrides
107
+ local_path = File.join(File.dirname(@history_file), "#{plugin_id}.rb")
108
+ if File.exist?(local_path)
109
+ code_to_run = File.read(local_path)
110
+ else
111
+ code_to_run=plugin['code'] || ""
112
+ end
113
+
114
+ id_and_name = "#{plugin['id']}-#{plugin['name']}".sub(/\A-/, "")
115
+ last_run = @history["last_runs"][id_and_name] ||
116
+ @history["last_runs"][plugin['name']]
117
+ memory = @history["memory"][id_and_name] ||
118
+ @history["memory"][plugin['name']]
119
+ options=(plugin['options'] || Hash.new)
120
+ options.merge!(:tuner_days=>"")
121
+ code_class=Plugin.extract_code_class(code_to_run)
122
+ begin
123
+ eval(code_to_run, TOPLEVEL_BINDING, plugin['path'] || plugin['name'] )
124
+ klass=Plugin.const_get(code_class)
125
+ info "Added a #{klass.name} plugin, id = #{plugin_id}"
126
+ @plugins << klass.load(last_run, (memory || Hash.new), options)
127
+
128
+ # turn certain methods into null-ops, so summaries aren't generated. Note that this is ad-hoc, and not future-proof.
129
+ if klass.name=="RailsRequests"; def klass.analyze;end;end
130
+ if klass.name=="ApacheAnalyzer"; def klass.generate_log_analysis;end;end
131
+
132
+ rescue Exception
133
+ error "Plugin would not compile: #{$!.message}"
134
+ end
135
+ end
136
+
137
+
138
+ def load_history
139
+ begin
140
+ debug "Loading history file..."
141
+ contents=File.read(@history_file)
142
+ @history = YAML.load(contents)
143
+ rescue => e
144
+ info "Couldn't load or parse the history file at #{@history_file}. Exiting."
145
+ exit(1)
146
+ end
147
+ info "History file loaded."
148
+ end
149
+
150
+ # Forward Logger methods to an active instance, when there is one.
151
+ def method_missing(meth, *args, &block)
152
+ if (Logger::SEV_LABEL - %w[ANY]).include? meth.to_s.upcase
153
+ @logger.send(meth, *args, &block) unless @logger.nil?
154
+ else
155
+ super
156
+ end
157
+ end
158
+
159
+ def growl(message)`growlnotify -m '#{message.gsub("'","\'")}'`;end
160
+
161
+ end
162
+ end