logworm_amqp 0.8.9 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ v0.9.0
2
+ Combines base and client into a single gem
3
+
4
+ base
5
+ ----
1
6
  v0.8.9
2
7
  Cleaned up pushed stats output.
3
8
 
@@ -64,4 +69,101 @@ v0.2.0
64
69
  removed app/ libraries. Added tail as a utility, lw-tail as a command
65
70
 
66
71
  v0.1.0
67
- initial version.
72
+ initial version.
73
+
74
+
75
+ client
76
+ ------
77
+ v0.8.8
78
+ Cleaned up pushed stats output.
79
+
80
+ v0.8.7
81
+ Started logging amqp usage statistics to our servers (randomly) for better QA.
82
+
83
+ v.0.8.6
84
+ No longer prints out amqp information when logging and should also self check for amqp url updates incase it was changed.
85
+
86
+ v0.8.5
87
+ Small fix
88
+
89
+ v0.8.0
90
+ Dropped http for amqp for high scalability and reduce latency
91
+
92
+ v0.7.2
93
+ Renamed Rails and Rack configuration commands:
94
+ lw_disable_request_logging ==> donot_log_requests
95
+ lw_log_request_headers ==> log_headers
96
+ lw_enable_dev_logging ==> log_in_development
97
+
98
+ Renamed :headers field in web_log to :request_headers
99
+
100
+ v0.7.1
101
+ lw-tail and lw-compute now receive an optional app parameter, for the cases where you want to call command-line tools from a directory other than the app's directory... or when you have more than one Heroku remote/app from the same directory
102
+
103
+ v0.7.0
104
+ Eliminated long logs. Instead, there's now a parameter (log_request_headers in Rack, lw_log_request_headers in Rails) to add
105
+ request and response headers to the web_log.
106
+ Cleaned up fields in the web_log table
107
+
108
+ Added option to turn off automatic logging of requests (disable_request_logging in Rack, lw_disable_request_logging in Rails)
109
+ Added option to turn on logging even if in development mode (enable_dev_logging in Rack, lw_enable_dev_logging in Rails)
110
+
111
+ Honor filter_parameter_logging switch when used with Rails
112
+
113
+ Cleaned up log and flush
114
+ Cleaned up display of elapsed time for flush
115
+ Cleaned up call from Rack and Rails, and enforce timeout (1 sec by default)
116
+ Added unit tests
117
+
118
+ v0.6.2
119
+ Added list_tables command
120
+
121
+ v0.6.1
122
+ Added a bit of documentation
123
+
124
+ v0.6.0
125
+ Add support for querying API: lw_query
126
+
127
+ v0.5.6
128
+ log HEROKU_QUEUE as an integer value
129
+
130
+ v0.5.5
131
+ Added default logging of HEROKU_QUEUE
132
+
133
+ v0.5.4
134
+ Removed obsolete lw-heroku application
135
+
136
+ v0.5.3
137
+ Show nicer error message with tail and compute if the app is not properly configured.
138
+
139
+ v0.5.2
140
+ Fixes minor issues with in Rails logging
141
+
142
+ v0.5.1
143
+ Fixes minor issues with logging statements sent to console
144
+
145
+ v0.5.0
146
+ Supports new base logworm gem, which can work as a Heroku addon
147
+ Configuration parameters now stored as URL
148
+ No error if logworm cannot be properly configured -- lw_log simply ignored
149
+ No logging in development mode
150
+
151
+ v0.4.1
152
+ Show in console time spent communicating with the server.
153
+
154
+ v0.4.0 Added support for running on Rails!
155
+ To use, add config.gem ‘logworm_client’ to environment.rb
156
+ Then you can configure in ApplicationController, via logs_requests [:short | :long]+
157
+ Works from views, models, or controllers.
158
+
159
+ v0.3.2 added lw_log method --fixed small issue
160
+
161
+ v0.3.1 added lw_log method
162
+
163
+ v0.3.0 ensure gem and dependencies live in gemcutter
164
+
165
+ v0.2.0 log short requests by default in the rack
166
+
167
+ v0.1.1 minor changes
168
+
169
+ v0.1.0 initial version.
data/Manifest CHANGED
@@ -1,14 +1,15 @@
1
1
  CHANGELOG
