request-log-analyzer 1.9.10 → 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 +4 -1
- data/.infinity_test +8 -0
- data/Gemfile +2 -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 +19 -28
- 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 +232 -0
- data/lib/request_log_analyzer/file_format/mysql.rb +5 -5
- data/lib/request_log_analyzer/file_format/rails3.rb +9 -2
- data/lib/request_log_analyzer/file_format/w3c.rb +56 -0
- data/lib/request_log_analyzer/file_format.rb +46 -15
- 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/request.rb +7 -0
- 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 +4 -4
- 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/spec_helper.rb +3 -2
- 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 +90 -161
- 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 +82 -0
- 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 +10 -21
- data/spec/unit/file_format/rack_format_spec.rb +49 -44
- data/spec/unit/file_format/rails3_format_spec.rb +46 -38
- data/spec/unit/file_format/rails_format_spec.rb +52 -68
- data/spec/unit/file_format/w3c_format_spec.rb +47 -0
- data/spec/unit/source/log_parser_spec.rb +1 -1
- metadata +12 -7
- 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/Gemfile
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'
|
|
@@ -30,20 +30,23 @@ module RequestLogAnalyzer::FileFormat
|
|
|
30
30
|
|
|
31
31
|
# A hash that defines how the log format directives should be parsed.
|
|
32
32
|
LOG_DIRECTIVES = {
|
|
33
|
-
'%' => { :regexp => '%', :captures => [] },
|
|
34
|
-
'h' => { :regexp =>
|
|
35
|
-
'a' => { :regexp => "(#{ip_address})", :captures => [{:name => :remote_ip, :type => :string}] },
|
|
36
|
-
'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] },
|
|
37
|
-
'c' => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] },
|
|
38
|
-
'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :musec}] },
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
|
|
33
|
+
'%' => { nil => { :regexp => '%', :captures => [] } },
|
|
34
|
+
'h' => { nil => { :regexp => "(#{hostname_or_ip_address})", :captures => [{:name => :remote_host, :type => :string}] } },
|
|
35
|
+
'a' => { nil => { :regexp => "(#{ip_address})", :captures => [{:name => :remote_ip, :type => :string}] } },
|
|
36
|
+
'b' => { nil => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] } },
|
|
37
|
+
'c' => { nil => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] } },
|
|
38
|
+
'D' => { nil => { :regexp => '(\d+|-)', :captures => [ {:name => :duration, :type => :duration, :unit => :musec }] },
|
|
39
|
+
'micro' => { :regexp => '(\d+|-)', :captures => [ {:name => :duration, :type => :duration, :unit => :musec }] },
|
|
40
|
+
'milli' => { :regexp => '(\d+|-)', :captures => [ {:name => :duration, :type => :duration, :unit => :msec }] }
|
|
41
|
+
},
|
|
42
|
+
'l' => { nil => { :regexp => '([\w-]+)', :captures => [{:name => :remote_logname, :type => :nillable_string}] } },
|
|
43
|
+
'T' => { nil => { :regexp => '((?:\d+(?:\.\d+))|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] } },
|
|
44
|
+
't' => { nil => { :regexp => "\\[(#{APACHE_TIMESTAMP})?\\]", :captures => [{:name => :timestamp, :type => :timestamp}] } },
|
|
45
|
+
's' => { nil => { :regexp => '(\d{3})', :captures => [{:name => :http_status, :type => :integer}] } },
|
|
46
|
+
'u' => { nil => { :regexp => '(\w+|-)', :captures => [{:name => :user, :type => :nillable_string}] } },
|
|
47
|
+
'U' => { nil => { :regexp => '(\/\S*)', :captures => [{:name => :path, :type => :string}] } },
|
|
48
|
+
'r' => { nil => { :regexp => '([A-Z]+) (\S+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
|
|
49
|
+
{:name => :path, :type => :path}, {:name => :http_version, :type => :string}]} },
|
|
47
50
|
'i' => { 'Referer' => { :regexp => '(\S+)', :captures => [{:name => :referer, :type => :nillable_string}] },
|
|
48
51
|
'User-agent' => { :regexp => '(.*)', :captures => [{:name => :user_agent, :type => :user_agent}] }
|
|
49
52
|
}
|
|
@@ -72,14 +75,13 @@ module RequestLogAnalyzer::FileFormat
|
|
|
72
75
|
|
|
73
76
|
if variable
|
|
74
77
|
# Check if we recognize the log directive
|
|
75
|
-
directive = LOG_DIRECTIVES[variable]
|
|
76
|
-
directive = directive[arg] if directive && arg
|
|
78
|
+
directive = LOG_DIRECTIVES[variable][arg] rescue nil
|
|
77
79
|
|
|
78
80
|
if directive
|
|
79
81
|
line_regexp << directive[:regexp] # Parse the value of the directive
|
|
80
82
|
captures += directive[:captures] # Add the directive's information to the captures
|
|
81
83
|
else
|
|
82
|
-
puts "%#{
|
|
84
|
+
puts "Apache log directive %#{arg}#{variable} is not yet supported by RLA, the field will be ignored."
|
|
83
85
|
line_regexp << '.*' # Just accept any input for this literal
|
|
84
86
|
end
|
|
85
87
|
end
|
|
@@ -126,22 +128,11 @@ module RequestLogAnalyzer::FileFormat
|
|
|
126
128
|
"#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
|
|
127
129
|
end
|
|
128
130
|
|
|
129
|
-
# This function can be overridden to rewrite the path for better categorization in the
|
|
130
|
-
# reports.
|
|
131
|
-
def convert_path(value, definition)
|
|
132
|
-
value
|
|
133
|
-
end
|
|
134
|
-
|
|
135
131
|
# This function can be overridden to simplify the user agent string for better
|
|
136
132
|
# categorization in the reports
|
|
137
133
|
def convert_user_agent(value, definition)
|
|
138
134
|
value # TODO
|
|
139
135
|
end
|
|
140
|
-
|
|
141
|
-
# Make sure that the string '-' is parsed as a nil value.
|
|
142
|
-
def convert_nillable_string(value, definition)
|
|
143
|
-
value == '-' ? nil : value
|
|
144
|
-
end
|
|
145
136
|
end
|
|
146
137
|
end
|
|
147
138
|
end
|
|
@@ -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)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
|
2
|
+
|
|
3
|
+
class Haproxy < RequestLogAnalyzer::FileFormat::Base
|
|
4
|
+
|
|
5
|
+
extend CommonRegularExpressions
|
|
6
|
+
|
|
7
|
+
# substitute version specific parts of the haproxy entry regexp.
|
|
8
|
+
def self.compose_regexp(millisecs, backends, counters, connections, queues)
|
|
9
|
+
%r{
|
|
10
|
+
(#{ip_address}):\d+\s # client_ip ':' client_port
|
|
11
|
+
\[(#{timestamp('%d/%b/%Y:%H:%M:%S')})#{millisecs}\]\s # '[' accept_date ']'
|
|
12
|
+
(\S+)\s # frontend_name
|
|
13
|
+
#{backends}
|
|
14
|
+
#{counters}
|
|
15
|
+
(\d+)\s # status_code
|
|
16
|
+
\+?(\d+)\s # bytes_read
|
|
17
|
+
(\S+)\s # captured_request_cookie
|
|
18
|
+
(\S+)\s # captured_response_cookie
|
|
19
|
+
(\w|-)(\w|-)(\w|-)(\w|-)\s # termination_state
|
|
20
|
+
#{connections}
|
|
21
|
+
#{queues}
|
|
22
|
+
(\S*)\s? # captured_request_headers
|
|
23
|
+
(\S*)\s? # captured_response_headers
|
|
24
|
+
"([^"]*)" # '"' http_request '"'
|
|
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
|
+
)
|
|
43
|
+
|
|
44
|
+
line.capture(:client_ip).as(:string)
|
|
45
|
+
line.capture(:timestamp).as(:timestamp)
|
|
46
|
+
line.capture(:frontend_name).as(:string)
|
|
47
|
+
line.capture(:backend_name).as(:string)
|
|
48
|
+
line.capture(:server_name).as(:string)
|
|
49
|
+
line.capture(:tq).as(:nillable_duration, :unit => :msec)
|
|
50
|
+
line.capture(:tw).as(:nillable_duration, :unit => :msec)
|
|
51
|
+
line.capture(:tc).as(:nillable_duration, :unit => :msec)
|
|
52
|
+
line.capture(:tr).as(:nillable_duration, :unit => :msec)
|
|
53
|
+
line.capture(:tt).as(:duration, :unit => :msec)
|
|
54
|
+
line.capture(:status_code).as(:integer)
|
|
55
|
+
line.capture(:bytes_read).as(:traffic, :unit => :byte)
|
|
56
|
+
line.capture(:captured_request_cookie).as(:nillable_string)
|
|
57
|
+
line.capture(:captured_response_cookie).as(:nillable_string)
|
|
58
|
+
line.capture(:termination_event_code).as(:nillable_string)
|
|
59
|
+
line.capture(:terminated_session_state).as(:nillable_string)
|
|
60
|
+
line.capture(:clientside_persistence_cookie).as(:nillable_string)
|
|
61
|
+
line.capture(:serverside_persistence_cookie).as(:nillable_string)
|
|
62
|
+
line.capture(:actconn).as(:integer)
|
|
63
|
+
line.capture(:feconn).as(:integer)
|
|
64
|
+
line.capture(:beconn).as(:integer)
|
|
65
|
+
line.capture(:srv_conn).as(:integer)
|
|
66
|
+
line.capture(:retries).as(:integer)
|
|
67
|
+
line.capture(:srv_queue).as(:integer)
|
|
68
|
+
line.capture(:backend_queue).as(:integer)
|
|
69
|
+
line.capture(:captured_request_headers).as(:nillable_string)
|
|
70
|
+
line.capture(:captured_response_headers).as(:nillable_string)
|
|
71
|
+
line.capture(:http_request).as(:nillable_string)
|
|
72
|
+
end
|
|
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
|
+
|
|
154
|
+
# Define the summary report
|
|
155
|
+
report do |analyze|
|
|
156
|
+
analyze.hourly_spread :field => :timestamp
|
|
157
|
+
|
|
158
|
+
analyze.frequency :client_ip,
|
|
159
|
+
:title => "Hits per IP"
|
|
160
|
+
|
|
161
|
+
analyze.frequency :frontend_name,
|
|
162
|
+
:title => "Hits per frontend service"
|
|
163
|
+
|
|
164
|
+
analyze.frequency :backend_name,
|
|
165
|
+
:title => "Hits per backend service"
|
|
166
|
+
|
|
167
|
+
analyze.frequency :server_name,
|
|
168
|
+
:title => "Hits per backend server"
|
|
169
|
+
|
|
170
|
+
analyze.frequency :status_code,
|
|
171
|
+
:title => "HTTP response code frequency"
|
|
172
|
+
|
|
173
|
+
analyze.frequency :http_request,
|
|
174
|
+
:title => "Most popular requests"
|
|
175
|
+
|
|
176
|
+
analyze.frequency :http_request,
|
|
177
|
+
:title => "Most frequent HTTP 40x errors",
|
|
178
|
+
:category => lambda { |r| "#{r[:http_request]}"},
|
|
179
|
+
:if => lambda { |r| r[:status_code] >= 400 and r[:status_code] <= 417 }
|
|
180
|
+
|
|
181
|
+
analyze.frequency :http_request,
|
|
182
|
+
:title => "Most frequent HTTP 50x errors",
|
|
183
|
+
:category => lambda { |r| "#{r[:http_request]}"},
|
|
184
|
+
:if => lambda { |r| r[:status_code] >= 500 and r[:status_code] <= 505 }
|
|
185
|
+
|
|
186
|
+
analyze.traffic :bytes_read,
|
|
187
|
+
:title => "Traffic per frontend service",
|
|
188
|
+
:category => lambda { |r| "#{r[:frontend_name]}"}
|
|
189
|
+
|
|
190
|
+
analyze.traffic :bytes_read,
|
|
191
|
+
:title => "Traffic per backend service",
|
|
192
|
+
:category => lambda { |r| "#{r[:backend_name]}"}
|
|
193
|
+
|
|
194
|
+
analyze.traffic :bytes_read,
|
|
195
|
+
:title => "Traffic per backend server",
|
|
196
|
+
:category => lambda { |r| "#{r[:server_name]}"}
|
|
197
|
+
|
|
198
|
+
analyze.duration :tr,
|
|
199
|
+
:title => "Time waiting for backend response",
|
|
200
|
+
:category => lambda { |r| "#{r[:http_request]}"}
|
|
201
|
+
|
|
202
|
+
analyze.duration :tt,
|
|
203
|
+
:title => "Total time spent on request",
|
|
204
|
+
:category => lambda { |r| "#{r[:http_request]}"}
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Define a custom Request class for the HAProxy file format to speed up
|
|
208
|
+
# timestamp handling. Shamelessly copied from apache.rb
|
|
209
|
+
class Request < RequestLogAnalyzer::Request
|
|
210
|
+
|
|
211
|
+
MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
|
|
212
|
+
'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
|
|
213
|
+
|
|
214
|
+
# Do not use DateTime.parse, but parse the timestamp ourselves to return
|
|
215
|
+
# a integer to speed up parsing.
|
|
216
|
+
def convert_timestamp(value, definition)
|
|
217
|
+
"#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Make sure that the strings '-' or '{}' or '' are parsed as a nil value.
|
|
221
|
+
def convert_nillable_string(value, definition)
|
|
222
|
+
value =~ /-|\{\}|^$/ ? nil : value
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Make sure that -1 is parsed as a nil value.
|
|
226
|
+
def convert_nillable_duration(value, definition)
|
|
227
|
+
value == '-1' ? nil : convert_duration(value, definition)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -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
|
|
@@ -36,7 +43,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
36
43
|
line_definition :completed do |line|
|
|
37
44
|
line.footer = true
|
|
38
45
|
line.teaser = /Completed /
|
|
39
|
-
line.regexp = /Completed (\d+)? .*in (\d+(?:\.\d+)?)ms(?:[^\(]*\(Views: (\d+(?:\.\d+)?)ms .* ActiveRecord: (\d+(?:\.\d+)?)ms
|
|
46
|
+
line.regexp = /Completed (\d+)? .*in (\d+(?:\.\d+)?)ms(?:[^\(]*\(Views: (\d+(?:\.\d+)?)ms .* ActiveRecord: (\d+(?:\.\d+)?)ms.*\))?/
|
|
40
47
|
|
|
41
48
|
line.capture(:status).as(:integer)
|
|
42
49
|
line.capture(:duration).as(:duration, :unit => :msec)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
|
2
|
+
|
|
3
|
+
# FileFormat for W3C access logs.
|
|
4
|
+
class W3c < Base
|
|
5
|
+
|
|
6
|
+
extend CommonRegularExpressions
|
|
7
|
+
|
|
8
|
+
line_definition :access do |line|
|
|
9
|
+
line.header = true
|
|
10
|
+
line.footer = true
|
|
11
|
+
line.regexp = /^(#{timestamp('%Y-%m-%d %H:%M:%S')}) (#{ip_address}) (.*) (#{ip_address}) (\d+) (\w+) ([\w|\/|.]+) \- (\d+) (\d+) (\d+) (\d+) (.*) (\S+)/
|
|
12
|
+
|
|
13
|
+
line.capture(:timestamp).as(:timestamp)
|
|
14
|
+
line.capture(:remote_ip)
|
|
15
|
+
line.capture(:username).as(:nillable_string)
|
|
16
|
+
line.capture(:local_ip)
|
|
17
|
+
line.capture(:port).as(:integer)
|
|
18
|
+
line.capture(:method)
|
|
19
|
+
line.capture(:path).as(:path)
|
|
20
|
+
line.capture(:http_status).as(:integer)
|
|
21
|
+
line.capture(:bytes_sent).as(:traffic, :unit => :byte)
|
|
22
|
+
line.capture(:bytes_received).as(:traffic, :unit => :byte)
|
|
23
|
+
line.capture(:duration).as(:duration, :unit => :msec)
|
|
24
|
+
line.capture(:user_agent)
|
|
25
|
+
line.capture(:referer)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
report do |analyze|
|
|
29
|
+
analyze.timespan
|
|
30
|
+
analyze.hourly_spread
|
|
31
|
+
|
|
32
|
+
analyze.frequency :category => :http_method, :title => "HTTP methods"
|
|
33
|
+
analyze.frequency :category => :http_status, :title => "HTTP statuses"
|
|
34
|
+
|
|
35
|
+
analyze.frequency :category => :path, :title => "Most popular URIs"
|
|
36
|
+
|
|
37
|
+
analyze.frequency :category => :user_agent, :title => "User agents"
|
|
38
|
+
analyze.frequency :category => :referer, :title => "Referers"
|
|
39
|
+
|
|
40
|
+
analyze.duration :duration => :duration, :category => :path, :title => 'Request duration'
|
|
41
|
+
analyze.traffic :traffic => :bytes_sent, :category => :path, :title => 'Traffic out'
|
|
42
|
+
analyze.traffic :traffic => :bytes_received, :category => :path, :title => 'Traffic in'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Request < RequestLogAnalyzer::Request
|
|
46
|
+
# Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
|
|
47
|
+
# to speed up parsing.
|
|
48
|
+
def convert_timestamp(value, definition)
|
|
49
|
+
"#{value[0,4]}#{value[5,2]}#{value[8,2]}#{value[11,2]}#{value[14,2]}#{value[17,2]}".to_i
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|