request-log-analyzer 1.3.5 → 1.4.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/LICENSE +3 -3
- data/README.rdoc +1 -1
- data/bin/request-log-analyzer +17 -14
- data/lib/cli/command_line_arguments.rb +51 -51
- data/lib/cli/database_console.rb +3 -3
- data/lib/cli/database_console_init.rb +4 -3
- data/lib/cli/progressbar.rb +10 -10
- data/lib/cli/tools.rb +3 -3
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +13 -14
- data/lib/request_log_analyzer/aggregator/echo.rb +14 -9
- data/lib/request_log_analyzer/aggregator/summarizer.rb +26 -26
- data/lib/request_log_analyzer/aggregator.rb +11 -15
- data/lib/request_log_analyzer/controller.rb +162 -89
- data/lib/request_log_analyzer/database/base.rb +21 -21
- data/lib/request_log_analyzer/database/connection.rb +3 -3
- data/lib/request_log_analyzer/database/request.rb +22 -0
- data/lib/request_log_analyzer/database/source.rb +13 -0
- data/lib/request_log_analyzer/database/warning.rb +14 -0
- data/lib/request_log_analyzer/database.rb +28 -103
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +17 -18
- data/lib/request_log_analyzer/file_format/apache.rb +26 -27
- data/lib/request_log_analyzer/file_format/merb.rb +30 -13
- data/lib/request_log_analyzer/file_format/rack.rb +4 -4
- data/lib/request_log_analyzer/file_format/rails.rb +143 -73
- data/lib/request_log_analyzer/file_format/rails_development.rb +4 -49
- data/lib/request_log_analyzer/file_format.rb +16 -28
- data/lib/request_log_analyzer/filter/anonymize.rb +8 -7
- data/lib/request_log_analyzer/filter/field.rb +9 -9
- data/lib/request_log_analyzer/filter/timespan.rb +12 -10
- data/lib/request_log_analyzer/filter.rb +12 -16
- data/lib/request_log_analyzer/line_definition.rb +15 -14
- data/lib/request_log_analyzer/log_processor.rb +27 -29
- data/lib/request_log_analyzer/mailer.rb +15 -9
- data/lib/request_log_analyzer/output/fixed_width.rb +40 -41
- data/lib/request_log_analyzer/output/html.rb +20 -20
- data/lib/request_log_analyzer/output.rb +53 -12
- data/lib/request_log_analyzer/request.rb +75 -68
- data/lib/request_log_analyzer/source/database_loader.rb +10 -14
- data/lib/request_log_analyzer/source/log_parser.rb +57 -50
- data/lib/request_log_analyzer/source.rb +10 -12
- data/lib/request_log_analyzer/tracker/duration.rb +39 -132
- data/lib/request_log_analyzer/tracker/frequency.rb +31 -32
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +21 -21
- data/lib/request_log_analyzer/tracker/timespan.rb +17 -17
- data/lib/request_log_analyzer/tracker/traffic.rb +36 -116
- data/lib/request_log_analyzer/tracker.rb +139 -32
- data/lib/request_log_analyzer.rb +4 -4
- data/request-log-analyzer.gemspec +19 -15
- data/spec/database.yml +6 -0
- data/spec/fixtures/rails_22.log +1 -1
- data/spec/integration/command_line_usage_spec.rb +7 -1
- data/spec/lib/helpers.rb +7 -7
- data/spec/lib/macros.rb +3 -3
- data/spec/lib/matchers.rb +41 -27
- data/spec/lib/mocks.rb +15 -14
- data/spec/lib/testing_format.rb +9 -9
- data/spec/spec_helper.rb +6 -6
- data/spec/unit/aggregator/database_inserter_spec.rb +16 -16
- data/spec/unit/aggregator/summarizer_spec.rb +4 -4
- data/spec/unit/controller/controller_spec.rb +2 -2
- data/spec/unit/controller/log_processor_spec.rb +1 -1
- data/spec/unit/database/base_class_spec.rb +26 -33
- data/spec/unit/database/connection_spec.rb +3 -3
- data/spec/unit/database/database_spec.rb +33 -38
- data/spec/unit/file_format/amazon_s3_format_spec.rb +5 -5
- data/spec/unit/file_format/apache_format_spec.rb +13 -13
- data/spec/unit/file_format/file_format_api_spec.rb +13 -13
- data/spec/unit/file_format/line_definition_spec.rb +24 -17
- data/spec/unit/file_format/merb_format_spec.rb +41 -45
- data/spec/unit/file_format/rails_format_spec.rb +157 -117
- data/spec/unit/filter/anonymize_filter_spec.rb +2 -2
- data/spec/unit/filter/field_filter_spec.rb +13 -13
- data/spec/unit/filter/filter_spec.rb +1 -1
- data/spec/unit/filter/timespan_filter_spec.rb +15 -15
- data/spec/unit/mailer_spec.rb +30 -0
- data/spec/unit/{source/request_spec.rb → request_spec.rb} +30 -30
- data/spec/unit/source/log_parser_spec.rb +27 -27
- data/spec/unit/tracker/duration_tracker_spec.rb +115 -78
- data/spec/unit/tracker/frequency_tracker_spec.rb +74 -63
- data/spec/unit/tracker/hourly_spread_spec.rb +28 -20
- data/spec/unit/tracker/timespan_tracker_spec.rb +25 -13
- data/spec/unit/tracker/tracker_api_spec.rb +117 -42
- data/spec/unit/tracker/traffic_tracker_spec.rb +81 -79
- data/tasks/github-gem.rake +125 -75
- data/tasks/request_log_analyzer.rake +2 -2
- metadata +13 -8
|
@@ -1,103 +1,173 @@
|
|
|
1
1
|
module RequestLogAnalyzer::FileFormat
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
# Default FileFormat class for Rails logs.
|
|
4
|
+
#
|
|
5
|
+
# Instances will be created dynamically based on the lines you want it to parse. You can
|
|
6
|
+
# specify what lines should be included in the parser by providing a list to the create
|
|
7
|
+
# method as first argument.
|
|
3
8
|
class Rails < Base
|
|
4
|
-
|
|
5
|
-
# Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
|
|
6
|
-
line_definition :processing do |line|
|
|
7
|
-
line.header = true # this line is the first log line for a request
|
|
8
|
-
line.teaser = /Processing /
|
|
9
|
-
line.regexp = /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/
|
|
10
|
-
line.captures << { :name => :controller, :type => :string } \
|
|
11
|
-
<< { :name => :action, :type => :string } \
|
|
12
|
-
<< { :name => :format, :type => :format } \
|
|
13
|
-
<< { :name => :ip, :type => :string } \
|
|
14
|
-
<< { :name => :timestamp, :type => :timestamp } \
|
|
15
|
-
<< { :name => :method, :type => :string }
|
|
16
|
-
end
|
|
17
9
|
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
# Creates a Rails FileFormat instance.
|
|
11
|
+
#
|
|
12
|
+
# The lines that will be parsed can be defined by the argument to this function,
|
|
13
|
+
# which should be an array of line names, or a list of line names as comma separated
|
|
14
|
+
# string. The resulting report depends on the lines that will be parsed. You can
|
|
15
|
+
# also provide s string that describes a common set of lines, like "production",
|
|
16
|
+
# "development" or "production".
|
|
17
|
+
def self.create(lines = 'production')
|
|
18
|
+
definitions_hash = line_definer.line_definitions.clone
|
|
19
|
+
|
|
20
|
+
lines = lines.to_s.split(',') if lines.kind_of?(String)
|
|
21
|
+
lines = [lines.to_s] if lines.kind_of?(Symbol)
|
|
22
|
+
|
|
23
|
+
lines.each do |line|
|
|
24
|
+
line = line.to_sym
|
|
25
|
+
if LINE_COLLECTIONS.has_key?(line)
|
|
26
|
+
LINE_COLLECTIONS[line].each { |l| definitions_hash[l] = LINE_DEFINITIONS[l] }
|
|
27
|
+
elsif LINE_DEFINITIONS.has_key?(line)
|
|
28
|
+
definitions_hash[line] = LINE_DEFINITIONS[line]
|
|
29
|
+
else
|
|
30
|
+
raise "Unrecognized Rails log line name: #{line.inspect}!"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
return self.new(definitions_hash, report_trackers(definitions_hash))
|
|
32
35
|
end
|
|
36
|
+
|
|
37
|
+
# Creates trackers based on the specified line definitions.
|
|
38
|
+
#
|
|
39
|
+
# The more lines that will be parsed, the more information will appear in the report.
|
|
40
|
+
def self.report_trackers(lines)
|
|
41
|
+
analyze = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
|
|
42
|
+
|
|
43
|
+
analyze.timespan
|
|
44
|
+
analyze.hourly_spread
|
|
45
|
+
|
|
46
|
+
analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Most requested'
|
|
47
|
+
analyze.frequency :method, :title => 'HTTP methods'
|
|
48
|
+
analyze.frequency :status, :title => 'HTTP statuses returned'
|
|
49
|
+
|
|
50
|
+
if lines.has_key?(:cache_hit)
|
|
51
|
+
analyze.frequency(:category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' },
|
|
52
|
+
:title => 'Rails action cache hits')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
|
|
56
|
+
analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
|
|
57
|
+
analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
|
|
58
|
+
|
|
59
|
+
analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
|
|
60
|
+
:if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
|
|
61
|
+
|
|
62
|
+
if lines.has_key?(:failure)
|
|
63
|
+
analyze.frequency :error, :title => 'Failed requests', :line_type => :failure
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if lines.has_key?(:rendered)
|
|
67
|
+
analyze.duration :render_duration, :category => :render_file, :multiple_per_request => true, :title => 'Partial rendering duration'
|
|
68
|
+
end
|
|
33
69
|
|
|
70
|
+
if lines.has_key?(:query_executed)
|
|
71
|
+
analyze.duration :query_duration, :category => :query_sql, :multiple_per_request => true, :title => 'Query duration'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return analyze.trackers + report_definer.trackers
|
|
75
|
+
end
|
|
34
76
|
|
|
35
77
|
# Rails < 2.1 completed line example
|
|
36
78
|
# Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
|
|
37
79
|
RAILS_21_COMPLETED = /Completed in (\d+\.\d{5}) \(\d+ reqs\/sec\) (?:\| Rendering: (\d+\.\d{5}) \(\d+\%\) )?(?:\| DB: (\d+\.\d{5}) \(\d+\%\) )?\| (\d\d\d).+\[(http.+)\]/
|
|
38
80
|
|
|
39
81
|
# Rails > 2.1 completed line example
|
|
40
|
-
# Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
|
|
82
|
+
# Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
|
|
41
83
|
RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
|
|
42
84
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
# A hash of definitions for all common lines in Rails logs.
|
|
86
|
+
LINE_DEFINITIONS = {
|
|
87
|
+
:processing => RequestLogAnalyzer::LineDefinition.new(:processing, :header => true,
|
|
88
|
+
:teaser => /Processing /,
|
|
89
|
+
:regexp => /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/,
|
|
90
|
+
:captures => [{ :name => :controller, :type => :string },
|
|
91
|
+
{ :name => :action, :type => :string },
|
|
92
|
+
{ :name => :format, :type => :string, :default => 'html' },
|
|
93
|
+
{ :name => :ip, :type => :string },
|
|
94
|
+
{ :name => :timestamp, :type => :timestamp },
|
|
95
|
+
{ :name => :method, :type => :string }]),
|
|
96
|
+
|
|
97
|
+
:completed => RequestLogAnalyzer::LineDefinition.new(:completed, :footer => true,
|
|
98
|
+
:teaser => /Completed in /,
|
|
99
|
+
:regexp => Regexp.union(RAILS_21_COMPLETED, RAILS_22_COMPLETED),
|
|
100
|
+
:captures => [{ :name => :duration, :type => :duration, :unit => :sec }, # First old variant capture
|
|
101
|
+
{ :name => :view, :type => :duration, :unit => :sec },
|
|
102
|
+
{ :name => :db, :type => :duration, :unit => :sec },
|
|
103
|
+
{ :name => :status, :type => :integer },
|
|
104
|
+
{ :name => :url, :type => :string }, # Last old variant capture
|
|
105
|
+
{ :name => :duration, :type => :duration, :unit => :msec }, # First new variant capture
|
|
106
|
+
{ :name => :view, :type => :duration, :unit => :msec },
|
|
107
|
+
{ :name => :db, :type => :duration, :unit => :msec },
|
|
108
|
+
{ :name => :status, :type => :integer },
|
|
109
|
+
{ :name => :url, :type => :string }]), # Last new variant capture
|
|
110
|
+
|
|
111
|
+
:failure => RequestLogAnalyzer::LineDefinition.new(:failure, :footer => true,
|
|
112
|
+
:teaser => /((?:[A-Z]\w*[a-z]\w+\:\:)*[A-Z]\w*[a-z]\w+) \((.*)\)(?: on line #(\d+) of (.+))?\:/,
|
|
113
|
+
:regexp => /((?:[A-Z]\w*[a-z]\w+\:\:)*[A-Z]\w*[a-z]\w+) \((.*)\)(?: on line #(\d+) of (.+))?\:\s*$/,
|
|
114
|
+
:captures => [{ :name => :error, :type => :string },
|
|
115
|
+
{ :name => :message, :type => :string },
|
|
116
|
+
{ :name => :line, :type => :integer },
|
|
117
|
+
{ :name => :file, :type => :string }]),
|
|
118
|
+
|
|
119
|
+
:cache_hit => RequestLogAnalyzer::LineDefinition.new(:cache_hit,
|
|
120
|
+
:regexp => /Filter chain halted as \[\#<ActionController::Caching::Actions::ActionCacheFilter/),
|
|
121
|
+
|
|
122
|
+
:parameters => RequestLogAnalyzer::LineDefinition.new(:parameters,
|
|
123
|
+
:teaser => / Parameters:/,
|
|
124
|
+
:regexp => / Parameters:\s+(\{.*\})/,
|
|
125
|
+
:captures => [{ :name => :params, :type => :eval }]),
|
|
126
|
+
|
|
127
|
+
:rendered => RequestLogAnalyzer::LineDefinition.new(:rendered,
|
|
128
|
+
:teaser => /Rendered /,
|
|
129
|
+
:regexp => /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/,
|
|
130
|
+
:captures => [{ :name => :render_file, :type => :string },
|
|
131
|
+
{ :name => :render_duration, :type => :duration, :unit => :msec }]),
|
|
132
|
+
|
|
133
|
+
:query_executed => RequestLogAnalyzer::LineDefinition.new(:query_executed,
|
|
134
|
+
:regexp => /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?([^\e]+) ?(?:\e\[0m)?/,
|
|
135
|
+
:captures => [{ :name => :query_class, :type => :string },
|
|
136
|
+
{ :name => :query_duration, :type => :duration, :unit => :msec },
|
|
137
|
+
{ :name => :query_sql, :type => :sql }]),
|
|
138
|
+
|
|
139
|
+
:query_cached => RequestLogAnalyzer::LineDefinition.new(:query_cached,
|
|
140
|
+
:regexp => /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?([^\e]+) ?(?:\e\[0m)?/,
|
|
141
|
+
:captures => [{ :name => :cached_duration, :type => :duration, :unit => :msec },
|
|
142
|
+
{ :name => :cached_sql, :type => :sql }])
|
|
143
|
+
}
|
|
64
144
|
|
|
145
|
+
# Definitions of common combinations of lines that can be parsed
|
|
146
|
+
LINE_COLLECTIONS = {
|
|
147
|
+
:minimal => [:processing, :completed],
|
|
148
|
+
:production => [:processing, :completed, :failure, :cache_hit],
|
|
149
|
+
:development => [:processing, :completed, :failure, :rendered, :query_executed, :query_cached],
|
|
150
|
+
:all => LINE_DEFINITIONS.keys
|
|
151
|
+
}
|
|
65
152
|
|
|
66
153
|
|
|
154
|
+
# Simple function to categorize Rails requests using controller/actions/format and method.
|
|
67
155
|
REQUEST_CATEGORIZER = Proc.new do |request|
|
|
68
156
|
"#{request[:controller]}##{request[:action]}.#{request[:format]} [#{request[:method]}]"
|
|
69
157
|
end
|
|
70
158
|
|
|
71
|
-
report do |analyze|
|
|
72
|
-
analyze.timespan :line_type => :processing
|
|
73
|
-
analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
|
|
74
|
-
analyze.frequency :method, :title => 'HTTP methods'
|
|
75
|
-
analyze.frequency :status, :title => 'HTTP statuses returned'
|
|
76
|
-
analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
|
|
77
|
-
|
|
78
|
-
analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
|
|
79
|
-
analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
|
|
80
|
-
analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
|
|
81
|
-
|
|
82
|
-
analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
|
|
83
|
-
:if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
|
|
84
|
-
|
|
85
|
-
analyze.hourly_spread :line_type => :processing
|
|
86
|
-
analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
|
|
87
|
-
end
|
|
88
|
-
|
|
89
159
|
# Define a custom Request class for the Rails file format to speed up timestamp handling
|
|
90
160
|
# and to ensure that a format is always set.
|
|
91
161
|
class Request < RequestLogAnalyzer::Request
|
|
92
162
|
|
|
93
163
|
# Do not use DateTime.parse
|
|
94
164
|
def convert_timestamp(value, definition)
|
|
95
|
-
value.gsub(/[^0-9]/, '')[0...14].to_i
|
|
165
|
+
value.gsub(/[^0-9]/, '')[0...14].to_i
|
|
96
166
|
end
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
def
|
|
100
|
-
|
|
167
|
+
|
|
168
|
+
# Sanitizes SQL queries so that they can be grouped
|
|
169
|
+
def convert_sql(sql, definition)
|
|
170
|
+
sql.gsub(/\b\d+\b/, ':int').gsub(/`([^`]+)`/, '\1').gsub(/'[^']*'/, ':string').rstrip
|
|
101
171
|
end
|
|
102
172
|
end
|
|
103
173
|
|
|
@@ -1,57 +1,12 @@
|
|
|
1
1
|
module RequestLogAnalyzer::FileFormat
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# The RailsDevelopment FileFormat is an extention to the default Rails file format. It includes
|
|
4
4
|
# all lines of the normal Rails file format, but parses SQL queries and partial rendering lines
|
|
5
5
|
# as well.
|
|
6
6
|
class RailsDevelopment < Rails
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
line.teaser = /Parameters/
|
|
11
|
-
line.regexp = /\s+Parameters:\s+(\{.*\})/
|
|
12
|
-
line.captures << { :name => :params, :type => :eval }
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Rendered layouts/_footer (2.9ms)
|
|
16
|
-
line_definition :rendered do |line|
|
|
17
|
-
line.regexp = /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/
|
|
18
|
-
line.captures << { :name => :render_file, :type => :string } \
|
|
19
|
-
<< { :name => :render_duration, :type => :duration, :unit => :msec }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# [4;36;1mUser Load (0.4ms)[0m [0;1mSELECT * FROM `users` WHERE (`users`.`id` = 18205844) [0m
|
|
23
|
-
line_definition :query_executed do |line|
|
|
24
|
-
line.regexp = /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?([^\e]+) ?(?:\e\[0m)?/
|
|
25
|
-
line.captures << { :name => :query_class, :type => :string } \
|
|
26
|
-
<< { :name => :query_duration, :type => :duration, :unit => :msec } \
|
|
27
|
-
<< { :name => :query_sql, :type => :sql }
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# [4;35;1mCACHE (0.0ms)[0m [0mSELECT * FROM `users` WHERE (`users`.`id` = 0) [0m
|
|
31
|
-
line_definition :query_cached do |line|
|
|
32
|
-
line.regexp = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?([^\e]+) ?(?:\e\[0m)?/
|
|
33
|
-
line.captures << { :name => :cached_duration, :type => :duration, :unit => :msec } \
|
|
34
|
-
<< { :name => :cached_sql, :type => :sql }
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Define the reporting for the additional parsed lines
|
|
38
|
-
report(:append) do |analyze|
|
|
39
|
-
|
|
40
|
-
analyze.duration :render_duration, :category => :render_file, :multiple_per_request => true,
|
|
41
|
-
:amount => 20, :title => 'Partial rendering duration'
|
|
42
|
-
|
|
43
|
-
analyze.duration :query_duration, :category => :query_sql, :multiple_per_request => true,
|
|
44
|
-
:amount => 20, :title => 'Query duration'
|
|
45
|
-
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Add a converter method for the SQL fields the the Rails request class
|
|
49
|
-
class Request < Rails::Request
|
|
50
|
-
|
|
51
|
-
# Sanitizes SQL queries so that they can be grouped
|
|
52
|
-
def convert_sql(sql, definition)
|
|
53
|
-
sql.gsub(/\b\d+\b/, ':int').gsub(/`([^`]+)`/, '\1').gsub(/'[^']*'/, ':string').rstrip
|
|
54
|
-
end
|
|
7
|
+
def self.create
|
|
8
|
+
puts 'DEPRECATED: use --rails-format development instead!'
|
|
9
|
+
super('development')
|
|
55
10
|
end
|
|
56
11
|
end
|
|
57
12
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module RequestLogAnalyzer::FileFormat
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
def self.const_missing(const)
|
|
4
4
|
RequestLogAnalyzer::load_default_class_file(self, const)
|
|
5
|
-
end
|
|
6
|
-
|
|
5
|
+
end
|
|
6
|
+
|
|
7
7
|
# Loads a FileFormat::Base subclass instance.
|
|
8
8
|
# You can provide:
|
|
9
9
|
# * A FileFormat instance (which will return itself)
|
|
@@ -34,58 +34,46 @@ module RequestLogAnalyzer::FileFormat
|
|
|
34
34
|
|
|
35
35
|
else
|
|
36
36
|
# load a provided file format
|
|
37
|
-
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
|
|
37
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
|
|
38
38
|
end
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
# check the returned klass to see if it can be used
|
|
41
41
|
raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
|
|
42
42
|
raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
@current_file_format = klass.create(*args) # return an instance of the class
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Makes classes aware of a file format by registering the file_format variable
|
|
48
|
-
module Awareness
|
|
49
|
-
|
|
50
|
-
def self.included(base)
|
|
51
|
-
base.send(:attr_reader, :file_format)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def register_file_format(format)
|
|
55
|
-
@file_format = format
|
|
56
|
-
end
|
|
57
|
-
end
|
|
45
|
+
end
|
|
58
46
|
|
|
59
|
-
# Base class for all log file format definitions. This class provides functions for subclasses to
|
|
47
|
+
# Base class for all log file format definitions. This class provides functions for subclasses to
|
|
60
48
|
# define their LineDefinitions and to define a summary report.
|
|
61
49
|
#
|
|
62
50
|
# A subclass of this class is instantiated when request-log-analyzer is started and this instance
|
|
63
51
|
# is shared with all components of the application so they can act on the specifics of the format
|
|
64
52
|
class Base
|
|
65
|
-
|
|
53
|
+
|
|
66
54
|
attr_reader :line_definitions, :report_trackers
|
|
67
|
-
|
|
55
|
+
|
|
68
56
|
####################################################################################
|
|
69
57
|
# CLASS METHODS for format definition
|
|
70
58
|
####################################################################################
|
|
71
|
-
|
|
59
|
+
|
|
72
60
|
# Registers the line definer instance for a subclass.
|
|
73
61
|
def self.inherited(subclass)
|
|
74
62
|
if subclass.superclass == RequestLogAnalyzer::FileFormat::Base
|
|
75
63
|
|
|
76
64
|
# Create aline and report definer for this class
|
|
77
|
-
subclass.class_eval do
|
|
65
|
+
subclass.class_eval do
|
|
78
66
|
instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
|
|
79
67
|
instance_variable_set(:@report_definer, RequestLogAnalyzer::Aggregator::Summarizer::Definer.new)
|
|
80
68
|
class << self; attr_accessor :line_definer, :report_definer; end
|
|
81
|
-
end
|
|
69
|
+
end
|
|
82
70
|
|
|
83
71
|
# Create a custom Request class for this file format
|
|
84
72
|
subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request)) unless subclass.const_defined?('Request')
|
|
85
73
|
else
|
|
86
74
|
|
|
87
75
|
# Copy the line and report definer from the parent class.
|
|
88
|
-
subclass.class_eval do
|
|
76
|
+
subclass.class_eval do
|
|
89
77
|
instance_variable_set(:@line_definer, superclass.line_definer.clone)
|
|
90
78
|
instance_variable_set(:@report_definer, superclass.report_definer.clone)
|
|
91
79
|
class << self; attr_accessor :line_definer, :report_definer; end
|
|
@@ -125,7 +113,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
125
113
|
return self.new(line_definer.line_definitions, report_definer.trackers)
|
|
126
114
|
end
|
|
127
115
|
|
|
128
|
-
def initialize(line_definitions =
|
|
116
|
+
def initialize(line_definitions = {}, report_trackers = [])
|
|
129
117
|
@line_definitions, @report_trackers = line_definitions, report_trackers
|
|
130
118
|
end
|
|
131
119
|
|
|
@@ -165,7 +153,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
165
153
|
match = definition.matches(line, &warning_handler)
|
|
166
154
|
return match if match
|
|
167
155
|
end
|
|
168
|
-
|
|
156
|
+
|
|
169
157
|
return nil
|
|
170
158
|
end
|
|
171
159
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Filter to anonymize parsed values
|
|
4
4
|
# Options
|
|
5
5
|
# * <tt>:mode</tt> :reject or :accept.
|
|
6
6
|
# * <tt>:field</tt> Specific field to accept or reject.
|
|
7
7
|
# * <tt>:value</tt> Value that the field should match to be accepted or rejected.
|
|
8
8
|
class Anonymize < Base
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
def generate_random_ip
|
|
11
11
|
"#{rand(256)}.#{rand(256)}.#{rand(256)}.#{rand(256)}"
|
|
12
12
|
end
|
|
@@ -14,12 +14,13 @@ module RequestLogAnalyzer::Filter
|
|
|
14
14
|
def anonymize_url(value)
|
|
15
15
|
return value.sub(/^https?\:\/\/[A-Za-z0-9\.-]+\//, "http://example.com/")
|
|
16
16
|
end
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
def fuzz(value)
|
|
19
19
|
value * ((75 + rand(50)) / 100.0)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def filter(request)
|
|
22
|
+
def filter(request)
|
|
23
|
+
# TODO: request.attributes is bad practice
|
|
23
24
|
request.attributes.each do |key, value|
|
|
24
25
|
if key == :ip
|
|
25
26
|
request.attributes[key] = generate_random_ip
|
|
@@ -30,9 +31,9 @@ module RequestLogAnalyzer::Filter
|
|
|
30
31
|
request.attributes[key] = fuzz(value)
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
return request
|
|
35
|
-
end
|
|
36
|
+
end
|
|
36
37
|
end
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
end
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Filter to select or reject a specific field
|
|
4
4
|
# Options
|
|
5
5
|
# * <tt>:mode</tt> :reject or :accept.
|
|
6
6
|
# * <tt>:field</tt> Specific field to accept or reject.
|
|
7
7
|
# * <tt>:value</tt> Value that the field should match to be accepted or rejected.
|
|
8
8
|
class Field < Base
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
attr_reader :field, :value, :mode
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
def initialize(file_format, options = {})
|
|
13
13
|
super(file_format, options)
|
|
14
14
|
setup_filter
|
|
15
15
|
end
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
# Setup mode, field and value.
|
|
18
18
|
def setup_filter
|
|
19
19
|
@mode = (@options[:mode] || :accept).to_sym
|
|
20
20
|
@field = @options[:field].to_sym
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Convert the timestamp to the correct formats for quick timestamp comparisons
|
|
23
23
|
if @options[:value].kind_of?(String) && @options[:value][0, 1] == '/' && @options[:value][-1, 1] == '/'
|
|
24
24
|
@value = Regexp.new(@options[:value][1..-2])
|
|
@@ -26,17 +26,17 @@ module RequestLogAnalyzer::Filter
|
|
|
26
26
|
@value = @options[:value] # TODO: convert value?
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
# Keep request if @mode == :select and request has the field and value.
|
|
31
31
|
# Drop request if @mode == :reject and request has the field and value.
|
|
32
32
|
# Returns nil otherwise.
|
|
33
33
|
# <tt>request</tt> Request Object
|
|
34
34
|
def filter(request)
|
|
35
|
-
found_field = request.every(@field).any? { |value| @value === value.to_s }
|
|
35
|
+
found_field = request.every(@field).any? { |value| @value === value.to_s }
|
|
36
36
|
return nil if !found_field && @mode == :select
|
|
37
37
|
return nil if found_field && @mode == :reject
|
|
38
38
|
return request
|
|
39
|
-
end
|
|
39
|
+
end
|
|
40
40
|
end
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
end
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Reject all requests not in given timespan
|
|
4
4
|
# Options
|
|
5
5
|
# * <tt>:after</tt> Only keep requests after this DateTime.
|
|
6
6
|
# * <tt>:before</tt> Only keep requests before this DateTime.
|
|
7
7
|
class Timespan < Base
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
attr_reader :before, :after
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
def initialize(file_format, options = {})
|
|
12
|
+
@after = nil
|
|
13
|
+
@before = nil
|
|
12
14
|
super(file_format, options)
|
|
13
15
|
setup_filter
|
|
14
16
|
end
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
|
|
17
19
|
# Convert the timestamp to the correct formats for quick timestamp comparisons.
|
|
18
20
|
# These are stored in the before and after attr_reader fields.
|
|
19
21
|
def setup_filter
|
|
20
|
-
@after = @options[:after].strftime('%Y%m%d%H%M%S').to_i if options[:after]
|
|
22
|
+
@after = @options[:after].strftime('%Y%m%d%H%M%S').to_i if options[:after]
|
|
21
23
|
@before = @options[:before].strftime('%Y%m%d%H%M%S').to_i if options[:before]
|
|
22
24
|
end
|
|
23
|
-
|
|
25
|
+
|
|
24
26
|
# Returns request if:
|
|
25
27
|
# * @after <= request.timestamp <= @before
|
|
26
28
|
# * @after <= request.timestamp
|
|
@@ -32,12 +34,12 @@ module RequestLogAnalyzer::Filter
|
|
|
32
34
|
return request
|
|
33
35
|
elsif @after && @before.nil? && @after <= request.timestamp
|
|
34
36
|
return request
|
|
35
|
-
elsif @before && @after.nil? && request.timestamp <= @before
|
|
37
|
+
elsif @before && @after.nil? && request.timestamp <= @before
|
|
36
38
|
return request
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
return nil
|
|
40
|
-
end
|
|
42
|
+
end
|
|
41
43
|
end
|
|
42
|
-
|
|
44
|
+
|
|
43
45
|
end
|
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Filter class loader using const_missing
|
|
4
4
|
# This function will automatically load the class file based on the name of the class
|
|
5
5
|
def self.const_missing(const)
|
|
6
6
|
RequestLogAnalyzer::load_default_class_file(self, const)
|
|
7
7
|
end
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
# Base filter class used to filter input requests.
|
|
10
|
-
# All filters should interit from this base.
|
|
10
|
+
# All filters should interit from this base.
|
|
11
11
|
class Base
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
attr_reader :log_parser
|
|
16
|
-
attr_reader :options
|
|
17
|
-
|
|
12
|
+
|
|
13
|
+
attr_reader :file_format, :options
|
|
14
|
+
|
|
18
15
|
# Initializer
|
|
19
16
|
# <tt>format</tt> The file format
|
|
20
17
|
# <tt>options</tt> Are passed to the filters.
|
|
21
18
|
def initialize(format, options = {})
|
|
22
|
-
@
|
|
23
|
-
|
|
19
|
+
@file_format = format
|
|
20
|
+
@options = options
|
|
24
21
|
end
|
|
25
|
-
|
|
22
|
+
|
|
26
23
|
# Return the request if the request should be kept.
|
|
27
24
|
# Return nil otherwise.
|
|
28
25
|
def filter(request)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
26
|
+
request
|
|
27
|
+
end
|
|
32
28
|
end
|
|
33
|
-
|
|
29
|
+
|
|
34
30
|
end
|