2
2
  Manifest
3
- README.md
4
3
  Rakefile
4
+ bin/lw-compute
5
+ bin/lw-tail
5
6
  lib/base/config.rb
6
7
  lib/base/db.rb
7
8
  lib/base/query_builder.rb
9
+ lib/client/logger.rb
10
+ lib/client/rack.rb
11
+ lib/client/rails.rb
12
+ lib/cmd/compute.rb
13
+ lib/cmd/tail.rb
8
14
  lib/logworm_amqp.rb
9
- spec/base_spec.rb
10
- spec/builder_spec.rb
11
- spec/config_spec.rb
12
- spec/spec.opts
13
- spec/spec_helper.rb
14
- tests/builder_test.rb
15
+ lib/logworm_utils.rb
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'echoe'
2
- Echoe.new('logworm_amqp', '0.8.9') do |p|
3
- p.description = "logworm logging tool"
2
+ Echoe.new('logworm_amqp', '0.9.0') do |p|
3
+ p.description = "logworm - logging service"
4
4
  p.url = "http://www.logworm.com"
5
5
  p.author = "Pomelo, LLC"
6
6
  p.email = "schapira@pomelollc.com"
7
7
  p.ignore_pattern = ["tmp/*", "script/*"]
8
- p.development_dependencies = ["memcache-client", "hpricot", "oauth", "heroku", "minion >=0.1.15"]
9
- p.runtime_dependencies = ["memcache-client", "hpricot", "oauth", "heroku", "minion >=0.1.15"]
8
+ p.development_dependencies = ["json >=1.4.3", "ruby-hmac", "hpricot", "oauth", "heroku"]
9
+ p.runtime_dependencies = ["json >=1.4.3", "ruby-hmac", "memcache-client", "hpricot", "oauth", "heroku"]
10
10
  end
