wvanbergen-request-log-analyzer 1.3.5 → 1.3.6
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cli/database_console_init.rb +2 -1
- data/lib/request_log_analyzer.rb +1 -1
- data/lib/request_log_analyzer/aggregator.rb +1 -5
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +4 -5
- data/lib/request_log_analyzer/controller.rb +10 -21
- data/lib/request_log_analyzer/database.rb +16 -91
- data/lib/request_log_analyzer/database/base.rb +4 -4
- 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/file_format.rb +1 -13
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +1 -2
- data/lib/request_log_analyzer/file_format/apache.rb +8 -10
- data/lib/request_log_analyzer/file_format/merb.rb +21 -5
- data/lib/request_log_analyzer/file_format/rails.rb +8 -14
- data/lib/request_log_analyzer/filter.rb +6 -10
- data/lib/request_log_analyzer/filter/anonymize.rb +2 -1
- data/lib/request_log_analyzer/log_processor.rb +6 -8
- data/lib/request_log_analyzer/request.rb +47 -35
- data/lib/request_log_analyzer/source.rb +4 -6
- data/lib/request_log_analyzer/source/database_loader.rb +3 -7
- data/lib/request_log_analyzer/source/log_parser.rb +3 -6
- data/lib/request_log_analyzer/tracker.rb +12 -19
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +1 -2
- data/request-log-analyzer.gemspec +3 -3
- data/spec/database.yml +6 -0
- data/spec/unit/aggregator/database_inserter_spec.rb +3 -3
- data/spec/unit/database/base_class_spec.rb +9 -16
- data/spec/unit/database/database_spec.rb +9 -14
- data/spec/unit/tracker/tracker_api_spec.rb +111 -36
- metadata +7 -4
@@ -3,6 +3,7 @@ $:.unshift(File.dirname(__FILE__) + '/..')
|
|
3
3
|
|
4
4
|
$database = RequestLogAnalyzer::Database.new(ENV['RLA_DBCONSOLE_DATABASE'])
|
5
5
|
$database.load_database_schema!
|
6
|
+
$database.register_default_orm_classes!
|
6
7
|
|
7
8
|
require 'cli/tools'
|
8
9
|
|
@@ -39,4 +40,4 @@ end
|
|
39
40
|
puts "request-log-analyzer database console"
|
40
41
|
puts "-------------------------------------"
|
41
42
|
puts "The following ActiveRecord classes are available:"
|
42
|
-
puts $database.orm_classes.join(", ")
|
43
|
+
puts $database.orm_classes.map { |k| k.name.split('::').last }.join(", ")
|
data/lib/request_log_analyzer.rb
CHANGED
@@ -11,7 +11,7 @@ module RequestLogAnalyzer
|
|
11
11
|
|
12
12
|
# The current version of request-log-analyzer.
|
13
13
|
# This will be diplayed in output reports etc.
|
14
|
-
VERSION = "1.3.
|
14
|
+
VERSION = "1.3.6"
|
15
15
|
|
16
16
|
# Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
|
17
17
|
# <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
|
@@ -8,16 +8,12 @@ module RequestLogAnalyzer::Aggregator
|
|
8
8
|
# every aggregator should comply (by simply subclassing this class).
|
9
9
|
class Base
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
attr_reader :options
|
14
|
-
attr_reader :source
|
11
|
+
attr_reader :options, :source
|
15
12
|
|
16
13
|
# Intializes a new RequestLogAnalyzer::Aggregator::Base instance
|
17
14
|
# It will include the specific file format module.
|
18
15
|
def initialize(source, options = {})
|
19
16
|
@source = source
|
20
|
-
self.register_file_format(source.file_format)
|
21
17
|
@options = options
|
22
18
|
end
|
23
19
|
|
@@ -31,7 +31,7 @@ module RequestLogAnalyzer::Aggregator
|
|
31
31
|
# This will create a record in the requests table and create a record for every line that has been parsed,
|
32
32
|
# in which the captured values will be stored.
|
33
33
|
def aggregate(request)
|
34
|
-
@request_object =
|
34
|
+
@request_object = RequestLogAnalyzer::Database::Request.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
|
35
35
|
request.lines.each do |line|
|
36
36
|
class_columns = database.get_class(line[:line_type]).column_names.reject { |column| ['id', 'source_id', 'request_id'].include?(column) }
|
37
37
|
attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten]
|
@@ -45,14 +45,14 @@ module RequestLogAnalyzer::Aggregator
|
|
45
45
|
|
46
46
|
# Finalizes the aggregator by closing the connection to the database
|
47
47
|
def finalize
|
48
|
-
@request_count =
|
48
|
+
@request_count = RequestLogAnalyzer::Database::Request.count
|
49
49
|
database.disconnect
|
50
50
|
database.remove_orm_classes!
|
51
51
|
end
|
52
52
|
|
53
53
|
# Records w warining in the warnings table.
|
54
54
|
def warning(type, message, lineno)
|
55
|
-
|
55
|
+
RequestLogAnalyzer::Database::Warning.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
|
56
56
|
end
|
57
57
|
|
58
58
|
# Records source changes in the sources table
|
@@ -60,8 +60,7 @@ module RequestLogAnalyzer::Aggregator
|
|
60
60
|
if File.exist?(filename)
|
61
61
|
case change
|
62
62
|
when :started
|
63
|
-
|
64
|
-
@sources[filename] = database.source_class.create!(:filename => filename)
|
63
|
+
@sources[filename] = RequestLogAnalyzer::Database::Source.create!(:filename => filename)
|
65
64
|
when :finished
|
66
65
|
@sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
|
67
66
|
end
|
@@ -17,14 +17,7 @@ module RequestLogAnalyzer
|
|
17
17
|
# from several logrotated log files.
|
18
18
|
class Controller
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
attr_reader :aggregators
|
23
|
-
attr_reader :filters
|
24
|
-
attr_reader :log_parser
|
25
|
-
attr_reader :source
|
26
|
-
attr_reader :output
|
27
|
-
attr_reader :options
|
20
|
+
attr_reader :source, :filters, :aggregators, :output, :options
|
28
21
|
|
29
22
|
# Builds a RequestLogAnalyzer::Controller given parsed command line arguments
|
30
23
|
# <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
|
@@ -35,8 +28,8 @@ module RequestLogAnalyzer
|
|
35
28
|
# Database command line options
|
36
29
|
options[:database] = arguments[:database] if arguments[:database]
|
37
30
|
options[:reset_database] = arguments[:reset_database]
|
38
|
-
options[:debug]
|
39
|
-
options[:dump]
|
31
|
+
options[:debug] = arguments[:debug]
|
32
|
+
options[:dump] = arguments[:dump]
|
40
33
|
options[:parse_strategy] = arguments[:parse_strategy]
|
41
34
|
options[:no_progress] = arguments[:no_progress]
|
42
35
|
|
@@ -125,17 +118,13 @@ module RequestLogAnalyzer
|
|
125
118
|
@filters = []
|
126
119
|
@output = options[:output]
|
127
120
|
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
# Pass all warnings to every aggregator so they can do something useful with them.
|
132
|
-
@source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } } if @source
|
133
|
-
|
134
|
-
# Handle progress messagess
|
135
|
-
@source.progress = lambda { |message, value| handle_progress(message, value) } if @source && !options[:no_progress]
|
121
|
+
# Register the request format for this session after checking its validity
|
122
|
+
raise "Invalid file format!" unless @source.file_format.valid?
|
136
123
|
|
137
|
-
#
|
138
|
-
@source.
|
124
|
+
# Install event handlers for wrnings, progress updates and source changes
|
125
|
+
@source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } }
|
126
|
+
@source.progress = lambda { |message, value| handle_progress(message, value) } unless options[:no_progress]
|
127
|
+
@source.source_changes = lambda { |change, filename| handle_source_change(change, filename) }
|
139
128
|
end
|
140
129
|
|
141
130
|
# Progress function.
|
@@ -176,7 +165,7 @@ module RequestLogAnalyzer
|
|
176
165
|
# Adds a request filter to the controller.
|
177
166
|
def add_filter(filter, filter_options = {})
|
178
167
|
filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer::to_camelcase(filter)) if filter.kind_of?(Symbol)
|
179
|
-
@filters << filter.new(file_format, @options.merge(filter_options))
|
168
|
+
@filters << filter.new(source.file_format, @options.merge(filter_options))
|
180
169
|
end
|
181
170
|
|
182
171
|
# Push a request through the entire filterchain (@filters).
|
@@ -10,7 +10,7 @@ class RequestLogAnalyzer::Database
|
|
10
10
|
include RequestLogAnalyzer::Database::Connection
|
11
11
|
|
12
12
|
attr_accessor :file_format
|
13
|
-
attr_reader :
|
13
|
+
attr_reader :line_classes
|
14
14
|
|
15
15
|
def initialize(connection_identifier = nil)
|
16
16
|
@line_classes = []
|
@@ -24,97 +24,17 @@ class RequestLogAnalyzer::Database
|
|
24
24
|
Object.const_get("#{line_type}_line".camelize)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
# It will create the class if not previously done so. The class will
|
30
|
-
# include a create_table! method the migrate the database.
|
31
|
-
def request_class
|
32
|
-
@request_class ||= begin
|
33
|
-
klass = Class.new(RequestLogAnalyzer::Database::Base) do
|
34
|
-
|
35
|
-
def lines
|
36
|
-
@lines ||= begin
|
37
|
-
lines = []
|
38
|
-
self.class.reflections.each { |r, d| lines += self.send(r).all }
|
39
|
-
lines.sort
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Creates the requests table
|
44
|
-
def self.create_table!
|
45
|
-
unless database.connection.table_exists?(:requests)
|
46
|
-
database.connection.create_table(:requests) do |t|
|
47
|
-
t.column :first_lineno, :integer
|
48
|
-
t.column :last_lineno, :integer
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
Object.const_set('Request', klass)
|
55
|
-
Object.const_get('Request')
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Returns the Source ORM class for the current database.
|
60
|
-
#
|
61
|
-
# It will create the class if not previously done so. The class will
|
62
|
-
# include a create_table! method the migrate the database.
|
63
|
-
def source_class
|
64
|
-
@source_class ||= begin
|
65
|
-
klass = Class.new(RequestLogAnalyzer::Database::Base) do
|
66
|
-
|
67
|
-
# Creates the sources table
|
68
|
-
def self.create_table!
|
69
|
-
unless database.connection.table_exists?(:sources)
|
70
|
-
database.connection.create_table(:sources) do |t|
|
71
|
-
t.column :filename, :string
|
72
|
-
t.column :mtime, :datetime
|
73
|
-
t.column :filesize, :integer
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
Object.const_set('Source', klass)
|
80
|
-
Object.const_get('Source')
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
# Returns the Warning ORM class for the current database.
|
86
|
-
#
|
87
|
-
# It will create the class if not previously done so. The class will
|
88
|
-
# include a create_table! method the migrate the database.
|
89
|
-
def warning_class
|
90
|
-
@warning_class ||= begin
|
91
|
-
klass = Class.new(RequestLogAnalyzer::Database::Base) do
|
92
|
-
|
93
|
-
# Creates the warnings table
|
94
|
-
def self.create_table!
|
95
|
-
unless database.connection.table_exists?(:warnings)
|
96
|
-
database.connection.create_table(:warnings) do |t|
|
97
|
-
t.column :warning_type, :string, :limit => 30, :null => false
|
98
|
-
t.column :message, :string
|
99
|
-
t.column :source_id, :integer
|
100
|
-
t.column :lineno, :integer
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
Object.const_set('Warning', klass)
|
107
|
-
Object.const_get('Warning')
|
108
|
-
end
|
27
|
+
def default_classes
|
28
|
+
[RequestLogAnalyzer::Database::Request, RequestLogAnalyzer::Database::Source, RequestLogAnalyzer::Database::Warning]
|
109
29
|
end
|
110
30
|
|
111
31
|
# Loads the ORM classes by inspecting the tables in the current database
|
112
32
|
def load_database_schema!
|
113
33
|
connection.tables.map do |table|
|
114
34
|
case table.to_sym
|
115
|
-
when :warnings then
|
116
|
-
when :sources then
|
117
|
-
when :requests then
|
35
|
+
when :warnings then RequestLogAnalyzer::Database::Warning
|
36
|
+
when :sources then RequestLogAnalyzer::Database::Source
|
37
|
+
when :requests then RequestLogAnalyzer::Database::Request
|
118
38
|
else load_activerecord_class(table)
|
119
39
|
end
|
120
40
|
end
|
@@ -122,7 +42,7 @@ class RequestLogAnalyzer::Database
|
|
122
42
|
|
123
43
|
# Returns an array of all the ActiveRecord-bases ORM classes for this database
|
124
44
|
def orm_classes
|
125
|
-
|
45
|
+
default_classes + line_classes
|
126
46
|
end
|
127
47
|
|
128
48
|
# Loads an ActiveRecord-based class that correspond to the given parameter, which can either be
|
@@ -146,8 +66,6 @@ class RequestLogAnalyzer::Database
|
|
146
66
|
|
147
67
|
def fileformat_classes
|
148
68
|
raise "No file_format provided!" unless file_format
|
149
|
-
|
150
|
-
default_classes = [request_class, source_class, warning_class]
|
151
69
|
line_classes = file_format.line_definitions.map { |(name, definition)| load_activerecord_class(definition) }
|
152
70
|
return default_classes + line_classes
|
153
71
|
end
|
@@ -164,12 +82,19 @@ class RequestLogAnalyzer::Database
|
|
164
82
|
remove_orm_classes!
|
165
83
|
end
|
166
84
|
|
85
|
+
# Registers the default ORM classes in the default namespace
|
86
|
+
def register_default_orm_classes!
|
87
|
+
Object.const_set('Request', RequestLogAnalyzer::Database::Request)
|
88
|
+
Object.const_set('Source', RequestLogAnalyzer::Database::Source)
|
89
|
+
Object.const_set('Warning', RequestLogAnalyzer::Database::Warning)
|
90
|
+
end
|
91
|
+
|
167
92
|
# Unregisters every ORM class constant
|
168
93
|
def remove_orm_classes!
|
169
94
|
orm_classes.each do |klass|
|
170
95
|
if klass.respond_to?(:name) && !klass.name.blank?
|
171
|
-
|
172
|
-
Object.send(:remove_const,
|
96
|
+
klass_name = klass.name.split('::').last
|
97
|
+
Object.send(:remove_const, klass_name) if Object.const_defined?(klass_name)
|
173
98
|
end
|
174
99
|
end
|
175
100
|
end
|
@@ -32,8 +32,8 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
|
|
32
32
|
klass.send(:serialize, capture[:name], Hash)
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
RequestLogAnalyzer::Database::Request.has_many "#{definition.name}_lines".to_sym
|
36
|
+
RequestLogAnalyzer::Database::Source.has_many "#{definition.name}_lines".to_sym
|
37
37
|
|
38
38
|
return klass
|
39
39
|
end
|
@@ -46,12 +46,12 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
|
|
46
46
|
|
47
47
|
if klass.column_names.include?('request_id')
|
48
48
|
klass.belongs_to :request
|
49
|
-
|
49
|
+
RequestLogAnalyzer::Database::Request.has_many table.to_sym
|
50
50
|
end
|
51
51
|
|
52
52
|
if klass.column_names.include?('source_id')
|
53
53
|
klass.belongs_to :source
|
54
|
-
|
54
|
+
RequestLogAnalyzer::Database::Source.has_many table.to_sym
|
55
55
|
end
|
56
56
|
|
57
57
|
return klass
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class RequestLogAnalyzer::Database::Request < RequestLogAnalyzer::Database::Base
|
2
|
+
|
3
|
+
# Returns an array of all the Line objects of this request in the correct order.
|
4
|
+
def lines
|
5
|
+
@lines ||= begin
|
6
|
+
lines = []
|
7
|
+
self.class.reflections.each { |r, d| lines += self.send(r).all }
|
8
|
+
lines.sort
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates the table to store requests in.
|
13
|
+
def self.create_table!
|
14
|
+
unless database.connection.table_exists?(:requests)
|
15
|
+
database.connection.create_table(:requests) do |t|
|
16
|
+
t.column :first_lineno, :integer
|
17
|
+
t.column :last_lineno, :integer
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class RequestLogAnalyzer::Database::Source < RequestLogAnalyzer::Database::Base
|
2
|
+
|
3
|
+
def self.create_table!
|
4
|
+
unless database.connection.table_exists?(:sources)
|
5
|
+
database.connection.create_table(:sources) do |t|
|
6
|
+
t.column :filename, :string
|
7
|
+
t.column :mtime, :datetime
|
8
|
+
t.column :filesize, :integer
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class RequestLogAnalyzer::Database::Warning < RequestLogAnalyzer::Database::Base
|
2
|
+
|
3
|
+
def self.create_table!
|
4
|
+
unless database.connection.table_exists?(:warnings)
|
5
|
+
database.connection.create_table(:warnings) do |t|
|
6
|
+
t.column :warning_type, :string, :limit => 30, :null => false
|
7
|
+
t.column :message, :string
|
8
|
+
t.column :source_id, :integer
|
9
|
+
t.column :lineno, :integer
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -42,19 +42,7 @@ module RequestLogAnalyzer::FileFormat
|
|
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
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.
|
@@ -48,8 +48,7 @@ module RequestLogAnalyzer::FileFormat
|
|
48
48
|
# Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
|
49
49
|
# to speed up parsing.
|
50
50
|
def convert_timestamp(value, definition)
|
51
|
-
|
52
|
-
"#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
|
51
|
+
"#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
|
53
52
|
end
|
54
53
|
|
55
54
|
# Make sure that the string '-' is parsed as a nil value.
|
@@ -92,18 +92,13 @@ module RequestLogAnalyzer::FileFormat
|
|
92
92
|
|
93
93
|
analyze.frequency :category => :http_method, :amount => 20, :title => "HTTP methods" if line_definition.captures?(:http_method)
|
94
94
|
analyze.frequency :category => :http_status, :amount => 20, :title => "HTTP statuses" if line_definition.captures?(:http_status)
|
95
|
-
analyze.frequency :category =>
|
95
|
+
analyze.frequency :category => lambda { |r| r.category }, :amount => 20, :title => "Most popular URIs" if line_definition.captures?(:path)
|
96
96
|
|
97
97
|
analyze.frequency :category => :user_agent, :amount => 20, :title => "User agents" if line_definition.captures?(:user_agent)
|
98
98
|
analyze.frequency :category => :referer, :amount => 20, :title => "Referers" if line_definition.captures?(:referer)
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
end
|
103
|
-
|
104
|
-
if line_definition.captures?(:path) && line_definition.captures?(:bytes_sent)
|
105
|
-
analyze.traffic :traffic => :bytes_sent, :category => :path , :title => 'Traffic'
|
106
|
-
end
|
100
|
+
analyze.duration :duration => :duration, :category => lambda { |r| r.category }, :title => 'Request duration' if line_definition.captures?(:duration)
|
101
|
+
analyze.traffic :traffic => :bytes_sent, :category => lambda { |r| r.category }, :title => 'Traffic' if line_definition.captures?(:bytes_sent)
|
107
102
|
|
108
103
|
return analyze.trackers
|
109
104
|
end
|
@@ -111,14 +106,17 @@ module RequestLogAnalyzer::FileFormat
|
|
111
106
|
# Define a custom Request class for the Apache file format to speed up timestamp handling.
|
112
107
|
class Request < RequestLogAnalyzer::Request
|
113
108
|
|
109
|
+
def category
|
110
|
+
first(:path)
|
111
|
+
end
|
112
|
+
|
114
113
|
MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
|
115
114
|
'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
|
116
115
|
|
117
116
|
# Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
|
118
117
|
# to speed up parsing.
|
119
118
|
def convert_timestamp(value, definition)
|
120
|
-
|
121
|
-
"#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
|
119
|
+
"#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
|
122
120
|
end
|
123
121
|
|
124
122
|
# This function can be overridden to rewrite the path for better categorization in the
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module RequestLogAnalyzer::FileFormat
|
2
2
|
|
3
|
+
# The Merb file format parses the request header with the timestamp, the params line
|
4
|
+
# with the most important request information and the durations line which contains
|
5
|
+
# the different request durations that can be used for analysis.
|
3
6
|
class Merb < Base
|
4
7
|
|
5
8
|
# ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
|
6
9
|
line_definition :started do |line|
|
7
10
|
line.header = true
|
8
|
-
line.teaser = /Started/
|
11
|
+
# line.teaser = /Started/
|
9
12
|
line.regexp = /Started request handling\:\ (.+)/
|
10
13
|
line.captures << { :name => :timestamp, :type => :timestamp }
|
11
14
|
end
|
@@ -13,7 +16,7 @@ module RequestLogAnalyzer::FileFormat
|
|
13
16
|
# ~ Params: {"action"=>"create", "controller"=>"session"}
|
14
17
|
# ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
|
15
18
|
line_definition :params do |line|
|
16
|
-
line.teaser = /Params/
|
19
|
+
# line.teaser = /Params/
|
17
20
|
line.regexp = /Params\:\ (\{.+\})/
|
18
21
|
line.captures << { :name => :params, :type => :eval, :provides => {
|
19
22
|
:namespace => :string, :controller => :string, :action => :string, :format => :string, :method => :string } }
|
@@ -36,15 +39,28 @@ module RequestLogAnalyzer::FileFormat
|
|
36
39
|
end
|
37
40
|
|
38
41
|
report do |analyze|
|
39
|
-
|
42
|
+
|
43
|
+
analyze.timespan
|
44
|
+
analyze.hourly_spread
|
45
|
+
|
40
46
|
analyze.frequency :category => REQUEST_CATEGORIZER, :amount => 20, :title => "Top 20 by hits"
|
41
|
-
analyze.hourly_spread :line_type => :started
|
42
47
|
analyze.duration :dispatch_time, :category => REQUEST_CATEGORIZER, :title => 'Request dispatch duration'
|
48
|
+
|
43
49
|
# analyze.duration :action_time, :category => REQUEST_CATEGORIZER, :title => 'Request action duration'
|
44
50
|
# analyze.duration :after_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request after_filter duration'
|
45
51
|
# analyze.duration :before_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request before_filter duration'
|
46
52
|
end
|
47
|
-
|
53
|
+
|
54
|
+
class Request < RequestLogAnalyzer::Request
|
55
|
+
|
56
|
+
MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
|
57
|
+
'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
|
58
|
+
|
59
|
+
# Speed up timestamp conversion
|
60
|
+
def convert_timestamp(value, definition)
|
61
|
+
"#{value[26,4]}#{MONTHS[value[4,3]]}#{value[8,2]}#{value[11,2]}#{value[14,2]}#{value[17,2]}".to_i
|
62
|
+
end
|
63
|
+
end
|
48
64
|
end
|
49
65
|
|
50
66
|
end
|