request-log-analyzer 1.8.0 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/bin/request-log-analyzer +2 -2
- data/lib/cli/database_console_init.rb +1 -1
- data/lib/cli/tools.rb +2 -1
- data/lib/mixins/gets_memory_protection.rb +1 -1
- data/lib/request_log_analyzer/aggregator/summarizer.rb +2 -2
- data/lib/request_log_analyzer/aggregator.rb +3 -3
- data/lib/request_log_analyzer/controller.rb +16 -12
- data/lib/request_log_analyzer/database.rb +5 -3
- data/lib/request_log_analyzer/file_format/oink.rb +1 -1
- data/lib/request_log_analyzer/file_format/rails3.rb +23 -9
- data/lib/request_log_analyzer/file_format.rb +27 -8
- data/lib/request_log_analyzer/filter.rb +3 -5
- data/lib/request_log_analyzer/output/fancy_html.rb +0 -5
- data/lib/request_log_analyzer/output.rb +6 -7
- data/lib/request_log_analyzer/source/log_parser.rb +3 -1
- data/lib/request_log_analyzer/source.rb +2 -6
- data/lib/request_log_analyzer/tracker.rb +6 -5
- data/lib/request_log_analyzer.rb +36 -17
- data/request-log-analyzer.gemspec +11 -4
- data/spec/integration/command_line_usage_spec.rb +3 -3
- data/spec/integration/mailer_spec.rb +1 -1
- data/spec/integration/munin_plugins_rails_spec.rb +1 -1
- data/spec/integration/scout_spec.rb +1 -1
- data/spec/lib/helpers.rb +1 -1
- data/spec/unit/aggregator/database_inserter_spec.rb +1 -1
- data/spec/unit/aggregator/summarizer_spec.rb +1 -1
- data/spec/unit/controller/controller_spec.rb +1 -1
- data/spec/unit/controller/log_processor_spec.rb +1 -1
- data/spec/unit/database/base_class_spec.rb +1 -1
- data/spec/unit/database/connection_spec.rb +1 -1
- data/spec/unit/database/database_spec.rb +1 -1
- data/spec/unit/file_format/amazon_s3_format_spec.rb +1 -1
- data/spec/unit/file_format/apache_format_spec.rb +1 -1
- data/spec/unit/file_format/common_regular_expressions_spec.rb +1 -1
- data/spec/unit/file_format/delayed_job_format_spec.rb +1 -1
- data/spec/unit/file_format/file_format_api_spec.rb +2 -2
- data/spec/unit/file_format/format_autodetection_spec.rb +1 -1
- data/spec/unit/file_format/line_definition_spec.rb +1 -1
- data/spec/unit/file_format/merb_format_spec.rb +1 -1
- data/spec/unit/file_format/mysql_format_spec.rb +1 -1
- data/spec/unit/file_format/oink_format_spec.rb +1 -1
- data/spec/unit/file_format/postgresql_format_spec.rb +1 -1
- data/spec/unit/file_format/rack_format_spec.rb +1 -1
- data/spec/unit/file_format/rails3_format_spec.rb +4 -4
- data/spec/unit/file_format/rails_format_spec.rb +1 -1
- data/spec/unit/filter/anonymize_filter_spec.rb +1 -1
- data/spec/unit/filter/field_filter_spec.rb +1 -1
- data/spec/unit/filter/filter_spec.rb +1 -1
- data/spec/unit/filter/timespan_filter_spec.rb +1 -1
- data/spec/unit/mailer_spec.rb +1 -1
- data/spec/unit/request_spec.rb +1 -1
- data/spec/unit/source/log_parser_spec.rb +1 -1
- data/spec/unit/tracker/duration_tracker_spec.rb +1 -1
- data/spec/unit/tracker/frequency_tracker_spec.rb +1 -1
- data/spec/unit/tracker/hourly_spread_spec.rb +1 -1
- data/spec/unit/tracker/numeric_value_tracker_spec.rb +1 -1
- data/spec/unit/tracker/timespan_tracker_spec.rb +1 -1
- data/spec/unit/tracker/tracker_api_spec.rb +1 -1
- data/spec/unit/tracker/traffic_tracker_spec.rb +1 -1
- data/tasks/github-gem.rake +5 -5
- data/tasks/request_log_analyzer.rake +102 -0
- metadata +56 -12
data/.gitignore
CHANGED
data/bin/request-log-analyzer
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: utf-8
|
3
3
|
|
4
|
-
$:.unshift(File.
|
4
|
+
$:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
5
5
|
require 'request_log_analyzer'
|
6
6
|
require 'cli/command_line_arguments'
|
7
7
|
require 'cli/progressbar'
|
@@ -93,6 +93,7 @@ rescue CommandLine::Error => e
|
|
93
93
|
puts " --report-amount <amount> Maximum numer of results per report."
|
94
94
|
puts " --silent Skip the commercials (includes --no-progress)."
|
95
95
|
puts " --yaml <filename> Dump the results in YAML format in the given file."
|
96
|
+
puts " --parse-strategy <strategy> Legal values are 'assume-correct' or 'cautious'."
|
96
97
|
puts
|
97
98
|
puts "Examples:"
|
98
99
|
puts " request-log-analyzer production.log"
|
@@ -114,7 +115,6 @@ when :console
|
|
114
115
|
require 'cli/database_console'
|
115
116
|
DatabaseConsole.new(arguments).run!
|
116
117
|
when :strip
|
117
|
-
require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
|
118
118
|
RequestLogAnalyzer::LogProcessor.build(:strip, arguments).run!
|
119
119
|
else
|
120
120
|
unless arguments[:silent]
|
data/lib/cli/tools.rb
CHANGED
@@ -34,7 +34,8 @@ def install_rake_tasks(install_type = :rails)
|
|
34
34
|
if install_type.to_sym == :rails
|
35
35
|
require 'ftools'
|
36
36
|
if File.directory?('./lib/tasks/')
|
37
|
-
File.
|
37
|
+
task_file = File.expand_path('../../tasks/request_log_analyzer.rake', File.dirname(__FILE__))
|
38
|
+
File.copy(task_file, './lib/tasks/request_log_analyze.rake')
|
38
39
|
puts "Installed rake tasks."
|
39
40
|
puts "To use, run: rake rla:report"
|
40
41
|
else
|
@@ -32,7 +32,7 @@ module RequestLogAnalyzer::Aggregator
|
|
32
32
|
# <tt>optiont</tt> The options to pass to the trackers.
|
33
33
|
def track(tracker_klass, value_field = {}, other_options = {})
|
34
34
|
options = value_field.kind_of?(Symbol) ? other_options.merge(:value => value_field) : value_field.merge(other_options)
|
35
|
-
tracker_klass = RequestLogAnalyzer::Tracker.const_get(RequestLogAnalyzer
|
35
|
+
tracker_klass = RequestLogAnalyzer::Tracker.const_get(RequestLogAnalyzer.to_camelcase(tracker_klass)) if tracker_klass.kind_of?(Symbol)
|
36
36
|
@trackers << tracker_klass.new(options)
|
37
37
|
end
|
38
38
|
end
|
@@ -85,7 +85,7 @@ module RequestLogAnalyzer::Aggregator
|
|
85
85
|
trackers_export = @trackers.inject({}) do |export, tracker|
|
86
86
|
export[tracker.title] = tracker.to_yaml_object; export
|
87
87
|
end
|
88
|
-
YAML
|
88
|
+
YAML.dump(trackers_export)
|
89
89
|
end
|
90
90
|
|
91
91
|
# Call report on all trackers.
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module RequestLogAnalyzer::Aggregator
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
autoload :Echo, 'request_log_analyzer/aggregator/echo'
|
4
|
+
autoload :Summarizer, 'request_log_analyzer/aggregator/summarizer'
|
5
|
+
autoload :DatabaseInserter, 'request_log_analyzer/aggregator/database_inserter'
|
6
6
|
|
7
7
|
# The base class of an aggregator. This class provides the interface to which
|
8
8
|
# every aggregator should comply (by simply subclassing this class).
|
@@ -21,7 +21,7 @@ module RequestLogAnalyzer
|
|
21
21
|
# <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
|
22
22
|
def self.build_from_arguments(arguments)
|
23
23
|
|
24
|
-
require
|
24
|
+
require 'mixins/gets_memory_protection' if arguments[:gets_memory_protection]
|
25
25
|
|
26
26
|
options = {}
|
27
27
|
|
@@ -47,6 +47,7 @@ module RequestLogAnalyzer
|
|
47
47
|
options[:mailhost] = arguments[:mailhost]
|
48
48
|
options[:mailsubject] = arguments[:mailsubject]
|
49
49
|
options[:silent] = arguments[:silent]
|
50
|
+
options[:parse_strategy] = arguments[:parse_strategy]
|
50
51
|
|
51
52
|
# Apache format workaround
|
52
53
|
if arguments[:rails_format]
|
@@ -155,7 +156,7 @@ module RequestLogAnalyzer
|
|
155
156
|
if options[:output].is_a?(Class)
|
156
157
|
output_class = options[:output]
|
157
158
|
else
|
158
|
-
output_class = RequestLogAnalyzer::Output
|
159
|
+
output_class = RequestLogAnalyzer::Output.const_get(options[:output])
|
159
160
|
end
|
160
161
|
|
161
162
|
output_sort = options[:report_sort].split(',').map { |s| s.to_sym }
|
@@ -183,14 +184,17 @@ module RequestLogAnalyzer
|
|
183
184
|
end
|
184
185
|
|
185
186
|
# Kickstart the controller
|
186
|
-
controller =
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
187
|
+
controller =
|
188
|
+
Controller.new(RequestLogAnalyzer::Source::LogParser.new(file_format,
|
189
|
+
:source_files => options[:source_files],
|
190
|
+
:parse_strategy => options[:parse_strategy]),
|
191
|
+
{ :output => output_instance,
|
192
|
+
:database => options[:database], # FUGLY!
|
193
|
+
:yaml => options[:yaml],
|
194
|
+
:reset_database => options[:reset_database],
|
195
|
+
:no_progress => options[:no_progress],
|
196
|
+
:silent => options[:silent]
|
197
|
+
})
|
194
198
|
|
195
199
|
# register filters
|
196
200
|
if options[:after] || options[:before]
|
@@ -283,7 +287,7 @@ module RequestLogAnalyzer
|
|
283
287
|
# Adds an aggregator to the controller. The aggregator will be called for every request
|
284
288
|
# that is parsed from the provided sources (see add_source)
|
285
289
|
def add_aggregator(agg)
|
286
|
-
agg = RequestLogAnalyzer::Aggregator.const_get(RequestLogAnalyzer
|
290
|
+
agg = RequestLogAnalyzer::Aggregator.const_get(RequestLogAnalyzer.to_camelcase(agg)) if agg.kind_of?(Symbol)
|
287
291
|
@aggregators << agg.new(@source, @options)
|
288
292
|
end
|
289
293
|
|
@@ -291,7 +295,7 @@ module RequestLogAnalyzer
|
|
291
295
|
|
292
296
|
# Adds a request filter to the controller.
|
293
297
|
def add_filter(filter, filter_options = {})
|
294
|
-
filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer
|
298
|
+
filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer.to_camelcase(filter)) if filter.kind_of?(Symbol)
|
295
299
|
@filters << filter.new(source.file_format, @options.merge(filter_options))
|
296
300
|
end
|
297
301
|
|
@@ -3,9 +3,11 @@ require 'active_record'
|
|
3
3
|
|
4
4
|
class RequestLogAnalyzer::Database
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
autoload :Connection, 'request_log_analyzer/database/connection'
|
7
|
+
autoload :Base, 'request_log_analyzer/database/base'
|
8
|
+
autoload :Request, 'request_log_analyzer/database/request'
|
9
|
+
autoload :Source, 'request_log_analyzer/database/source'
|
10
|
+
autoload :Warning, 'request_log_analyzer/database/warning'
|
9
11
|
|
10
12
|
include RequestLogAnalyzer::Database::Connection
|
11
13
|
|
@@ -36,7 +36,7 @@ class RequestLogAnalyzer::FileFormat::Oink < RequestLogAnalyzer::FileFormat::Rai
|
|
36
36
|
@pids ||= {}
|
37
37
|
end
|
38
38
|
|
39
|
-
class Request
|
39
|
+
class Request < RequestLogAnalyzer::FileFormat::Rails::Request
|
40
40
|
# Overrides the #validate method to handle PID updating.
|
41
41
|
def validate
|
42
42
|
update_pids
|
@@ -8,11 +8,11 @@ module RequestLogAnalyzer::FileFormat
|
|
8
8
|
|
9
9
|
extend CommonRegularExpressions
|
10
10
|
|
11
|
-
# Started GET "/
|
11
|
+
# beta4: Started GET "/" for 127.0.0.1 at Wed Jul 07 09:13:27 -0700 2010 (different time format)
|
12
12
|
line_definition :started do |line|
|
13
13
|
line.header = true
|
14
14
|
line.teaser = /Started /
|
15
|
-
line.regexp = /Started ([A-Z]+) "([^"]+)" for (#{ip_address}) at (#{timestamp('%
|
15
|
+
line.regexp = /Started ([A-Z]+) "([^"]+)" for (#{ip_address}) at (#{timestamp('%a %b %d %H:%M:%S %z %Y')})/
|
16
16
|
|
17
17
|
line.capture(:method)
|
18
18
|
line.capture(:path)
|
@@ -30,14 +30,18 @@ module RequestLogAnalyzer::FileFormat
|
|
30
30
|
line.capture(:format)
|
31
31
|
end
|
32
32
|
|
33
|
-
# Completed in
|
33
|
+
# Completed 200 OK in 224ms (Views: 200.2ms | ActiveRecord: 3.4ms)
|
34
|
+
# Completed 302 Found in 23ms
|
35
|
+
# Completed in 189ms
|
34
36
|
line_definition :completed do |line|
|
35
37
|
line.footer = true
|
36
38
|
line.teaser = /Completed /
|
37
|
-
line.regexp = /Completed (\d+) .*
|
39
|
+
line.regexp = /Completed (\d+)? .*in (\d+)ms(?:[^(]*\(Views: ((?:\d|\.)+)ms .* ActiveRecord: ((?:\d|\.)+)ms\))?/
|
38
40
|
|
39
41
|
line.capture(:status).as(:integer)
|
40
42
|
line.capture(:duration).as(:duration, :unit => :msec)
|
43
|
+
line.capture(:view).as(:duration, :unit => :msec)
|
44
|
+
line.capture(:db).as(:duration, :unit => :msec)
|
41
45
|
end
|
42
46
|
|
43
47
|
# ActionView::Template::Error (undefined local variable or method `field' for #<Class>) on line #3 of /Users/willem/Code/warehouse/app/views/queries/execute.csv.erb:
|
@@ -69,18 +73,28 @@ module RequestLogAnalyzer::FileFormat
|
|
69
73
|
analyze.frequency :status, :title => 'HTTP statuses returned'
|
70
74
|
|
71
75
|
analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
|
72
|
-
|
73
|
-
|
76
|
+
analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
|
77
|
+
analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
|
74
78
|
|
75
79
|
analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
|
76
80
|
:if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
|
77
81
|
end
|
78
82
|
|
79
83
|
class Request < RequestLogAnalyzer::Request
|
80
|
-
|
81
|
-
|
84
|
+
# Used to handle conversion of abbrev. month name to a digit
|
85
|
+
MONTHS = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
|
86
|
+
|
87
|
+
def convert_timestamp(value, definition)
|
88
|
+
value.gsub!(/\W/,'')
|
89
|
+
time_as_str = value[-4..-1] # year
|
90
|
+
# convert the month to a 2-digit representation
|
91
|
+
month = MONTHS.index(value[3..5])+1
|
92
|
+
month < 10 ? time_as_str << "0#{month}" : time_as_str << month
|
93
|
+
|
94
|
+
time_as_str << value[6..13] # day of month + time
|
95
|
+
time_as_str.to_i
|
82
96
|
end
|
83
97
|
end
|
84
98
|
|
85
99
|
end
|
86
|
-
end
|
100
|
+
end
|
@@ -1,8 +1,17 @@
|
|
1
1
|
module RequestLogAnalyzer::FileFormat
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
autoload :Rails, 'request_log_analyzer/file_format/rails'
|
4
|
+
autoload :Rails3, 'request_log_analyzer/file_format/rails3'
|
5
|
+
autoload :RailsDevelopment, 'request_log_analyzer/file_format/rails_development'
|
6
|
+
autoload :Oink, 'request_log_analyzer/file_format/oink'
|
7
|
+
autoload :Rack, 'request_log_analyzer/file_format/rack'
|
8
|
+
autoload :Merb, 'request_log_analyzer/file_format/merb'
|
9
|
+
autoload :Mysql, 'request_log_analyzer/file_format/mysql'
|
10
|
+
autoload :Postgresql, 'request_log_analyzer/file_format/postgresql'
|
11
|
+
autoload :DelayedJob, 'request_log_analyzer/file_format/delayed_job'
|
12
|
+
autoload :DelayedJob2, 'request_log_analyzer/file_format/delayed_job2'
|
13
|
+
autoload :Apache, 'request_log_analyzer/file_format/apache'
|
14
|
+
autoload :AmazonS3, 'request_log_analyzer/file_format/amazon_s3'
|
6
15
|
|
7
16
|
# Loads a FileFormat::Base subclass instance.
|
8
17
|
# You can provide:
|
@@ -24,7 +33,7 @@ module RequestLogAnalyzer::FileFormat
|
|
24
33
|
# load a format from a ruby file
|
25
34
|
require file_format
|
26
35
|
|
27
|
-
const = RequestLogAnalyzer
|
36
|
+
const = RequestLogAnalyzer.to_camelcase(File.basename(file_format, '.rb'))
|
28
37
|
if RequestLogAnalyzer::FileFormat.const_defined?(const)
|
29
38
|
klass = RequestLogAnalyzer::FileFormat.const_get(const)
|
30
39
|
elsif Object.const_defined?(const)
|
@@ -35,7 +44,7 @@ module RequestLogAnalyzer::FileFormat
|
|
35
44
|
|
36
45
|
else
|
37
46
|
# load a provided file format
|
38
|
-
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer
|
47
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer.to_camelcase(file_format))
|
39
48
|
end
|
40
49
|
|
41
50
|
# check the returned klass to see if it can be used
|
@@ -47,7 +56,7 @@ module RequestLogAnalyzer::FileFormat
|
|
47
56
|
|
48
57
|
# Returns an array of all FileFormat instances that are shipped with request-log-analyzer by default.
|
49
58
|
def self.all_formats
|
50
|
-
@all_formats ||= Dir[File.
|
59
|
+
@all_formats ||= Dir[File.expand_path('file_format/*.rb', File.dirname(__FILE__))].map do |file|
|
51
60
|
self.load(File.basename(file, '.rb'))
|
52
61
|
end
|
53
62
|
end
|
@@ -247,11 +256,21 @@ module RequestLogAnalyzer::FileFormat
|
|
247
256
|
request_class.create(self, *hashes)
|
248
257
|
end
|
249
258
|
|
250
|
-
# Checks whether the
|
251
|
-
# A file format should have at least a header and a footer line type
|
259
|
+
# Checks whether the file format is valid so it can be safely used with RLA.
|
252
260
|
def valid?
|
261
|
+
valid_line_definitions? && valid_request_class?
|
262
|
+
end
|
263
|
+
|
264
|
+
# Checks whether the line definitions form a valid language.
|
265
|
+
# A file format should have at least a header and a footer line type
|
266
|
+
def valid_line_definitions?
|
253
267
|
line_definitions.any? { |(name, ld)| ld.header } && line_definitions.any? { |(name, ld)| ld.footer }
|
254
268
|
end
|
269
|
+
|
270
|
+
# Checks whether the request class inherits from the base Request class.
|
271
|
+
def valid_request_class?
|
272
|
+
request_class.ancestors.include?(RequestLogAnalyzer::Request)
|
273
|
+
end
|
255
274
|
|
256
275
|
# Returns true if this language captures the given symbol in one of its line definitions
|
257
276
|
def captures?(name)
|
@@ -1,10 +1,8 @@
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
RequestLogAnalyzer::load_default_class_file(self, const)
|
7
|
-
end
|
3
|
+
autoload :Field, 'request_log_analyzer/filter/field'
|
4
|
+
autoload :Timespan, 'request_log_analyzer/filter/timespan'
|
5
|
+
autoload :Anonymize, 'request_log_analyzer/filter/anonymize'
|
8
6
|
|
9
7
|
# Base filter class used to filter input requests.
|
10
8
|
# All filters should interit from this base.
|
@@ -10,11 +10,6 @@ module RequestLogAnalyzer::Output
|
|
10
10
|
|
11
11
|
class FancyHTML < HTML
|
12
12
|
|
13
|
-
# Load class files if needed
|
14
|
-
def self.const_missing(const)
|
15
|
-
RequestLogAnalyzer::load_default_class_file(self, const)
|
16
|
-
end
|
17
|
-
|
18
13
|
def report_tracker(tracker)
|
19
14
|
case tracker
|
20
15
|
when RequestLogAnalyzer::Tracker::HourlySpread then report_hourly_spread(tracker)
|
@@ -1,11 +1,10 @@
|
|
1
1
|
# Module for generating output
|
2
2
|
module RequestLogAnalyzer::Output
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
autoload :FixedWidth, 'request_log_analyzer/output/fixed_width'
|
5
|
+
autoload :HTML, 'request_log_analyzer/output/html'
|
6
|
+
autoload :FancyHTML, 'request_log_analyzer/output/fancy_html'
|
7
|
+
|
9
8
|
# Loads a Output::Base subclass instance.
|
10
9
|
def self.load(file_format, *args)
|
11
10
|
|
@@ -21,7 +20,7 @@ module RequestLogAnalyzer::Output
|
|
21
20
|
elsif file_format.kind_of?(String) && File.exist?(file_format)
|
22
21
|
# load a format from a ruby file
|
23
22
|
require file_format
|
24
|
-
const = RequestLogAnalyzer
|
23
|
+
const = RequestLogAnalyzer.to_camelcase(File.basename(file_format, '.rb'))
|
25
24
|
if RequestLogAnalyzer::FileFormat.const_defined?(const)
|
26
25
|
klass = RequestLogAnalyzer::Output.const_get(const)
|
27
26
|
elsif Object.const_defined?(const)
|
@@ -32,7 +31,7 @@ module RequestLogAnalyzer::Output
|
|
32
31
|
|
33
32
|
else
|
34
33
|
# load a provided file format
|
35
|
-
klass = RequestLogAnalyzer::Output.const_get(RequestLogAnalyzer
|
34
|
+
klass = RequestLogAnalyzer::Output.const_get(RequestLogAnalyzer.to_camelcase(file_format))
|
36
35
|
end
|
37
36
|
|
38
37
|
# check the returned klass to see if it can be used
|
@@ -43,7 +43,9 @@ module RequestLogAnalyzer::Source
|
|
43
43
|
@progress_handler = nil
|
44
44
|
|
45
45
|
@options[:parse_strategy] ||= DEFAULT_PARSE_STRATEGY
|
46
|
-
|
46
|
+
unless PARSE_STRATEGIES.include?(@options[:parse_strategy])
|
47
|
+
raise "Unknown parse strategy: #{@options[@parse_strategy]}"
|
48
|
+
end
|
47
49
|
end
|
48
50
|
|
49
51
|
# Reads the input, which can either be a file, sequence of files or STDIN to parse
|
@@ -8,12 +8,8 @@
|
|
8
8
|
# - Currently, RequestLogAnalyzer::Source::LogParser is the only implemented source.
|
9
9
|
module RequestLogAnalyzer::Source
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
# <tt>const</tt>:: The constant to load in the RequestLogAnalyzer::Source namespace.
|
14
|
-
def self.const_missing(const)
|
15
|
-
RequestLogAnalyzer::load_default_class_file(self, const)
|
16
|
-
end
|
11
|
+
autoload :LogParser, 'request_log_analyzer/source/log_parser'
|
12
|
+
autoload :DatabaseLoader, 'request_log_analyzer/source/database_loader'
|
17
13
|
|
18
14
|
# The base Source class. All other sources should inherit from this class.
|
19
15
|
#
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module RequestLogAnalyzer::Tracker
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
autoload :Duration, 'request_log_analyzer/tracker/duration'
|
4
|
+
autoload :Frequency, 'request_log_analyzer/tracker/frequency'
|
5
|
+
autoload :HourlySpread, 'request_log_analyzer/tracker/hourly_spread'
|
6
|
+
autoload :NumericValue, 'request_log_analyzer/tracker/numeric_value'
|
7
|
+
autoload :Timespan, 'request_log_analyzer/tracker/timespan'
|
8
|
+
autoload :Traffic, 'request_log_analyzer/tracker/traffic'
|
8
9
|
|
9
10
|
# Base Tracker class. All other trackers inherit from this class
|
10
11
|
#
|
data/lib/request_log_analyzer.rb
CHANGED
@@ -4,40 +4,59 @@ require 'date'
|
|
4
4
|
Encoding.default_external = 'binary' if defined? Encoding and Encoding.respond_to? 'default_external='
|
5
5
|
|
6
6
|
# RequestLogAnalyzer is the base namespace in which all functionality of RequestLogAnalyzer is implemented.
|
7
|
+
# This module itselfs contains some functions to help with class and source file loading. The actual
|
8
|
+
# application startup code resides in the {RequestLogAnalyzer::Controller} class.
|
7
9
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
+
# The {RequestLogAnalyzer::VERSION} constant can be used to determine what version of request-log-analyzer
|
11
|
+
# is running.
|
10
12
|
module RequestLogAnalyzer
|
11
13
|
|
12
14
|
# The current version of request-log-analyzer.
|
13
15
|
# Do not change the value by hand; it will be updated automatically by the gem release script.
|
14
|
-
VERSION = "1.8.
|
16
|
+
VERSION = "1.8.1"
|
15
17
|
|
16
|
-
# Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
|
17
|
-
# <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
|
18
|
-
def self.const_missing(const)
|
19
|
-
load_default_class_file(RequestLogAnalyzer, const)
|
20
|
-
end
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
autoload :Controller, 'request_log_analyzer/controller'
|
20
|
+
autoload :Aggregator, 'request_log_analyzer/aggregator'
|
21
|
+
autoload :Database, 'request_log_analyzer/database'
|
22
|
+
autoload :FileFormat, 'request_log_analyzer/file_format'
|
23
|
+
autoload :Filter, 'request_log_analyzer/filter'
|
24
|
+
autoload :LineDefinition, 'request_log_analyzer/line_definition'
|
25
|
+
autoload :LogProcessor, 'request_log_analyzer/log_processor'
|
26
|
+
autoload :Mailer, 'request_log_analyzer/mailer'
|
27
|
+
autoload :Output, 'request_log_analyzer/output'
|
28
|
+
autoload :Request, 'request_log_analyzer/request'
|
29
|
+
autoload :Source, 'request_log_analyzer/source'
|
30
|
+
autoload :Tracker, 'request_log_analyzer/tracker'
|
31
|
+
|
32
|
+
# Loads constants that reside in the RequestLogAnalyzer tree using the constant name and its base
|
33
|
+
# constant to determine the filename.
|
34
|
+
# @param [Module] base The base constant to load the constant from. This should be <tt>Foo</tt> when
|
35
|
+
# the constant <tt>Foo::Bar</tt> is being loaded.
|
36
|
+
# @param [Symbol] const The constant to load from the base constant as a string or symbol. This
|
37
|
+
# should be <tt>"Bar"<tt> or <tt>:Bar</tt> when the constant <tt>Foo::Bar</tt> is being loaded.
|
38
|
+
# @return [Module] The loaded module, nil if it was not found on the expected location.
|
26
39
|
def self.load_default_class_file(base, const)
|
27
40
|
require "#{to_underscore("#{base.name}::#{const}")}"
|
28
41
|
base.const_get(const) if base.const_defined?(const)
|
29
42
|
end
|
30
43
|
|
31
|
-
# Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores
|
32
|
-
# This function can be used to load the file (using
|
33
|
-
# <tt>
|
44
|
+
# Convert a string/symbol in camelcase ({RequestLogAnalyzer::Controller}) to underscores
|
45
|
+
# (<tt>request_log_analyzer/controller</tt>). This function can be used to load the file (using
|
46
|
+
# <tt>require</tt>) in which the given constant is defined.
|
47
|
+
#
|
48
|
+
# @param [#to_s] str The string-like to convert in the following format: <tt>ModuleName::ClassName</tt>.
|
49
|
+
# @return [String] The input string converted to underscore form.
|
34
50
|
def self.to_underscore(str)
|
35
51
|
str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
|
36
52
|
end
|
37
53
|
|
38
54
|
# Convert a string/symbol in underscores (<tt>request_log_analyzer/controller</tt>) to camelcase
|
39
|
-
# (
|
40
|
-
#
|
55
|
+
# ({RequestLogAnalyzer::Controller}). This can be used to find the class that is defined in a given
|
56
|
+
# filename.
|
57
|
+
#
|
58
|
+
# @param [#to_s] str The string-like to convert in the following format: <tt>module_name/class_name</tt>.
|
59
|
+
# @return [String] The input string converted to camelcase form.
|
41
60
|
def self.to_camelcase(str)
|
42
61
|
str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
43
62
|
end
|
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
|
|
2
2
|
s.name = "request-log-analyzer"
|
3
3
|
|
4
4
|
# Do not set the version and date field manually, this is done by the release script
|
5
|
-
s.version = "1.8.
|
6
|
-
s.date = "2010-
|
5
|
+
s.version = "1.8.1"
|
6
|
+
s.date = "2010-08-31"
|
7
7
|
|
8
8
|
s.rubyforge_project = 'r-l-a'
|
9
9
|
|
@@ -27,8 +27,15 @@ Gem::Specification.new do |s|
|
|
27
27
|
|
28
28
|
s.requirements << "To use the database inserter, ActiveRecord and an appropriate database adapter are required."
|
29
29
|
|
30
|
-
s.add_development_dependency('
|
31
|
-
s.add_development_dependency('
|
30
|
+
s.add_development_dependency('rake')
|
31
|
+
s.add_development_dependency('rspec')
|
32
|
+
|
33
|
+
s.add_development_dependency('activerecord')
|
34
|
+
s.add_development_dependency('sqlite3-ruby')
|
35
|
+
|
36
|
+
s.add_development_dependency('git')
|
37
|
+
s.add_development_dependency('gemcutter')
|
38
|
+
|
32
39
|
|
33
40
|
s.authors = ['Willem van Bergen', 'Bart ten Brinke']
|
34
41
|
s.email = ['willem@railsdoctors.com', 'bart@railsdoctors.com']
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe RequestLogAnalyzer, 'running from command line' do
|
4
4
|
|
@@ -38,7 +38,7 @@ describe RequestLogAnalyzer, 'running from command line' do
|
|
38
38
|
|
39
39
|
it "should not write output with the --silent option" do
|
40
40
|
output = run("#{log_fixture(:rails_1x)} --silent --file #{temp_output_file(:report)}")
|
41
|
-
output.
|
41
|
+
output.should be_empty
|
42
42
|
File.exist?(temp_output_file(:report)).should be_true
|
43
43
|
end
|
44
44
|
|
@@ -85,7 +85,7 @@ describe RequestLogAnalyzer, 'running from command line' do
|
|
85
85
|
it "should dump the results to a YAML file" do
|
86
86
|
run("#{log_fixture(:rails_1x)} --yaml #{temp_output_file(:yaml)}")
|
87
87
|
File.exist?(temp_output_file(:yaml)).should be_true
|
88
|
-
YAML
|
88
|
+
YAML.load(File.read(temp_output_file(:yaml))).should have_at_least(1).item
|
89
89
|
end
|
90
90
|
|
91
91
|
it "should parse 4 requests from the standard input" do
|
data/spec/lib/helpers.rb
CHANGED
@@ -53,7 +53,7 @@ module RequestLogAnalyzer::Spec::Helpers
|
|
53
53
|
# Check if a given string can be found in the given file
|
54
54
|
# Returns the line number if found, nil otherwise
|
55
55
|
def find_string_in_file(string, file, options = {})
|
56
|
-
return nil unless File
|
56
|
+
return nil unless File.exists?(file)
|
57
57
|
|
58
58
|
line_counter = 0
|
59
59
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe RequestLogAnalyzer::FileFormat do
|
4
4
|
|
@@ -60,7 +60,7 @@ describe RequestLogAnalyzer::FileFormat do
|
|
60
60
|
end
|
61
61
|
|
62
62
|
it "should load a provided format file" do
|
63
|
-
format_filename = File.
|
63
|
+
format_filename = File.expand_path('../../lib/testing_format.rb', File.dirname(__FILE__))
|
64
64
|
@file_format = RequestLogAnalyzer::FileFormat.load(format_filename)
|
65
65
|
@file_format.should be_kind_of(TestingFormat)
|
66
66
|
end
|
@@ -10,7 +10,7 @@ describe RequestLogAnalyzer::FileFormat::Rails do
|
|
10
10
|
before(:each) { @file_format = RequestLogAnalyzer::FileFormat.load(:rails3) }
|
11
11
|
|
12
12
|
it "should parse :started lines correctly" do
|
13
|
-
line = 'Started GET "/queries" for 127.0.0.1 at
|
13
|
+
line = 'Started GET "/queries" for 127.0.0.1 at Thu Feb 25 16:15:18 -0800 2010'
|
14
14
|
@file_format.should parse_line(line).as(:started).and_capture(:method => 'GET',
|
15
15
|
:path => '/queries', :ip => '127.0.0.1', :timestamp => 20100225161518)
|
16
16
|
end
|
@@ -50,7 +50,7 @@ describe RequestLogAnalyzer::FileFormat::Rails do
|
|
50
50
|
|
51
51
|
it "should parse a successful request correctly" do
|
52
52
|
log = <<-EOLOG
|
53
|
-
Started GET "/" for 127.0.0.1 at
|
53
|
+
Started GET "/" for 127.0.0.1 at Fri Mar 19 06:40:41 -0700 2010
|
54
54
|
Processing by QueriesController#index as HTML
|
55
55
|
SQL (16.3ms) SHOW TABLES
|
56
56
|
Query Load (32.0ms) SELECT `queries`.* FROM `queries`
|
@@ -68,7 +68,7 @@ describe RequestLogAnalyzer::FileFormat::Rails do
|
|
68
68
|
|
69
69
|
it "should parse an unroutable request correctly" do
|
70
70
|
log = <<-EOLOG
|
71
|
-
Started GET "/404" for 127.0.0.1 at
|
71
|
+
Started GET "/404" for 127.0.0.1 at Fri Mar 19 06:40:57 -0700 2010
|
72
72
|
|
73
73
|
ActionController::RoutingError (No route matches "/404"):
|
74
74
|
|
@@ -87,7 +87,7 @@ describe RequestLogAnalyzer::FileFormat::Rails do
|
|
87
87
|
|
88
88
|
it "should parse a failing request correctly" do
|
89
89
|
log = <<-EOLOG
|
90
|
-
Started POST "/queries/397638749/execute.csv" for 127.0.0.1 at
|
90
|
+
Started POST "/queries/397638749/execute.csv" for 127.0.0.1 at Mon Mar 01 18:44:33 -0800 2010
|
91
91
|
Processing by QueriesController#execute as CSV
|
92
92
|
Parameters: {"commit"=>"Run query", "authenticity_token"=>"pz9WcxkcrlG/43eg6BgSAnJL7yIsaffuHbYxPHUsUzQ=", "id"=>"397638749"}
|
93
93
|
|
data/spec/unit/mailer_spec.rb
CHANGED
data/spec/unit/request_spec.rb
CHANGED
data/tasks/github-gem.rake
CHANGED
@@ -134,8 +134,8 @@ module GithubGem
|
|
134
134
|
desc "Release the next version of the gem, by incrementing the last version segment by 1"
|
135
135
|
task(:next => [:next_version] + release_tasks) { release_task }
|
136
136
|
|
137
|
-
desc "Release the next version of the gem, using a
|
138
|
-
task(:
|
137
|
+
desc "Release the next version of the gem, using a patch increment (0.0.1)"
|
138
|
+
task(:patch => [:next_patch_version] + release_tasks) { release_task }
|
139
139
|
|
140
140
|
desc "Release the next version of the gem, using a minor increment (0.1.0)"
|
141
141
|
task(:minor => [:next_minor_version] + release_tasks) { release_task }
|
@@ -152,7 +152,7 @@ module GithubGem
|
|
152
152
|
task(:commit_modified_files) { commit_modified_files_task }
|
153
153
|
|
154
154
|
task(:next_version) { next_version_task }
|
155
|
-
task(:
|
155
|
+
task(:next_patch_version) { next_version_task(:patch) }
|
156
156
|
task(:next_minor_version) { next_version_task(:minor) }
|
157
157
|
task(:next_major_version) { next_version_task(:major) }
|
158
158
|
|
@@ -187,7 +187,7 @@ module GithubGem
|
|
187
187
|
next_version = newest_version.segments
|
188
188
|
increment_index = case increment
|
189
189
|
when :micro then 3
|
190
|
-
when :
|
190
|
+
when :patch then 2
|
191
191
|
when :minor then 1
|
192
192
|
when :major then 0
|
193
193
|
else next_version.length - 1
|
@@ -217,7 +217,7 @@ module GithubGem
|
|
217
217
|
|
218
218
|
def check_version_task
|
219
219
|
raise "#{ENV['VERSION']} is not a valid version number!" if ENV['VERSION'] && !Gem::Version.correct?(ENV['VERSION'])
|
220
|
-
proposed_version = Gem::Version.new(ENV['VERSION'] || gemspec.version)
|
220
|
+
proposed_version = Gem::Version.new(ENV['VERSION'].dup || gemspec.version)
|
221
221
|
raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version >= proposed_version
|
222
222
|
end
|
223
223
|
|
@@ -23,4 +23,106 @@ namespace :rla do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
# Split a single logfile into multiple multiple logfiles based on PID information
|
27
|
+
# provided in the log lines.
|
28
|
+
#
|
29
|
+
# Usage:
|
30
|
+
# rake rla:split_log FILE=logfile
|
31
|
+
#
|
32
|
+
# Example:
|
33
|
+
# $> rake rla:split_log FILE=log/development.log
|
34
|
+
# Splitting the Rails log file by pid using the request-log-analyzer gem.
|
35
|
+
#
|
36
|
+
# Log file was split into the following files:
|
37
|
+
# split_log_development.catchall.log # <- log lines without a pid
|
38
|
+
# split_log_development.14634.log
|
39
|
+
# split_log_development.31277.log
|
40
|
+
# split_log_development.31279.log
|
41
|
+
#
|
42
|
+
# To analyze type:
|
43
|
+
# request-log-analyzer /log/split_log_development.*.log
|
44
|
+
desc "Split a logfile into seperate files by PID"
|
45
|
+
task :split do
|
46
|
+
|
47
|
+
if ENV['FILE']
|
48
|
+
logfile = ENV['FILE']
|
49
|
+
if logfile.nil?
|
50
|
+
puts "Please provide a logfile to split"
|
51
|
+
exit(0)
|
52
|
+
elsif !File.exist?(logfile)
|
53
|
+
puts "the logfile name you provided can not be found"
|
54
|
+
exit(0)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Inline class def. Ugly, yet portable.
|
58
|
+
class LogSplitter
|
59
|
+
attr_reader :files_by_pid, :catchall, :filename, :rla_call_string
|
60
|
+
|
61
|
+
# All generated files should contain this prefix to allow for easy glob file matches
|
62
|
+
PREFIX = 'split_log_'
|
63
|
+
# The logfile pattern that captures the pid, this will be log format specific. Using Logging::Logger
|
64
|
+
# we define: Logging::Layouts::Pattern(:pattern => "%d pid:%p [%c:%l] %m [%F:%L]\n")
|
65
|
+
# PID_PATTERN = Regexp.union(/.*\[(\d+)\]\:/, /pid:(\d+)/)
|
66
|
+
PID_PATTERN = /(?-mix:.*\[(\d+)\]\:)|(?-mix:pid:(\d+))/ #
|
67
|
+
|
68
|
+
# Currently expects the filename to exist in the local directory for ease of implementation
|
69
|
+
def initialize(filename_to_split)
|
70
|
+
@filename = filename_to_split
|
71
|
+
directory = File.dirname(@filename) + '/'
|
72
|
+
directory = '' if @directory == './'
|
73
|
+
basename = @filename.split('/').last.gsub('.log', '')
|
74
|
+
@rla_call_string = "#{directory}#{PREFIX}#{basename}.*.log"
|
75
|
+
|
76
|
+
@files_by_pid =
|
77
|
+
Hash.new {|hash, pid_key| hash[pid_key] = File.open("#{directory}#{PREFIX}#{basename}.#{pid_key}.log", 'w') }
|
78
|
+
@catchall = File.open("#{directory}#{PREFIX}#{basename}.catchall.log", 'w')
|
79
|
+
end
|
80
|
+
|
81
|
+
def split
|
82
|
+
last_pid = nil
|
83
|
+
File.open(filename, 'r') do |f|
|
84
|
+
while(line = f.gets)
|
85
|
+
outfile =
|
86
|
+
if match_data = line.match(PID_PATTERN)
|
87
|
+
pid = match_data.captures[0]
|
88
|
+
last_pid = pid
|
89
|
+
files_by_pid[pid]
|
90
|
+
elsif last_pid
|
91
|
+
# handle cases where a single log call results in multiple lines of output.
|
92
|
+
files_by_pid[last_pid]
|
93
|
+
else
|
94
|
+
catchall
|
95
|
+
end
|
96
|
+
outfile.puts line
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def close_files
|
102
|
+
@files_by_pid.values.map(&:close)
|
103
|
+
@catchall.close
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# End inline class def
|
107
|
+
|
108
|
+
puts "Splitting the Rails log file by pid using the request-log-analyzer gem."
|
109
|
+
|
110
|
+
log_splitter = LogSplitter.new(logfile)
|
111
|
+
log_splitter.split
|
112
|
+
log_splitter.close_files
|
113
|
+
|
114
|
+
puts ''
|
115
|
+
puts ''
|
116
|
+
puts 'Log file was split into the following files:'
|
117
|
+
puts ' ' + log_splitter.catchall.path
|
118
|
+
log_splitter.files_by_pid.values.each {|f| puts ' ' + f.path }
|
119
|
+
puts ''
|
120
|
+
puts 'To analyze type:'
|
121
|
+
puts " request-log-analyzer #{log_splitter.rla_call_string}"
|
122
|
+
puts ''
|
123
|
+
puts ''
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
26
128
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 8
|
8
|
-
-
|
9
|
-
version: 1.8.
|
8
|
+
- 1
|
9
|
+
version: 1.8.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Willem van Bergen
|
@@ -15,37 +15,81 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-08-31 00:00:00 -07:00
|
19
19
|
default_executable: request-log-analyzer
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: rake
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
segments:
|
29
|
-
-
|
30
|
-
|
31
|
-
- 4
|
32
|
-
version: 1.2.4
|
29
|
+
- 0
|
30
|
+
version: "0"
|
33
31
|
type: :development
|
34
32
|
version_requirements: *id001
|
35
33
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
34
|
+
name: rspec
|
37
35
|
prerelease: false
|
38
36
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
37
|
requirements:
|
40
38
|
- - ">="
|
41
39
|
- !ruby/object:Gem::Version
|
42
40
|
segments:
|
43
|
-
- 1
|
44
|
-
- 1
|
45
41
|
- 0
|
46
|
-
version:
|
42
|
+
version: "0"
|
47
43
|
type: :development
|
48
44
|
version_requirements: *id002
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: activerecord
|
47
|
+
prerelease: false
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
type: :development
|
56
|
+
version_requirements: *id003
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: sqlite3-ruby
|
59
|
+
prerelease: false
|
60
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id004
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: git
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
version_requirements: *id005
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: gemcutter
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id006
|
49
93
|
description: " Request log analyzer's purpose is to find out how your web application is being used, how it performs and to\n focus your optimization efforts. This tool will parse all requests in the application's log file and aggregate the \n information. Once it is finished parsing the log file(s), it will show the requests that take op most server time \n using various metrics. It can also insert all parsed request information into a database so you can roll your own\n analysis. It supports Rails-, Merb- and Rack-based applications logs, Apache and Amazon S3 access logs and MySQL \n slow query logs out of the box, but file formats of other applications can easily be supported by supplying an \n easy to write log file format definition.\n"
|
50
94
|
email:
|
51
95
|
- willem@railsdoctors.com
|