data/bin/lw-compute ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'logworm_utils'
5
+ require 'optparse'
6
+
7
+ options = {
8
+ :aggregate_group => [],
9
+ :conditions => [],
10
+ :start => nil,
11
+ :end => nil,
12
+ :debug => false
13
+ }
14
+
15
+ option_parser = OptionParser.new do |opts|
16
+ opts.banner = "Usage: #{$0} [options] <log table> <function> <field>"
17
+
18
+ opts.on("--app app", String, "Specify the Heroku app") do |app|
19
+ options[:app] = app.strip
20
+ end
21
+
22
+ opts.on("-g group", String, "Specify an aggregation group (e.g, hour(_ts_utc), or response.code)") do |k|
23
+ options[:aggregate_group] = k
24
+ end
25
+
26
+ opts.on("-c condition", String, "Specify a condition to match. May be used multiple times") do |c|
27
+ options[:conditions] << c.strip
28
+ end
29
+
30
+ opts.on("-s starttime", String, "Specify the start time for the query") do |c|
31
+ options[:start] = c.strip
32
+ end
33
+
34
+ opts.on("-e endtime", String, "Specify the end time for the query") do |c|
35
+ options[:end] = c.strip
36
+ end
37
+
38
+ opts.on("-v", "Show debug information") do
39
+ options[:debug] = true
40
+ end
41
+
42
+ opts.on( '-h', '--help', 'Display this screen' ) do
43
+ puts option_parser.help
44
+ exit(1)
45
+ end
46
+
47
+ end
48
+
49
+ # Parse, and get the required <table>
50
+ option_parser.parse!
51
+ if ARGV.size == 3
52
+ table = ARGV[0].strip
53
+ function = ARGV[1].strip
54
+ field = ARGV[2].strip
55
+ elsif ARGV.size == 2
56
+ table = ARGV[0].strip
57
+ function = ARGV[1].strip
58
+ else
59
+ puts option_parser.help
60
+ exit(1)
61
+ end
62
+
63
+ # and run
64
+ LogwormCompute.new(table, function, field, options).run
65
+
66
+
data/bin/lw-tail ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'logworm_utils'
5
+ require 'optparse'
6
+
7
+ def help(option_parser, options)
8
+ puts option_parser.help
9
+ puts
10
+ LogwormTail.list(options)
11
+ end
12
+
13
+ options = {
14
+ :limit => 200,
15
+ :loop => false,
16
+ :frequency => 10,
17
+ :fields => [],
18
+ :conditions => [],
19
+ :start => nil,
20
+ :end => nil,
21
+ :debug => false,
22
+ :fkat => false
23
+ }
24
+
25
+ option_parser = OptionParser.new do |opts|
26
+ opts.banner = "Usage: #{$0} [options] <log table>"
27
+
28
+ opts.on("--app app", String, "Specify the Heroku app") do |app|
29
+ options[:app] = app.strip
30
+ end
31
+
32
+ opts.on("-f [secs]", Integer,
33
+ "Continuously check for more data, every [secs] seconds.", "Default: #{options[:frequency]}") do |s|
34
+ options[:loop] = true
35
+ options[:frequency] = s if s
36
+ end
37
+
38
+ opts.on("-r limit", Integer, "Specify how many log entries to fetch.", "Default: #{options[:limit]}") do |n|
39
+ options[:limit] = n
40
+ end
41
+
42
+ opts.on("-k fields", String, "Specify a comma-separated list of fields to retrieve.") do |k|
43
+ options[:fields] = k.split(",").map {|f| f.strip}
44
+ end
45
+
46
+ opts.on("-c condition", String, "Specify a condition to match. May be used multiple times") do |c|
47
+ options[:conditions] << c.strip
48
+ end
49
+
50
+ opts.on("-s starttime", String, "Specify the start time for the query") do |c|
51
+ options[:start] = c.strip
52
+ end
53
+
54
+ opts.on("-e endtime", String, "Specify the end time for the query") do |c|
55
+ options[:end] = c.strip
56
+ end
57
+
58
+ opts.on("--flat", "Do not expand log entries when printing them") do
59
+ options[:flat] = true
60
+ end
61
+
62
+ opts.on("-v", "Show debug information") do
63
+ options[:debug] = true
64
+ end
65
+
66
+ opts.on( '-h', '--help', 'Display this screen' ) do
67
+ options[:help] = true
68
+ end
69
+
70
+ end
71
+
72
+ # Parse, and get the required <table>
73
+ option_parser.parse!
74
+ table = ARGV.pop
75
+ if !table or options[:help]
76
+ help option_parser, options
77
+ exit(1)
78
+ end
79
+ table.strip!
80
+
81
+ # and run
82
+ t = LogwormTail.new(table, options).run
83
+
84
+
data/lib/base/db.rb CHANGED
@@ -31,7 +31,6 @@ module Logworm
31
31
  raise ForbiddenAccessException.new("Incorrect URL Format #{url}") unless match and match.size == 6
32
32
  @consumer_key, @consumer_secret, @host, @token, @token_secret = match[1..5]
33
33
  @connection = OAuth::AccessToken.new(OAuth::Consumer.new(@consumer_key, @consumer_secret), @token, @token_secret)
34
- reset_stats
35
34
  end
36
35
 
37
36
  def self.with_tokens(token, token_secret)
@@ -115,11 +114,12 @@ module Logworm
115
114
  resp = db_call(:get, "#{host_with_protocol}/amqp_url")
116
115
  @amqp_url = resp["url"]
117
116
  @stats_freq = resp["stats_freq"]
