logworm_amqp 0.8.9 → 0.9.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/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