request-log-analyzer 1.0.2 → 1.6.3
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 +10 -0
- data/DESIGN.rdoc +41 -0
- data/LICENSE +4 -4
- data/README.rdoc +38 -0
- data/Rakefile +6 -3
- data/bin/request-log-analyzer +70 -72
- data/lib/cli/command_line_arguments.rb +53 -53
- data/lib/cli/database_console.rb +26 -0
- data/lib/cli/database_console_init.rb +43 -0
- data/lib/cli/progressbar.rb +166 -189
- data/lib/cli/tools.rb +49 -0
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
- data/lib/request_log_analyzer/aggregator/echo.rb +17 -12
- data/lib/request_log_analyzer/aggregator/summarizer.rb +101 -63
- data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +17 -13
- data/lib/request_log_analyzer/controller.rb +251 -98
- data/lib/request_log_analyzer/database/base.rb +114 -0
- data/lib/request_log_analyzer/database/connection.rb +38 -0
- 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 +102 -0
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +74 -0
- data/lib/request_log_analyzer/file_format/apache.rb +147 -0
- data/lib/request_log_analyzer/file_format/delayed_job.rb +55 -0
- data/lib/request_log_analyzer/file_format/merb.rb +65 -29
- data/lib/request_log_analyzer/file_format/mysql.rb +101 -0
- data/lib/request_log_analyzer/file_format/postgresql.rb +68 -0
- data/lib/request_log_analyzer/file_format/rack.rb +9 -0
- data/lib/request_log_analyzer/file_format/rails.rb +164 -78
- data/lib/request_log_analyzer/file_format/rails3.rb +86 -0
- data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
- data/lib/request_log_analyzer/file_format.rb +252 -58
- data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
- data/lib/request_log_analyzer/filter/field.rb +19 -13
- data/lib/request_log_analyzer/filter/timespan.rb +25 -12
- data/lib/request_log_analyzer/filter.rb +30 -0
- data/lib/request_log_analyzer/line_definition.rb +69 -96
- data/lib/request_log_analyzer/log_processor.rb +31 -53
- data/lib/request_log_analyzer/mailer.rb +65 -0
- data/lib/request_log_analyzer/output/fancy_html.rb +49 -0
- data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
- data/lib/request_log_analyzer/output/html.rb +187 -0
- data/lib/request_log_analyzer/output.rb +117 -0
- data/lib/request_log_analyzer/request.rb +125 -40
- data/lib/request_log_analyzer/source/database_loader.rb +87 -0
- data/lib/request_log_analyzer/source/log_parser.rb +297 -0
- data/lib/request_log_analyzer/source.rb +72 -0
- data/lib/request_log_analyzer/tracker/duration.rb +28 -64
- data/lib/request_log_analyzer/tracker/frequency.rb +108 -0
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +76 -49
- data/lib/request_log_analyzer/tracker/numeric_value.rb +223 -0
- data/lib/request_log_analyzer/tracker/timespan.rb +54 -27
- data/lib/request_log_analyzer/tracker/traffic.rb +40 -0
- data/lib/request_log_analyzer/tracker.rb +101 -0
- data/lib/request_log_analyzer.rb +43 -13
- data/request-log-analyzer.gemspec +41 -0
- data/spec/database.yml +23 -0
- data/spec/fixtures/apache_combined.log +5 -0
- data/spec/fixtures/apache_common.log +10 -0
- data/spec/fixtures/decompression.log +12 -0
- data/spec/fixtures/decompression.log.bz2 +0 -0
- data/spec/fixtures/decompression.log.gz +0 -0
- data/spec/fixtures/decompression.log.zip +0 -0
- data/spec/fixtures/decompression.tar.gz +0 -0
- data/spec/fixtures/decompression.tgz +0 -0
- data/spec/fixtures/header_and_footer.log +6 -0
- data/spec/fixtures/merb_prefixed.log +9 -0
- data/spec/fixtures/mysql_slow_query.log +110 -0
- data/spec/fixtures/postgresql.log +2980 -0
- data/spec/fixtures/rails.db +0 -0
- data/spec/fixtures/rails_22.log +1 -1
- data/spec/fixtures/sinatra.log +99 -0
- data/spec/integration/command_line_usage_spec.rb +84 -0
- data/spec/integration/mailer_spec.rb +179 -0
- data/spec/integration/munin_plugins_rails_spec.rb +58 -0
- data/spec/integration/scout_spec.rb +152 -0
- data/spec/lib/helpers.rb +72 -0
- data/spec/lib/macros.rb +18 -0
- data/spec/lib/matchers.rb +77 -0
- data/spec/lib/mocks.rb +77 -0
- data/spec/lib/testing_format.rb +46 -0
- data/spec/spec_helper.rb +16 -59
- data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
- data/spec/unit/aggregator/summarizer_spec.rb +26 -0
- data/spec/unit/controller/controller_spec.rb +41 -0
- data/spec/unit/controller/log_processor_spec.rb +18 -0
- data/spec/unit/database/base_class_spec.rb +183 -0
- data/spec/unit/database/connection_spec.rb +34 -0
- data/spec/unit/database/database_spec.rb +133 -0
- data/spec/unit/file_format/amazon_s3_format_spec.rb +67 -0
- data/spec/unit/file_format/apache_format_spec.rb +203 -0
- data/spec/unit/file_format/common_regular_expressions_spec.rb +53 -0
- data/spec/unit/file_format/delayed_job_format_spec.rb +83 -0
- data/spec/unit/file_format/file_format_api_spec.rb +69 -0
- data/spec/unit/file_format/format_autodetection_spec.rb +40 -0
- data/spec/unit/file_format/line_definition_spec.rb +75 -0
- data/spec/unit/file_format/merb_format_spec.rb +52 -0
- data/spec/unit/file_format/mysql_format_spec.rb +154 -0
- data/spec/unit/file_format/postgresql_format_spec.rb +65 -0
- data/spec/unit/file_format/rack_format_spec.rb +50 -0
- data/spec/unit/file_format/rails3_format_spec.rb +120 -0
- data/spec/unit/file_format/rails_format_spec.rb +180 -0
- data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
- data/spec/unit/filter/field_filter_spec.rb +66 -0
- data/spec/unit/filter/filter_spec.rb +17 -0
- data/spec/unit/filter/timespan_filter_spec.rb +58 -0
- data/spec/unit/mailer_spec.rb +42 -0
- data/spec/unit/request_spec.rb +111 -0
- data/spec/unit/source/log_parser_spec.rb +119 -0
- data/spec/unit/tracker/duration_tracker_spec.rb +49 -0
- data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
- data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
- data/spec/unit/tracker/numeric_value_tracker_spec.rb +166 -0
- data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
- data/spec/unit/tracker/tracker_api_spec.rb +125 -0
- data/spec/unit/tracker/traffic_tracker_spec.rb +28 -0
- data/tasks/github-gem.rake +290 -139
- data/tasks/request_log_analyzer.rake +23 -7
- metadata +221 -94
- data/DESIGN +0 -14
- data/HACKING +0 -7
- data/README.textile +0 -36
- data/lib/cli/bashcolorizer.rb +0 -60
- data/lib/request_log_analyzer/aggregator/database.rb +0 -148
- data/lib/request_log_analyzer/filter/base.rb +0 -29
- data/lib/request_log_analyzer/log_parser.rb +0 -173
- data/lib/request_log_analyzer/source/base.rb +0 -42
- data/lib/request_log_analyzer/source/log_file.rb +0 -170
- data/lib/request_log_analyzer/tracker/base.rb +0 -54
- data/lib/request_log_analyzer/tracker/category.rb +0 -71
- data/spec/controller_spec.rb +0 -40
- data/spec/database_inserter_spec.rb +0 -101
- data/spec/file_format_spec.rb +0 -78
- data/spec/file_formats/spec_format.rb +0 -26
- data/spec/filter_spec.rb +0 -137
- data/spec/line_definition_spec.rb +0 -124
- data/spec/log_parser_spec.rb +0 -68
- data/spec/log_processor_spec.rb +0 -57
- data/spec/merb_format_spec.rb +0 -38
- data/spec/rails_format_spec.rb +0 -76
- data/spec/request_spec.rb +0 -72
- data/spec/summarizer_spec.rb +0 -9
- data/tasks/rspec.rake +0 -6
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
module RequestLogAnalyzer
|
|
2
|
-
|
|
3
|
-
module Anonymizers
|
|
4
|
-
def anonymizer_for_ip(value, capture_definition)
|
|
5
|
-
'127.0.0.1'
|
|
6
|
-
end
|
|
7
2
|
|
|
8
|
-
def anonymizer_for_url(value, capture_definition)
|
|
9
|
-
value.sub(/^https?\:\/\/[A-z0-9\.-]+\//, "http://example.com/")
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
3
|
# The line definition class is used to specify what lines should be parsed from the log file.
|
|
14
4
|
# It contains functionality to match a line against the definition and parse the information
|
|
15
5
|
# from this line. This is used by the LogParser class when parsing a log file..
|
|
16
6
|
class LineDefinition
|
|
17
7
|
|
|
18
|
-
include RequestLogAnalyzer::Anonymizers
|
|
19
|
-
|
|
20
8
|
class Definer
|
|
21
|
-
|
|
9
|
+
|
|
22
10
|
attr_accessor :line_definitions
|
|
23
|
-
|
|
11
|
+
|
|
24
12
|
def initialize
|
|
25
13
|
@line_definitions = {}
|
|
26
14
|
end
|
|
27
|
-
|
|
15
|
+
|
|
16
|
+
def initialize_copy(other)
|
|
17
|
+
@line_definitions = other.line_definitions.dup
|
|
18
|
+
end
|
|
19
|
+
|
|
28
20
|
def method_missing(name, *args, &block)
|
|
29
21
|
if block_given?
|
|
30
22
|
@line_definitions[name] = RequestLogAnalyzer::LineDefinition.define(name, &block)
|
|
@@ -34,126 +26,107 @@ module RequestLogAnalyzer
|
|
|
34
26
|
end
|
|
35
27
|
end
|
|
36
28
|
|
|
29
|
+
class CaptureDefiner
|
|
30
|
+
attr_accessor :capture_hash
|
|
31
|
+
|
|
32
|
+
def initialize(hash)
|
|
33
|
+
@capture_hash = hash
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def as(type, type_options = {})
|
|
37
|
+
@capture_hash.merge!(type_options.merge(:type => type))
|
|
38
|
+
return self
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
37
42
|
attr_reader :name
|
|
38
43
|
attr_accessor :teaser, :regexp, :captures
|
|
39
44
|
attr_accessor :header, :footer
|
|
40
|
-
|
|
45
|
+
|
|
46
|
+
alias_method :header?, :header
|
|
47
|
+
alias_method :footer?, :footer
|
|
48
|
+
|
|
41
49
|
# Initializes the LineDefinition instance with a hash containing the different elements of
|
|
42
50
|
# the definition.
|
|
43
51
|
def initialize(name, definition = {})
|
|
44
52
|
@name = name
|
|
45
53
|
@captures = []
|
|
54
|
+
@teaser = nil
|
|
46
55
|
definition.each { |key, value| self.send("#{key.to_s}=".to_sym, value) }
|
|
47
56
|
end
|
|
48
|
-
|
|
57
|
+
|
|
49
58
|
def self.define(name, &block)
|
|
50
59
|
definition = self.new(name)
|
|
51
60
|
yield(definition) if block_given?
|
|
52
61
|
return definition
|
|
53
62
|
end
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
when :float; value.to_f
|
|
60
|
-
when :decimal; value.to_f
|
|
61
|
-
when :symbol; value.to_sym
|
|
62
|
-
when :sec; value.to_f
|
|
63
|
-
when :msec; value.to_f / 1000
|
|
64
|
-
when :timestamp; value.gsub(/[^0-9]/,'')[0..13].to_i # Retrieve with: DateTime.parse(value, '%Y%m%d%H%M%S')
|
|
65
|
-
else value
|
|
66
|
-
end
|
|
64
|
+
def capture(name)
|
|
65
|
+
new_capture_hash = { :name => name, :type => :string}
|
|
66
|
+
captures << new_capture_hash
|
|
67
|
+
CaptureDefiner.new(new_capture_hash)
|
|
67
68
|
end
|
|
68
|
-
|
|
69
|
-
# Checks whether a given line matches this definition.
|
|
69
|
+
|
|
70
|
+
# Checks whether a given line matches this definition.
|
|
70
71
|
# It will return false if a line does not match. If the line matches, a hash is returned
|
|
71
72
|
# with all the fields parsed from that line as content.
|
|
72
73
|
# If the line definition has a teaser-check, a :teaser_check_failed warning will be emitted
|
|
73
74
|
# if this teaser-check is passed, but the full regular exprssion does not ,atch.
|
|
74
|
-
def matches(line,
|
|
75
|
+
def matches(line, &warning_handler)
|
|
75
76
|
if @teaser.nil? || @teaser =~ line
|
|
76
77
|
if match_data = line.match(@regexp)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
captures.each_with_index do |capture, index|
|
|
80
|
-
next if capture == :ignore
|
|
81
|
-
|
|
82
|
-
if match_data.captures[index]
|
|
83
|
-
request_info[capture[:name]] = convert_value(match_data.captures[index], capture[:type])
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
end
|
|
87
|
-
return request_info
|
|
78
|
+
return { :line_definition => self, :captures => match_data.captures}
|
|
88
79
|
else
|
|
89
|
-
if @teaser &&
|
|
90
|
-
|
|
80
|
+
if @teaser && warning_handler
|
|
81
|
+
warning_handler.call(:teaser_check_failed, "Teaser matched for #{name.inspect}, but full line did not:\n#{line.inspect}")
|
|
91
82
|
end
|
|
92
83
|
return false
|
|
93
84
|
end
|
|
94
85
|
else
|
|
95
86
|
return false
|
|
96
87
|
end
|
|
88
|
+
rescue
|
|
89
|
+
return false
|
|
97
90
|
end
|
|
98
|
-
|
|
91
|
+
|
|
99
92
|
alias :=~ :matches
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
|
|
94
|
+
# matches the line and converts the captured values using the request's
|
|
95
|
+
# convert_value function.
|
|
96
|
+
def match_for(line, request, &warning_handler)
|
|
97
|
+
if match_info = matches(line, &warning_handler)
|
|
98
|
+
convert_captured_values(match_info[:captures], request)
|
|
104
99
|
else
|
|
105
|
-
|
|
106
|
-
when nil; value
|
|
107
|
-
when false; value
|
|
108
|
-
when true; '***'
|
|
109
|
-
when :slightly; anonymize_slightly(value, capture_definition)
|
|
110
|
-
else
|
|
111
|
-
method_name = "anonymizer_for_#{capture_definition[:anonymize]}".to_sym
|
|
112
|
-
self.respond_to?(method_name) ? self.send(method_name, value, capture_definition) : '***'
|
|
113
|
-
end
|
|
100
|
+
false
|
|
114
101
|
end
|
|
115
102
|
end
|
|
116
|
-
|
|
117
|
-
def anonymize_slightly(value, capture_definition)
|
|
118
|
-
case capture_definition[:type]
|
|
119
|
-
when :integer
|
|
120
|
-
(value.to_i * (0.8 + rand * 0.4)).to_i
|
|
121
|
-
when :double
|
|
122
|
-
(value.to_f * (0.8 + rand * 0.4)).to_f
|
|
123
|
-
when :msec
|
|
124
|
-
(value.to_i * (0.8 + rand * 0.4)).to_i
|
|
125
|
-
when :sec
|
|
126
|
-
(value.to_f * (0.8 + rand * 0.4)).to_f
|
|
127
|
-
when :timestamp
|
|
128
|
-
(DateTime.parse(value) + (rand(100) - 50)).to_s
|
|
129
|
-
else
|
|
130
|
-
puts "Cannot anonymize #{capture_definition[:type].inspect} slightly, using ***"
|
|
131
|
-
'***'
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
103
|
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
104
|
+
# Updates a captures hash using the converters specified in the request
|
|
105
|
+
# and handle the :provides option in the line definition.
|
|
106
|
+
def convert_captured_values(values, request)
|
|
107
|
+
value_hash = {}
|
|
108
|
+
captures.each_with_index do |capture, index|
|
|
109
|
+
|
|
110
|
+
# convert the value using the request convert_value function
|
|
111
|
+
converted = request.convert_value(values[index], capture)
|
|
112
|
+
value_hash[capture[:name]] ||= converted
|
|
113
|
+
|
|
114
|
+
# Add items directly to the resulting hash from the converted value
|
|
115
|
+
# if it is a hash and they are set in the :provides hash for this line definition
|
|
116
|
+
if converted.kind_of?(Hash) && capture[:provides].kind_of?(Hash)
|
|
117
|
+
capture[:provides].each do |name, type|
|
|
118
|
+
value_hash[name] ||= request.convert_value(converted[name], { :type => type })
|
|
146
119
|
end
|
|
147
|
-
line
|
|
148
|
-
elsif self.teaser.nil?
|
|
149
|
-
nil
|
|
150
|
-
else
|
|
151
|
-
options[:discard_teaser_lines] ? "" : line
|
|
152
120
|
end
|
|
153
|
-
else
|
|
154
|
-
nil
|
|
155
121
|
end
|
|
122
|
+
return value_hash
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns true if this line captures values of the given name
|
|
126
|
+
def captures?(name)
|
|
127
|
+
captures.any? { |c| c[:name] == name }
|
|
156
128
|
end
|
|
129
|
+
|
|
157
130
|
end
|
|
158
|
-
|
|
131
|
+
|
|
159
132
|
end
|
|
@@ -1,58 +1,53 @@
|
|
|
1
1
|
module RequestLogAnalyzer
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# The Logprocessor class is used to perform simple processing actions over log files.
|
|
4
|
-
# It will go over the log file/stream line by line, pass the line to a processor and
|
|
5
|
-
# write the result back to the output file or stream. The processor can alter the
|
|
4
|
+
# It will go over the log file/stream line by line, pass the line to a processor and
|
|
5
|
+
# write the result back to the output file or stream. The processor can alter the
|
|
6
6
|
# contents of the line, remain it intact or remove it altogether, based on the current
|
|
7
7
|
# file format
|
|
8
8
|
#
|
|
9
|
-
# Currently,
|
|
10
|
-
# * :strip will remove all irrelevent lines (according to the file format) from the
|
|
9
|
+
# Currently, one processors is supported:
|
|
10
|
+
# * :strip will remove all irrelevent lines (according to the file format) from the
|
|
11
11
|
# sources. A compact, information packed log will remain/.
|
|
12
|
-
# * :anonymize will anonymize sensitive information from the lines according to the
|
|
13
|
-
# anonymization rules in the file format. The result can be passed to third parties
|
|
14
|
-
# without privacy concerns.
|
|
15
12
|
#
|
|
16
13
|
class LogProcessor
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
attr_reader :mode, :options, :sources
|
|
14
|
+
|
|
15
|
+
attr_reader :mode, :options, :sources, :file_format
|
|
21
16
|
attr_accessor :output_file
|
|
22
|
-
|
|
17
|
+
|
|
23
18
|
# Builds a logprocessor instance from the arguments given on the command line
|
|
24
|
-
# <tt>command</tt> The command hat was used to start the log processor. This
|
|
25
|
-
#
|
|
19
|
+
# <tt>command</tt> The command hat was used to start the log processor. This will set the
|
|
20
|
+
# processing mode. Currently, only :strip is supported.
|
|
26
21
|
# <tt>arguments</tt> The parsed command line arguments (a CommandLine::Arguments instance)
|
|
27
22
|
def self.build(command, arguments)
|
|
28
|
-
|
|
29
|
-
options = {
|
|
30
|
-
:discard_teaser_lines => arguments[:discard_teaser_lines],
|
|
31
|
-
:keep_junk_lines => arguments[:keep_junk_lines],
|
|
23
|
+
|
|
24
|
+
options = {
|
|
25
|
+
:discard_teaser_lines => arguments[:discard_teaser_lines],
|
|
26
|
+
:keep_junk_lines => arguments[:keep_junk_lines],
|
|
32
27
|
}
|
|
33
|
-
|
|
28
|
+
|
|
34
29
|
log_processor = RequestLogAnalyzer::LogProcessor.new(arguments[:format].to_sym, command, options)
|
|
35
30
|
log_processor.output_file = arguments[:output] if arguments[:output]
|
|
36
31
|
|
|
37
32
|
arguments.parameters.each do |input|
|
|
38
33
|
log_processor.sources << input
|
|
39
34
|
end
|
|
40
|
-
|
|
35
|
+
|
|
41
36
|
return log_processor
|
|
42
37
|
end
|
|
43
|
-
|
|
38
|
+
|
|
44
39
|
# Initializes a new LogProcessor instance.
|
|
45
40
|
# <tt>format</tt> The file format to use (e.g. :rails).
|
|
46
|
-
# <tt>mode</tt> The processing mode
|
|
41
|
+
# <tt>mode</tt> The processing mode
|
|
47
42
|
# <tt>options</tt> A hash with options to take into account
|
|
48
43
|
def initialize(format, mode, options = {})
|
|
49
|
-
@options
|
|
50
|
-
@mode
|
|
51
|
-
@sources
|
|
44
|
+
@options = options
|
|
45
|
+
@mode = mode
|
|
46
|
+
@sources = []
|
|
47
|
+
@file_format = format
|
|
52
48
|
$output_file = nil
|
|
53
|
-
self.register_file_format(format)
|
|
54
49
|
end
|
|
55
|
-
|
|
50
|
+
|
|
56
51
|
# Processes input files by opening it and sending the filestream to <code>process_io</code>,
|
|
57
52
|
# in which the actual processing is performed.
|
|
58
53
|
# <tt>file</tt> The file to process
|
|
@@ -61,15 +56,14 @@ module RequestLogAnalyzer
|
|
|
61
56
|
end
|
|
62
57
|
|
|
63
58
|
# Processes an input stream by iteration over each line and processing it according to
|
|
64
|
-
# the current operation mode
|
|
59
|
+
# the current operation mode
|
|
65
60
|
# <tt>io</tt> The IO instance to process.
|
|
66
61
|
def process_io(io)
|
|
67
62
|
case mode
|
|
68
63
|
when :strip; io.each_line { |line| @output << strip_line(line) }
|
|
69
|
-
when :anonymize; io.each_line { |line| @output << anonymize_line(line) }
|
|
70
64
|
end
|
|
71
65
|
end
|
|
72
|
-
|
|
66
|
+
|
|
73
67
|
# Returns the line itself if the string matches any of the line definitions. If no match is
|
|
74
68
|
# found, an empty line is returned, which will strip the line from the output.
|
|
75
69
|
# <tt>line</tt> The line to strip
|
|
@@ -77,42 +71,26 @@ module RequestLogAnalyzer
|
|
|
77
71
|
file_format.line_definitions.any? { |name, definition| definition =~ line } ? line : ""
|
|
78
72
|
end
|
|
79
73
|
|
|
80
|
-
# Returns an anonymized version of the provided line. This can be a copy of the line it self,
|
|
81
|
-
# an empty string or a string in which some substrings are substituted for anonymized values.
|
|
82
|
-
# <tt>line</tt> The line to anonymize
|
|
83
|
-
def anonymize_line(line)
|
|
84
|
-
anonymized_line = nil
|
|
85
|
-
file_format.line_definitions.detect { |name, definition| anonymized_line = definition.anonymize(line, options) }
|
|
86
|
-
|
|
87
|
-
if anonymized_line
|
|
88
|
-
return anonymized_line
|
|
89
|
-
elsif options[:keep_junk_lines]
|
|
90
|
-
return line
|
|
91
|
-
else
|
|
92
|
-
return ""
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
74
|
# Runs the log processing by setting up the output stream and iterating over all the
|
|
97
|
-
# input sources. Input sources can either be filenames (String instances) or IO streams
|
|
75
|
+
# input sources. Input sources can either be filenames (String instances) or IO streams
|
|
98
76
|
# (IO instances). The strings "-" and "STDIN" will be substituted for the $stdin variable.
|
|
99
77
|
def run!
|
|
100
78
|
if @output_file.nil?
|
|
101
|
-
@output = $stdout
|
|
79
|
+
@output = $stdout
|
|
102
80
|
else
|
|
103
|
-
@output = File.new(@output_file, 'a')
|
|
81
|
+
@output = File.new(@output_file, 'a')
|
|
104
82
|
end
|
|
105
|
-
|
|
83
|
+
|
|
106
84
|
@sources.each do |source|
|
|
107
85
|
if source.kind_of?(String) && File.exist?(source)
|
|
108
86
|
process_file(source)
|
|
109
87
|
elsif source.kind_of?(IO)
|
|
110
88
|
process_io(source)
|
|
111
89
|
elsif ['-', 'STDIN'].include?(source)
|
|
112
|
-
process_io($stdin)
|
|
90
|
+
process_io($stdin)
|
|
113
91
|
end
|
|
114
92
|
end
|
|
115
|
-
|
|
93
|
+
|
|
116
94
|
ensure
|
|
117
95
|
@output.close if @output.kind_of?(File)
|
|
118
96
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module RequestLogAnalyzer
|
|
2
|
+
|
|
3
|
+
# Mail report to a specified emailaddress
|
|
4
|
+
class Mailer
|
|
5
|
+
|
|
6
|
+
attr_accessor :data, :to, :host, :port
|
|
7
|
+
|
|
8
|
+
# Initialize a mailer
|
|
9
|
+
# <tt>to</tt> to email address to mail to
|
|
10
|
+
# <tt>host</tt> the mailer host (defaults to localhost)
|
|
11
|
+
# <tt>options</tt> Specific style options
|
|
12
|
+
#
|
|
13
|
+
# Options
|
|
14
|
+
# * <tt>:debug</tt> Do not actually mail
|
|
15
|
+
# * <tt>:from_alias</tt> The from alias
|
|
16
|
+
# * <tt>:to_alias</tt> The to alias
|
|
17
|
+
# * <tt>:subject</tt> The message subject
|
|
18
|
+
def initialize(to, host = 'localhost', options = {})
|
|
19
|
+
require 'net/smtp'
|
|
20
|
+
@to = to
|
|
21
|
+
@host = host
|
|
22
|
+
|
|
23
|
+
@port = 25
|
|
24
|
+
@options = options
|
|
25
|
+
@host, @port = host.split(':') if @host.include?(':')
|
|
26
|
+
@data = []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Send all data in @data to the email address used during initialization.
|
|
30
|
+
# Returns array containg [message_data, from_email_address, to_email_address] of sent email.
|
|
31
|
+
def mail
|
|
32
|
+
from = @options[:from] || 'contact@railsdoctors.com'
|
|
33
|
+
from_alias = @options[:from_alias] || 'Request-log-analyzer reporter'
|
|
34
|
+
to_alias = @options[:to_alias] || to
|
|
35
|
+
subject = @options[:subject] || "Request log analyzer report - generated on #{Time.now.to_s}"
|
|
36
|
+
content_type = ""
|
|
37
|
+
content_type = 'Content-Type: text/html; charset="ISO-8859-1";' if @data.map{|l| l.include?('html')}.include?(true)
|
|
38
|
+
msg = <<END_OF_MESSAGE
|
|
39
|
+
From: #{from_alias} <#{from}>
|
|
40
|
+
To: #{to_alias} <#{@to}>
|
|
41
|
+
Subject: #{subject}
|
|
42
|
+
#{content_type}
|
|
43
|
+
|
|
44
|
+
#{@data.join("\n")}
|
|
45
|
+
END_OF_MESSAGE
|
|
46
|
+
|
|
47
|
+
unless @options[:debug]
|
|
48
|
+
Net::SMTP.start(@host, @port) do |smtp|
|
|
49
|
+
smtp.send_message msg, from, to
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
return [msg, from, to]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def << string
|
|
57
|
+
data << string
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def puts string
|
|
61
|
+
data << string
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'gchart'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
$stderr.puts "The FancyHTML output format requires the googlechart gem:"
|
|
6
|
+
$stderr.puts " (sudo) gem install googlecharts"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module RequestLogAnalyzer::Output
|
|
10
|
+
|
|
11
|
+
class FancyHTML < HTML
|
|
12
|
+
|
|
13
|
+
# Load class files if needed
|
|
14
|
+
def self.const_missing(const)
|
|
15
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def report_tracker(tracker)
|
|
19
|
+
case tracker
|
|
20
|
+
when RequestLogAnalyzer::Tracker::HourlySpread then report_hourly_spread(tracker)
|
|
21
|
+
else tracker.report(self)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def report_hourly_spread(tracker)
|
|
26
|
+
title tracker.title
|
|
27
|
+
puts tag(:img, nil, :width => '700', :height => '120', :src =>
|
|
28
|
+
Gchart.sparkline(:data => tracker.hour_frequencies, :size => '700x120', :line_colors => '0077CC',
|
|
29
|
+
:axis_with_labels => 'x,y', :axis_labels => [x_axis_labels(tracker),y_axis_labels(tracker)]))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def x_axis_labels(tracker)
|
|
33
|
+
frmt = '%H:%M'
|
|
34
|
+
x_axis_labels = []
|
|
35
|
+
num_labels = 5
|
|
36
|
+
start = tracker.first_timestamp
|
|
37
|
+
step = tracker.timespan / (num_labels - 1)
|
|
38
|
+
for i in 0...num_labels do
|
|
39
|
+
x_axis_labels << (start + step * i).strftime(frmt)
|
|
40
|
+
end
|
|
41
|
+
x_axis_labels
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def y_axis_labels(tracker)
|
|
45
|
+
sorted_frequencies = tracker.hour_frequencies.sort
|
|
46
|
+
[sorted_frequencies.first, sorted_frequencies.last]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|