118
- $stderr.puts "logworm server acquired: #{@amqp_url}"
117
+ $stderr.puts "logworm server acquired: #{@amqp_url.gsub(/[^@]+@/, "amqp://")}"
119
118
  Minion.amqp_url = @amqp_url
119
+ reset_stats
120
120
  rescue
121
121
  @last_attempt = Time.now
122
- $stderr.puts "logworm cannot connect to server; please wait at least #{RETRY_FREQUENCY} seconds. log entries will be lost"
122
+ $stderr.puts "logworm cannot connect to server; waiting #{RETRY_FREQUENCY} seconds before retry. log entries will be lost"
123
123
  end
124
124
  end
125
125
  !(@amqp_url.nil?)
@@ -149,9 +149,11 @@ module Logworm
149
149
  end
150
150
 
151
151
  def push_stats()
152
- to_amqp("lw.stats", {:avg => (@total_time / @tock), :max => @amqp_max, :min => @amqp_min,
153
- :total => @total_time, :count => @tock, :sampling_length => (Time.now - @sampling_start) })
154
- $stderr.puts "logworm statistics: #{@tock} messages sent in #{Time.now - @sampling_start}. Times: #{(@total_time / @tock) * 1000}/#{@amqp_min * 1000}/#{@amqp_max * 1000} (avg/min/max seconds)"
152
+ avg = sprintf('%.6f', (@total_time / @tock)).to_f
153
+ len = sprintf('%.2f', (Time.now - @sampling_start)).to_f
154
+ to_amqp("lw.stats", {:avg => avg , :max => @amqp_max, :min => @amqp_min,
155
+ :total => @total_time, :count => @tock, :sampling_length => len})
156
+ $stderr.puts "logworm statistics: #{@tock} messages sent in last #{len} secs. Times: #{avg * 1000}/#{@amqp_min * 1000}/#{@amqp_max * 1000} (avg/min/max msecs)"
155
157
  reset_stats
156
158
  end
157
159
 
