request-log-analyzer 1.10.0 → 1.11.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/.gitignore +2 -1
- data/.infinity_test +8 -0
- data/bin/request-log-analyzer +0 -1
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +2 -1
- data/lib/request_log_analyzer/aggregator.rb +5 -5
- data/lib/request_log_analyzer/controller.rb +0 -3
- data/lib/request_log_analyzer/database.rb +6 -7
- data/lib/request_log_analyzer/file_format/apache.rb +1 -1
- data/lib/request_log_analyzer/file_format/delayed_job2.rb +2 -2
- data/lib/request_log_analyzer/file_format/delayed_job21.rb +2 -2
- data/lib/request_log_analyzer/file_format/haproxy.rb +107 -13
- data/lib/request_log_analyzer/file_format/mysql.rb +5 -5
- data/lib/request_log_analyzer/file_format/rails3.rb +8 -1
- data/lib/request_log_analyzer/file_format.rb +43 -14
- data/lib/request_log_analyzer/filter.rb +4 -5
- data/lib/request_log_analyzer/line_definition.rb +6 -4
- data/lib/request_log_analyzer/output.rb +3 -5
- data/lib/request_log_analyzer/source/log_parser.rb +56 -4
- data/lib/request_log_analyzer/source.rb +3 -4
- data/lib/request_log_analyzer/tracker.rb +8 -8
- data/lib/request_log_analyzer.rb +15 -29
- data/request-log-analyzer.gemspec +3 -3
- data/spec/fixtures/mysql_slow_query.log +0 -1
- data/spec/integration/command_line_usage_spec.rb +0 -5
- data/spec/lib/helpers.rb +2 -2
- data/spec/lib/matchers.rb +38 -7
- data/spec/lib/mocks.rb +1 -5
- data/spec/unit/database/base_class_spec.rb +1 -0
- data/spec/unit/file_format/amazon_s3_format_spec.rb +58 -55
- data/spec/unit/file_format/apache_format_spec.rb +74 -162
- data/spec/unit/file_format/common_regular_expressions_spec.rb +51 -26
- data/spec/unit/file_format/delayed_job21_format_spec.rb +22 -31
- data/spec/unit/file_format/delayed_job2_format_spec.rb +27 -32
- data/spec/unit/file_format/delayed_job_format_spec.rb +44 -63
- data/spec/unit/file_format/haproxy_format_spec.rb +69 -71
- data/spec/unit/file_format/line_definition_spec.rb +26 -33
- data/spec/unit/file_format/merb_format_spec.rb +22 -37
- data/spec/unit/file_format/mysql_format_spec.rb +80 -123
- data/spec/unit/file_format/oink_format_spec.rb +29 -61
- data/spec/unit/file_format/postgresql_format_spec.rb +2 -4
- data/spec/unit/file_format/rack_format_spec.rb +49 -44
- data/spec/unit/file_format/rails3_format_spec.rb +39 -37
- data/spec/unit/file_format/rails_format_spec.rb +52 -68
- data/spec/unit/file_format/w3c_format_spec.rb +40 -39
- data/spec/unit/source/log_parser_spec.rb +1 -1
- metadata +4 -6
- data/lib/mixins/gets_memory_protection.rb +0 -80
- data/lib/request_log_analyzer/output/fancy_html.rb +0 -44
- data/lib/request_log_analyzer/source/database_loader.rb +0 -87
data/.gitignore
CHANGED
data/.infinity_test
ADDED
data/bin/request-log-analyzer
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
module RequestLogAnalyzer::Aggregator
|
|
3
2
|
|
|
4
3
|
# The database aggregator will create an SQLite3 database with all parsed request information.
|
|
@@ -19,6 +18,8 @@ module RequestLogAnalyzer::Aggregator
|
|
|
19
18
|
# Establishes a connection to the database and creates the necessary database schema for the
|
|
20
19
|
# current file format
|
|
21
20
|
def prepare
|
|
21
|
+
require 'request_log_analyzer/database'
|
|
22
|
+
|
|
22
23
|
@sources = {}
|
|
23
24
|
@database = RequestLogAnalyzer::Database.new(options[:database])
|
|
24
25
|
@database.file_format = source.file_format
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Aggregator
|
|
2
2
|
|
|
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
|
-
|
|
7
3
|
# The base class of an aggregator. This class provides the interface to which
|
|
8
4
|
# every aggregator should comply (by simply subclassing this class).
|
|
9
5
|
class Base
|
|
@@ -46,4 +42,8 @@ module RequestLogAnalyzer::Aggregator
|
|
|
46
42
|
end
|
|
47
43
|
|
|
48
44
|
end
|
|
49
|
-
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
require 'request_log_analyzer/aggregator/echo'
|
|
48
|
+
require 'request_log_analyzer/aggregator/summarizer'
|
|
49
|
+
require 'request_log_analyzer/aggregator/database_inserter'
|
|
@@ -21,8 +21,6 @@ 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 'mixins/gets_memory_protection' if arguments[:gets_memory_protection]
|
|
25
|
-
|
|
26
24
|
options = {}
|
|
27
25
|
|
|
28
26
|
# Copy fields
|
|
@@ -58,7 +56,6 @@ module RequestLogAnalyzer
|
|
|
58
56
|
|
|
59
57
|
# Handle output format casing
|
|
60
58
|
if options[:output].class == String
|
|
61
|
-
options[:output] = 'FancyHTML' if options[:output] =~ /^fancy_?html$/i
|
|
62
59
|
options[:output] = 'HTML' if options[:output] =~ /^html$/i
|
|
63
60
|
options[:output] = 'FixedWidth' if options[:output] =~ /^fixed_?width$/i
|
|
64
61
|
end
|
|
@@ -3,12 +3,7 @@ require 'active_record'
|
|
|
3
3
|
|
|
4
4
|
class RequestLogAnalyzer::Database
|
|
5
5
|
|
|
6
|
-
|
|
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'
|
|
11
|
-
|
|
6
|
+
require 'request_log_analyzer/database/connection'
|
|
12
7
|
include RequestLogAnalyzer::Database::Connection
|
|
13
8
|
|
|
14
9
|
attr_accessor :file_format
|
|
@@ -100,5 +95,9 @@ class RequestLogAnalyzer::Database
|
|
|
100
95
|
end
|
|
101
96
|
end
|
|
102
97
|
end
|
|
103
|
-
|
|
104
98
|
end
|
|
99
|
+
|
|
100
|
+
require 'request_log_analyzer/database/base'
|
|
101
|
+
require 'request_log_analyzer/database/request'
|
|
102
|
+
require 'request_log_analyzer/database/source'
|
|
103
|
+
require 'request_log_analyzer/database/warning'
|
|
@@ -31,7 +31,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
31
31
|
# A hash that defines how the log format directives should be parsed.
|
|
32
32
|
LOG_DIRECTIVES = {
|
|
33
33
|
'%' => { nil => { :regexp => '%', :captures => [] } },
|
|
34
|
-
'h' => { nil => { :regexp =>
|
|
34
|
+
'h' => { nil => { :regexp => "(#{hostname_or_ip_address})", :captures => [{:name => :remote_host, :type => :string}] } },
|
|
35
35
|
'a' => { nil => { :regexp => "(#{ip_address})", :captures => [{:name => :remote_ip, :type => :string}] } },
|
|
36
36
|
'b' => { nil => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] } },
|
|
37
37
|
'c' => { nil => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] } },
|
|
@@ -8,7 +8,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
8
8
|
|
|
9
9
|
line_definition :job_lock do |line|
|
|
10
10
|
line.header = true
|
|
11
|
-
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[Worker\(\S+ host:(
|
|
11
|
+
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[Worker\(\S+ host:(#{hostname_or_ip_address}) pid:(\d+)\)\] acquired lock on (\S+)/
|
|
12
12
|
|
|
13
13
|
line.capture(:timestamp).as(:timestamp)
|
|
14
14
|
line.capture(:host)
|
|
@@ -18,7 +18,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
18
18
|
|
|
19
19
|
line_definition :job_completed do |line|
|
|
20
20
|
line.footer = true
|
|
21
|
-
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[JOB\] \S+ host:(
|
|
21
|
+
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[JOB\] \S+ host:(#{hostname_or_ip_address}) pid:(\d+) completed after (\d+\.\d+)/
|
|
22
22
|
line.capture(:timestamp).as(:timestamp)
|
|
23
23
|
line.capture(:host)
|
|
24
24
|
line.capture(:pid).as(:integer)
|
|
@@ -8,7 +8,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
8
8
|
|
|
9
9
|
line_definition :job_lock do |line|
|
|
10
10
|
line.header = true
|
|
11
|
-
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(
|
|
11
|
+
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(#{hostname_or_ip_address}) pid:(\d+)\)\] acquired lock on (\S+)/
|
|
12
12
|
|
|
13
13
|
line.capture(:timestamp).as(:timestamp)
|
|
14
14
|
line.capture(:host)
|
|
@@ -18,7 +18,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
18
18
|
|
|
19
19
|
line_definition :job_completed do |line|
|
|
20
20
|
line.footer = true
|
|
21
|
-
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(
|
|
21
|
+
line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(#{hostname_or_ip_address}) pid:(\d+)\)\] (\S+) completed after (\d+\.\d+)/
|
|
22
22
|
line.capture(:timestamp).as(:timestamp)
|
|
23
23
|
line.capture(:host)
|
|
24
24
|
line.capture(:pid).as(:integer)
|
|
@@ -4,31 +4,45 @@ module RequestLogAnalyzer::FileFormat
|
|
|
4
4
|
|
|
5
5
|
extend CommonRegularExpressions
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
line.footer = true
|
|
11
|
-
|
|
12
|
-
line.regexp = %r{
|
|
7
|
+
# substitute version specific parts of the haproxy entry regexp.
|
|
8
|
+
def self.compose_regexp(millisecs, backends, counters, connections, queues)
|
|
9
|
+
%r{
|
|
13
10
|
(#{ip_address}):\d+\s # client_ip ':' client_port
|
|
14
|
-
\[(#{timestamp('%d/%b/%Y:%H:%M:%S')})
|
|
11
|
+
\[(#{timestamp('%d/%b/%Y:%H:%M:%S')})#{millisecs}\]\s # '[' accept_date ']'
|
|
15
12
|
(\S+)\s # frontend_name
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
#{backends}
|
|
14
|
+
#{counters}
|
|
18
15
|
(\d+)\s # status_code
|
|
19
16
|
\+?(\d+)\s # bytes_read
|
|
20
17
|
(\S+)\s # captured_request_cookie
|
|
21
18
|
(\S+)\s # captured_response_cookie
|
|
22
19
|
(\w|-)(\w|-)(\w|-)(\w|-)\s # termination_state
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
#{connections}
|
|
21
|
+
#{queues}
|
|
25
22
|
(\S*)\s? # captured_request_headers
|
|
26
23
|
(\S*)\s? # captured_response_headers
|
|
27
24
|
"([^"]*)" # '"' http_request '"'
|
|
28
25
|
}x
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Define line types
|
|
29
|
+
|
|
30
|
+
# line definition for haproxy 1.3 and higher
|
|
31
|
+
line_definition :haproxy13 do |line|
|
|
32
|
+
line.header = true
|
|
33
|
+
line.footer = true
|
|
34
|
+
line.teaser = /\.\d{3}\] \S+ \S+\/\S+ / # .millisecs] frontend_name backend_name/server_name
|
|
35
|
+
|
|
36
|
+
line.regexp = compose_regexp(
|
|
37
|
+
'\.\d{3}', # millisecs
|
|
38
|
+
'(\S+)\/(\S+)\s', # backend_name '/' server_name
|
|
39
|
+
'(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/\+?(\d+)\s', # Tq '/' Tw '/' Tc '/' Tr '/' Tt
|
|
40
|
+
'(\d+)\/(\d+)\/(\d+)\/(\d+)\/\+?(\d+)\s', # actconn '/' feconn '/' beconn '/' srv_conn '/' retries
|
|
41
|
+
'(\d+)\/(\d+)\s' # srv_queue '/' backend_queue
|
|
42
|
+
)
|
|
29
43
|
|
|
30
44
|
line.capture(:client_ip).as(:string)
|
|
31
|
-
line.capture(:
|
|
45
|
+
line.capture(:timestamp).as(:timestamp)
|
|
32
46
|
line.capture(:frontend_name).as(:string)
|
|
33
47
|
line.capture(:backend_name).as(:string)
|
|
34
48
|
line.capture(:server_name).as(:string)
|
|
@@ -57,9 +71,89 @@ module RequestLogAnalyzer::FileFormat
|
|
|
57
71
|
line.capture(:http_request).as(:nillable_string)
|
|
58
72
|
end
|
|
59
73
|
|
|
74
|
+
# haproxy 1.2 has a few fields less than 1.3+
|
|
75
|
+
line_definition :haproxy12 do |line|
|
|
76
|
+
line.header = true
|
|
77
|
+
line.footer = true
|
|
78
|
+
line.teaser = /\.\d{3}\] \S+ \S+ / # .millisecs] frontend_name server_name
|
|
79
|
+
|
|
80
|
+
line.regexp = compose_regexp(
|
|
81
|
+
'\.\d{3}', # millisecs
|
|
82
|
+
'(\S+)\s', # server_name
|
|
83
|
+
'(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/\+?(\d+)\s', # Tq '/' Tw '/' Tc '/' Tr '/' Tt
|
|
84
|
+
'(\d+)\/(\d+)\/(\d+)\s', # srv_conn '/' listener_conn '/' process_conn
|
|
85
|
+
'(\d+)\/(\d+)\s' # srv_queue '/' backend_queue
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
line.capture(:client_ip).as(:string)
|
|
89
|
+
line.capture(:timestamp).as(:timestamp)
|
|
90
|
+
line.capture(:frontend_name).as(:string)
|
|
91
|
+
line.capture(:server_name).as(:string)
|
|
92
|
+
line.capture(:tq).as(:nillable_duration, :unit => :msec)
|
|
93
|
+
line.capture(:tw).as(:nillable_duration, :unit => :msec)
|
|
94
|
+
line.capture(:tc).as(:nillable_duration, :unit => :msec)
|
|
95
|
+
line.capture(:tr).as(:nillable_duration, :unit => :msec)
|
|
96
|
+
line.capture(:tt).as(:duration, :unit => :msec)
|
|
97
|
+
line.capture(:status_code).as(:integer)
|
|
98
|
+
line.capture(:bytes_read).as(:traffic, :unit => :byte)
|
|
99
|
+
line.capture(:captured_request_cookie).as(:nillable_string)
|
|
100
|
+
line.capture(:captured_response_cookie).as(:nillable_string)
|
|
101
|
+
line.capture(:termination_event_code).as(:nillable_string)
|
|
102
|
+
line.capture(:terminated_session_state).as(:nillable_string)
|
|
103
|
+
line.capture(:clientside_persistence_cookie).as(:nillable_string)
|
|
104
|
+
line.capture(:serverside_persistence_cookie).as(:nillable_string)
|
|
105
|
+
line.capture(:srv_conn).as(:integer)
|
|
106
|
+
line.capture(:listener_conn).as(:integer)
|
|
107
|
+
line.capture(:process_conn).as(:integer)
|
|
108
|
+
line.capture(:srv_queue).as(:integer)
|
|
109
|
+
line.capture(:backend_queue).as(:integer)
|
|
110
|
+
line.capture(:captured_request_headers).as(:nillable_string)
|
|
111
|
+
line.capture(:captured_response_headers).as(:nillable_string)
|
|
112
|
+
line.capture(:http_request).as(:nillable_string)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# and haproxy 1.1 has even less fields
|
|
116
|
+
line_definition :haproxy11 do |line|
|
|
117
|
+
line.header = true
|
|
118
|
+
line.footer = true
|
|
119
|
+
line.teaser = /:\d{2}\] \S+ \S+ / # :secs] frontend_name server_name
|
|
120
|
+
|
|
121
|
+
line.regexp = compose_regexp(
|
|
122
|
+
'', # no millisec precision in this version of haproxy
|
|
123
|
+
'(\S+)\s', # server_name
|
|
124
|
+
'(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/\+?(\d+)\s', # Tq '/' Tc '/' Tr '/' Tt
|
|
125
|
+
'(\d+)\/(\d+)\s', # listener_conn '/' process_conn
|
|
126
|
+
'' # no queues in this version of haproxy
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
line.capture(:client_ip).as(:string)
|
|
130
|
+
line.capture(:timestamp).as(:timestamp)
|
|
131
|
+
line.capture(:frontend_name).as(:string)
|
|
132
|
+
line.capture(:server_name).as(:string)
|
|
133
|
+
line.capture(:tq).as(:nillable_duration, :unit => :msec)
|
|
134
|
+
line.capture(:tc).as(:nillable_duration, :unit => :msec)
|
|
135
|
+
line.capture(:tr).as(:nillable_duration, :unit => :msec)
|
|
136
|
+
line.capture(:tt).as(:duration, :unit => :msec)
|
|
137
|
+
line.capture(:status_code).as(:integer)
|
|
138
|
+
line.capture(:bytes_read).as(:traffic, :unit => :byte)
|
|
139
|
+
line.capture(:captured_request_cookie).as(:nillable_string)
|
|
140
|
+
line.capture(:captured_response_cookie).as(:nillable_string)
|
|
141
|
+
line.capture(:termination_event_code).as(:nillable_string)
|
|
142
|
+
line.capture(:terminated_session_state).as(:nillable_string)
|
|
143
|
+
line.capture(:clientside_persistence_cookie).as(:nillable_string)
|
|
144
|
+
line.capture(:serverside_persistence_cookie).as(:nillable_string)
|
|
145
|
+
line.capture(:listener_conn).as(:integer)
|
|
146
|
+
line.capture(:process_conn).as(:integer)
|
|
147
|
+
line.capture(:srv_queue).as(:integer)
|
|
148
|
+
line.capture(:backend_queue).as(:integer)
|
|
149
|
+
line.capture(:captured_request_headers).as(:nillable_string)
|
|
150
|
+
line.capture(:captured_response_headers).as(:nillable_string)
|
|
151
|
+
line.capture(:http_request).as(:nillable_string)
|
|
152
|
+
end
|
|
153
|
+
|
|
60
154
|
# Define the summary report
|
|
61
155
|
report do |analyze|
|
|
62
|
-
analyze.hourly_spread :field => :
|
|
156
|
+
analyze.hourly_spread :field => :timestamp
|
|
63
157
|
|
|
64
158
|
analyze.frequency :client_ip,
|
|
65
159
|
:title => "Hits per IP"
|
|
@@ -15,7 +15,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
15
15
|
line_definition :user_host do |line|
|
|
16
16
|
line.header = :alternative
|
|
17
17
|
line.teaser = /\# User\@Host\: /
|
|
18
|
-
line.regexp = /\# User\@Host\: ([\w-]+)\[[\w-]+\] \@ (
|
|
18
|
+
line.regexp = /\# User\@Host\: ([\w-]+)\[[\w-]+\] \@ (#{hostname(true)}) \[(#{ip_address(true)})\]/
|
|
19
19
|
|
|
20
20
|
line.capture(:user)
|
|
21
21
|
line.capture(:host)
|
|
@@ -34,18 +34,18 @@ module RequestLogAnalyzer::FileFormat
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
line_definition :use_database do |line|
|
|
37
|
-
line.regexp =
|
|
37
|
+
line.regexp = /^\s*use (\w+);\s*$/
|
|
38
38
|
line.capture(:database)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
line_definition :query_part do |line|
|
|
42
|
-
line.regexp =
|
|
42
|
+
line.regexp = /^\s*(?!(?:use |\# |SET ))(.*[^;\s])\s*$/
|
|
43
43
|
line.capture(:query_fragment)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
line_definition :query do |line|
|
|
47
47
|
line.footer = true
|
|
48
|
-
line.regexp = /^(
|
|
48
|
+
line.regexp = /^(?!\s*(?:use |\# |SET ))(.*);\s*$/
|
|
49
49
|
line.capture(:query).as(:sql)
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -84,7 +84,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
84
84
|
sql.gsub!(/(:int,)+:int/, ':ints') # replace multiple ints by a list
|
|
85
85
|
sql.gsub!(/(:string,)+:string/, ':strings') # replace multiple strings by a list
|
|
86
86
|
|
|
87
|
-
return sql.
|
|
87
|
+
return sql.strip
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def host
|
|
@@ -23,12 +23,19 @@ module RequestLogAnalyzer::FileFormat
|
|
|
23
23
|
# Processing by QueriesController#index as HTML
|
|
24
24
|
line_definition :processing do |line|
|
|
25
25
|
line.teaser = /Processing by /
|
|
26
|
-
line.regexp = /Processing by ([A-Za-z0-9\-:]+)\#(\w+) as ([\w\/\*]
|
|
26
|
+
line.regexp = /Processing by ([A-Za-z0-9\-:]+)\#(\w+) as ([\w\/\*]*)/
|
|
27
27
|
|
|
28
28
|
line.capture(:controller)
|
|
29
29
|
line.capture(:action)
|
|
30
30
|
line.capture(:format)
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
# Parameters: {"action"=>"cached", "controller"=>"cached"}
|
|
34
|
+
line_definition :parameters do |line|
|
|
35
|
+
line.teaser = / Parameters:/
|
|
36
|
+
line.regexp = / Parameters:\s+(\{.*\})/
|
|
37
|
+
line.capture(:params).as(:eval)
|
|
38
|
+
end
|
|
32
39
|
|
|
33
40
|
# Completed 200 OK in 224ms (Views: 200.2ms | ActiveRecord: 3.4ms)
|
|
34
41
|
# Completed 302 Found in 23ms
|
|
@@ -34,7 +34,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
34
34
|
|
|
35
35
|
elsif file_format.kind_of?(String) && File.exist?(file_format) && File.file?(file_format)
|
|
36
36
|
# load a format from a ruby file
|
|
37
|
-
require file_format
|
|
37
|
+
require File.expand_path(file_format)
|
|
38
38
|
|
|
39
39
|
const = RequestLogAnalyzer.to_camelcase(File.basename(file_format, '.rb'))
|
|
40
40
|
if RequestLogAnalyzer::FileFormat.const_defined?(const)
|
|
@@ -121,6 +121,18 @@ module RequestLogAnalyzer::FileFormat
|
|
|
121
121
|
'Z' => '(?:[+-]\d{4}|[A-Z]{3,4})',
|
|
122
122
|
'%' => '%'
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
# Creates a regular expression to match a hostname
|
|
126
|
+
def hostname(blank = false)
|
|
127
|
+
regexp = /(?:(?:[a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*(?:[A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])/
|
|
128
|
+
add_blank_option(regexp, blank)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Creates a regular expression to match a hostname or ip address
|
|
132
|
+
def hostname_or_ip_address(blank = false)
|
|
133
|
+
regexp = Regexp.union(hostname, ip_address)
|
|
134
|
+
add_blank_option(regexp, blank)
|
|
135
|
+
end
|
|
124
136
|
|
|
125
137
|
# Create a regular expression for a timestamp, generated by a strftime call.
|
|
126
138
|
# Provide the format string to construct a matching regular expression.
|
|
@@ -139,12 +151,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
139
151
|
end
|
|
140
152
|
end
|
|
141
153
|
|
|
142
|
-
|
|
143
|
-
return case blank
|
|
144
|
-
when String then Regexp.union(regexp, Regexp.new(Regexp.quote(blank)))
|
|
145
|
-
when true then Regexp.union(regexp, //)
|
|
146
|
-
else regexp
|
|
147
|
-
end
|
|
154
|
+
add_blank_option(Regexp.new(regexp), blank)
|
|
148
155
|
end
|
|
149
156
|
|
|
150
157
|
# Construct a regular expression to parse IPv4 and IPv6 addresses.
|
|
@@ -162,12 +169,22 @@ module RequestLogAnalyzer::FileFormat
|
|
|
162
169
|
ipv6_regex_compressed_hex_4_dec = /(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::(?:(?:[0-9A-Fa-f]{1,4}:)*)#{ipv4_regexp}/
|
|
163
170
|
ipv6_regexp = Regexp.union(ipv6_regex_8_hex, ipv6_regex_compressed_hex, ipv6_regex_6_hex_4_dec, ipv6_regex_compressed_hex_4_dec)
|
|
164
171
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
add_blank_option(Regexp.union(ipv4_regexp, ipv6_regexp), blank)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def anchored(regexp)
|
|
176
|
+
/^#{regexp}$/
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
protected
|
|
180
|
+
|
|
181
|
+
# Allow the field to be blank if this option is given. This can be true to
|
|
182
|
+
# allow an empty string or a string alternative for the nil value.
|
|
183
|
+
def add_blank_option(regexp, blank)
|
|
184
|
+
case blank
|
|
185
|
+
when String; Regexp.union(regexp, Regexp.new(Regexp.quote(blank)))
|
|
186
|
+
when true; Regexp.union(regexp, //)
|
|
187
|
+
else regexp
|
|
171
188
|
end
|
|
172
189
|
end
|
|
173
190
|
end
|
|
@@ -260,9 +277,12 @@ module RequestLogAnalyzer::FileFormat
|
|
|
260
277
|
end
|
|
261
278
|
|
|
262
279
|
# Checks whether the file format is valid so it can be safely used with RLA.
|
|
263
|
-
def
|
|
280
|
+
def well_formed?
|
|
264
281
|
valid_line_definitions? && valid_request_class?
|
|
265
282
|
end
|
|
283
|
+
|
|
284
|
+
alias_method :valid?, :well_formed?
|
|
285
|
+
|
|
266
286
|
|
|
267
287
|
# Checks whether the line definitions form a valid language.
|
|
268
288
|
# A file format should have at least a header and a footer line type
|
|
@@ -294,5 +314,14 @@ module RequestLogAnalyzer::FileFormat
|
|
|
294
314
|
|
|
295
315
|
return nil
|
|
296
316
|
end
|
|
317
|
+
|
|
318
|
+
# Returns the max line length for this file format if any.
|
|
319
|
+
def max_line_length
|
|
320
|
+
self.class.const_get(MAX_LINE_LENGTH) if self.class.const_defined?(:MAX_LINE_LENGTH)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def line_divider
|
|
324
|
+
self.class.const_get(LINE_DIVIDER) if self.class.const_defined?(:LINE_DIVIDER)
|
|
325
|
+
end
|
|
297
326
|
end
|
|
298
327
|
end
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
2
|
|
|
3
|
-
autoload :Field, 'request_log_analyzer/filter/field'
|
|
4
|
-
autoload :Timespan, 'request_log_analyzer/filter/timespan'
|
|
5
|
-
autoload :Anonymize, 'request_log_analyzer/filter/anonymize'
|
|
6
|
-
|
|
7
3
|
# Base filter class used to filter input requests.
|
|
8
4
|
# All filters should interit from this base.
|
|
9
5
|
class Base
|
|
@@ -24,5 +20,8 @@ module RequestLogAnalyzer::Filter
|
|
|
24
20
|
request
|
|
25
21
|
end
|
|
26
22
|
end
|
|
23
|
+
end
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
require 'request_log_analyzer/filter/field'
|
|
26
|
+
require 'request_log_analyzer/filter/timespan'
|
|
27
|
+
require 'request_log_analyzer/filter/anonymize'
|
|
@@ -70,6 +70,10 @@ module RequestLogAnalyzer
|
|
|
70
70
|
captures << new_capture_hash
|
|
71
71
|
CaptureDefiner.new(new_capture_hash)
|
|
72
72
|
end
|
|
73
|
+
|
|
74
|
+
def all_captured_variables
|
|
75
|
+
captures.map { |c| c[:name] } + captures.map { |c| c[:provides] }.compact.map { |pr| pr.keys }.flatten
|
|
76
|
+
end
|
|
73
77
|
|
|
74
78
|
# Checks whether a given line matches this definition.
|
|
75
79
|
# It will return false if a line does not match. If the line matches, a hash is returned
|
|
@@ -128,9 +132,7 @@ module RequestLogAnalyzer
|
|
|
128
132
|
|
|
129
133
|
# Returns true if this line captures values of the given name
|
|
130
134
|
def captures?(name)
|
|
131
|
-
|
|
135
|
+
all_captured_variables.include?(name)
|
|
132
136
|
end
|
|
133
|
-
|
|
134
137
|
end
|
|
135
|
-
|
|
136
|
-
end
|
|
138
|
+
end
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# Module for generating output
|
|
2
2
|
module RequestLogAnalyzer::Output
|
|
3
3
|
|
|
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
|
-
|
|
8
4
|
# Loads a Output::Base subclass instance.
|
|
9
5
|
def self.load(file_format, *args)
|
|
10
6
|
|
|
@@ -111,6 +107,8 @@ module RequestLogAnalyzer::Output
|
|
|
111
107
|
def table_has_header?(columns)
|
|
112
108
|
columns.any? { |column| !column[:title].nil? }
|
|
113
109
|
end
|
|
114
|
-
|
|
115
110
|
end
|
|
116
111
|
end
|
|
112
|
+
|
|
113
|
+
require 'request_log_analyzer/output/fixed_width'
|
|
114
|
+
require 'request_log_analyzer/output/html'
|
|
@@ -13,6 +13,11 @@ module RequestLogAnalyzer::Source
|
|
|
13
13
|
|
|
14
14
|
include Enumerable
|
|
15
15
|
|
|
16
|
+
# The maximum number of bytes to read from a line.
|
|
17
|
+
DEFAULT_MAX_LINE_LENGTH = 8096
|
|
18
|
+
|
|
19
|
+
DEFAULT_LINE_DIVIDER = "\n"
|
|
20
|
+
|
|
16
21
|
# The default parse strategy that will be used to parse the input.
|
|
17
22
|
DEFAULT_PARSE_STRATEGY = 'assume-correct'
|
|
18
23
|
|
|
@@ -48,6 +53,14 @@ module RequestLogAnalyzer::Source
|
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
|
|
56
|
+
def max_line_length
|
|
57
|
+
file_format.max_line_length || DEFAULT_MAX_LINE_LENGTH
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def line_divider
|
|
61
|
+
file_format.line_divider || DEFAULT_LINE_DIVIDER
|
|
62
|
+
end
|
|
63
|
+
|
|
51
64
|
# Reads the input, which can either be a file, sequence of files or STDIN to parse
|
|
52
65
|
# lines specified in the FileFormat. This lines will be combined into Request instances,
|
|
53
66
|
# that will be yielded. The actual parsing occurs in the parse_io method.
|
|
@@ -132,6 +145,13 @@ module RequestLogAnalyzer::Source
|
|
|
132
145
|
def parse_stream(stream, options = {}, &block)
|
|
133
146
|
parse_io(stream, options, &block)
|
|
134
147
|
end
|
|
148
|
+
|
|
149
|
+
# Parses a string. It will simply call parse_io. This function does not support progress updates.
|
|
150
|
+
# <tt>string</tt>:: The string that should be parsed.
|
|
151
|
+
# <tt>options</tt>:: A Hash of options that will be pased to parse_io.
|
|
152
|
+
def parse_string(string, options = {}, &block)
|
|
153
|
+
parse_io(StringIO.new(string), options, &block)
|
|
154
|
+
end
|
|
135
155
|
|
|
136
156
|
# This method loops over each line of the input stream. It will try to parse this line as any of
|
|
137
157
|
# the lines that are defined by the current file format (see RequestLogAnalyazer::FileFormat).
|
|
@@ -143,11 +163,15 @@ module RequestLogAnalyzer::Source
|
|
|
143
163
|
# - The method will yield progress updates if a progress handler is installed using progress=
|
|
144
164
|
# - The method will yield parse warnings if a warning handler is installed using warning=
|
|
145
165
|
#
|
|
166
|
+
# This is a Ruby 1.9 specific version that offers memory protection.
|
|
167
|
+
#
|
|
146
168
|
# <tt>io</tt>:: The IO instance to use as source
|
|
147
169
|
# <tt>options</tt>:: A hash of options that can be used by the parser.
|
|
148
|
-
def
|
|
149
|
-
@
|
|
150
|
-
|
|
170
|
+
def parse_io_19(io, options = {}, &block) # :yields: request
|
|
171
|
+
@max_line_length = options[:max_line_length] || max_line_length
|
|
172
|
+
@line_divider = options[:line_divider] || line_divider
|
|
173
|
+
@current_lineno = 0
|
|
174
|
+
while line = io.gets(@line_divider, @max_line_length)
|
|
151
175
|
@current_lineno += 1
|
|
152
176
|
@progress_handler.call(:progress, io.pos) if @progress_handler && @current_lineno % 255 == 0
|
|
153
177
|
parse_line(line, &block)
|
|
@@ -157,6 +181,35 @@ module RequestLogAnalyzer::Source
|
|
|
157
181
|
@current_lineno = nil
|
|
158
182
|
end
|
|
159
183
|
|
|
184
|
+
# This method loops over each line of the input stream. It will try to parse this line as any of
|
|
185
|
+
# the lines that are defined by the current file format (see RequestLogAnalyazer::FileFormat).
|
|
186
|
+
# It will then combine these parsed line into requests using heuristics. These requests (see
|
|
187
|
+
# RequestLogAnalyzer::Request) will then be yielded for further processing in the pipeline.
|
|
188
|
+
#
|
|
189
|
+
# - RequestLogAnalyzer::LineDefinition#matches is called to test if a line matches a line definition of the file format.
|
|
190
|
+
# - update_current_request is used to combine parsed lines into requests using heuristics.
|
|
191
|
+
# - The method will yield progress updates if a progress handler is installed using progress=
|
|
192
|
+
# - The method will yield parse warnings if a warning handler is installed using warning=
|
|
193
|
+
#
|
|
194
|
+
# This is a Ruby 1.8 specific version that doesn't offer memory protection.
|
|
195
|
+
#
|
|
196
|
+
# <tt>io</tt>:: The IO instance to use as source
|
|
197
|
+
# <tt>options</tt>:: A hash of options that can be used by the parser.
|
|
198
|
+
def parse_io_18(io, options = {}, &block) # :yields: request
|
|
199
|
+
@line_divider = options[:line_divider] || line_divider
|
|
200
|
+
@current_lineno = 0
|
|
201
|
+
while line = io.gets(@line_divider)
|
|
202
|
+
@current_lineno += 1
|
|
203
|
+
@progress_handler.call(:progress, io.pos) if @progress_handler && @current_lineno % 255 == 0
|
|
204
|
+
parse_line(line, &block)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
warn(:unfinished_request_on_eof, "End of file reached, but last request was not completed!") unless @current_request.nil?
|
|
208
|
+
@current_lineno = nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
alias_method :parse_io, RUBY_VERSION.to_f < 1.9 ? :parse_io_18 : :parse_io_19
|
|
212
|
+
|
|
160
213
|
# Parses a single line using the current file format. If successful, use the parsed
|
|
161
214
|
# information to build a request
|
|
162
215
|
# <tt>line</tt>:: The line to parse
|
|
@@ -295,5 +348,4 @@ module RequestLogAnalyzer::Source
|
|
|
295
348
|
hash[:line_definition].footer
|
|
296
349
|
end
|
|
297
350
|
end
|
|
298
|
-
|
|
299
351
|
end
|
|
@@ -8,9 +8,6 @@
|
|
|
8
8
|
# - Currently, RequestLogAnalyzer::Source::LogParser is the only implemented source.
|
|
9
9
|
module RequestLogAnalyzer::Source
|
|
10
10
|
|
|
11
|
-
autoload :LogParser, 'request_log_analyzer/source/log_parser'
|
|
12
|
-
autoload :DatabaseLoader, 'request_log_analyzer/source/database_loader'
|
|
13
|
-
|
|
14
11
|
# The base Source class. All other sources should inherit from this class.
|
|
15
12
|
#
|
|
16
13
|
# A source implememtation should at least implement the each_request method, which should yield
|
|
@@ -65,4 +62,6 @@ module RequestLogAnalyzer::Source
|
|
|
65
62
|
end
|
|
66
63
|
|
|
67
64
|
end
|
|
68
|
-
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
require 'request_log_analyzer/source/log_parser'
|