@@ -0,0 +1,93 @@
1
+ require 'json'
2
+
3
+ module Logworm
4
+ class LogwormException < Exception ; end
5
+
6
+ class Logger
7
+ $lr_queue = []
8
+
9
+ ###
10
+ # Use connection to the backend servers specified in environment variable or config file
11
+ ###
12
+ def self.use_default_db
13
+ $lw_server = DB.from_config
14
+ end
15
+
16
+ ###
17
+ # Use a connection to a manually specified server
18
+ ###
19
+ def self.use_db(db)
20
+ $lw_server = db
21
+ end
22
+
23
+ ###
24
+ # Returns a reference to the current backend server
25
+ ###
26
+ def self.db
27
+ $lw_server
28
+ end
29
+
30
+
31
+ ###
32
+ # Starts a new cycle: sets a request_id variable, so that all entries (until flush) share that id
33
+ ###
34
+ def self.start_cycle
35
+ $request_id = "#{Thread.current.object_id}-#{(Time.now.utc.to_f * 1000).to_i}"
36
+ end
37
+
38
+ ###
39
+ # Record an entry. Not sent to the servers until 'flush' is called
40
+ #
41
+ # Warning: may raise Exception if there's a problem. It's up to the called to rescue from it in order to continue the processing
42
+ # of a web request, for example.
43
+ ###
44
+ def self.log(table, values = {})
45
+ return unless table and (table.is_a? String or table.is_a? Symbol)
46
+ return unless values.is_a? Hash
47
+
48
+ # Turn keys into symbols, delete empty ones, rename conflicting ones
49
+ kvalues = values.delete_if {|k,v| k.to_s == ""}.map {|k,v| [k.to_sym, v]}
50
+ kvalues = Hash[*kvalues.flatten]
51
+ [:_ts, :_ts_utc, :_request_id].each do |k|
52
+ kvalues["orig_#{k}".to_sym] = kvalues[k] if kvalues.has_key? k
53
+ end
54
+
55
+ # Add information
56
+ ts = Time.now.utc
57
+ kvalues[:_ts_utc] = (ts.to_f * 1000).to_i
58
+ kvalues[:_ts] = ts.strftime("%Y-%m-%dT%H:%M:%SZ")
59
+ kvalues[:_request_id] = $request_id if $request_id
60
+
61
+ # Enqueue
62
+ $lr_queue << [table, kvalues]
63
+ end
64
+
65
+ ###
66
+ # Sends the entries to the server, if configured
67
+ # Returns the number of entries send, and the time it takes
68
+ #
69
+ # Warning: may raise Exception if there's a problem. It's up to the called to rescue from it in order to continue the processing
70
+ # of a web request, for example.
71
+ ###
72
+ def self.flush
73
+ to_send = $lr_queue.size
74
+
75
+ # Return if no entries
76
+ return [0,0] if to_send == 0
77
+
78
+ # Return if no server
79
+ unless $lw_server
80
+ $stderr.puts "\t logworm not configured. #{to_send} entries dropped."
81
+ $lr_queue = []
82
+ return [0,0]
83
+ end
84
+
85
+ startTime = Time.now
86
+ $lw_server.batch_log($lr_queue.to_json)
87
+ $lr_queue = []
88
+ endTime = Time.now
89
+
90
+ [to_send, (endTime - startTime)]
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,61 @@
1
+ require 'rack/request'
2
+
3
+ module Logworm
4
+ class Rack
5
+
6
+ def initialize(app, options = {})
7
+ @app = app
8
+
9
+ @log_requests = (options[:donot_log_requests].nil? or options[:donot_log_requests] != true)
10
+ @log_headers = (options[:log_headers] and options[:log_headers] == true)
11
+ @dev_logging = (options[:log_in_development] and options[:log_in_development] == true)
12
+ Logger.use_default_db
13
+ @timeout = 1
14
+ end
15
+
16
+ def call(env)
17
+ return @app.call(env) unless (ENV['RACK_ENV'] == 'production' or (ENV['RACK_ENV'] == 'development' and @dev_logging))
18
+
19
+ Logger.start_cycle
20
+ begin
21
+ startTime = Time.now
22
+ status, response_headers, body = @app.call(env)
23
+ appTime = (Time.now - startTime)
24
+ ensure
25
+ log_request(env, status, response_headers, appTime)
26
+ return [status, response_headers, body]
27
+ end
28
+ end
29
+
30
+ private
31
+ def log_request(env, status, response_headers, appTime)
32
+ method = env['REQUEST_METHOD']
33
+ path = (env['REQUEST_PATH'].nil? or env['REQUEST_PATH'] == "") ? "/" : env['REQUEST_PATH']
34
+ ip = env['REMOTE_ADDR']
35
+ http_headers = env.reject {|k,v| !(k.to_s =~ /^HTTP/) }
36
+ queue_size = env['HTTP_X_HEROKU_QUEUE_DEPTH'].nil? ? -1 : env['HTTP_X_HEROKU_QUEUE_DEPTH'].to_i
37
+
38
+ entry = { :summary => "#{method} #{path} - #{status} #{appTime}",
39
+ :request_method => method,
40
+ :request_path => path,
41
+ :request_ip => ip,
42
+ :input => ::Rack::Request.new(env).params,
43
+ :response_status => status,
44
+ :profiling => appTime,
45
+ :queue_size => queue_size}
46
+ entry[:request_headers] = http_headers if @log_headers
47
+ entry[:response_headers] = response_headers if @log_headers
48
+ Logger.log(:web_log, entry) if @log_requests
49
+
50
+ begin
51
+ Timeout::timeout(@timeout) {
52
+ sent, elapsed = Logger.flush
53
+ }
54
+ rescue Exception => e
55
+ # Ignore --nothing we can do. The list of logs may (and most likely will) be preserved for the next request
56
+ env['rack.errors'].puts("logworm call failed: #{e}")
57
+ end
58
+ end
59
+
60
+ end
61
+ end