request-log-analyzer 1.4.0 → 1.5.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.
Files changed (32) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +19 -24
  3. data/bin/request-log-analyzer +10 -7
  4. data/lib/request_log_analyzer/aggregator/summarizer.rb +13 -2
  5. data/lib/request_log_analyzer/controller.rb +68 -36
  6. data/lib/request_log_analyzer/file_format/apache.rb +1 -1
  7. data/lib/request_log_analyzer/file_format/mysql.rb +93 -0
  8. data/lib/request_log_analyzer/file_format/rails_development.rb +1 -1
  9. data/lib/request_log_analyzer/file_format.rb +46 -2
  10. data/lib/request_log_analyzer/mailer.rb +25 -12
  11. data/lib/request_log_analyzer/output/fixed_width.rb +1 -1
  12. data/lib/request_log_analyzer/output/html.rb +2 -0
  13. data/lib/request_log_analyzer/request.rb +5 -0
  14. data/lib/request_log_analyzer/source/log_parser.rb +33 -10
  15. data/lib/request_log_analyzer/tracker/count.rb +93 -0
  16. data/lib/request_log_analyzer.rb +1 -1
  17. data/request-log-analyzer.gemspec +6 -6
  18. data/spec/fixtures/mysql_slow_query.log +110 -0
  19. data/spec/integration/command_line_usage_spec.rb +3 -3
  20. data/spec/integration/mailer_spec.rb +179 -0
  21. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  22. data/spec/integration/scout_spec.rb +152 -0
  23. data/spec/lib/helpers.rb +26 -1
  24. data/spec/lib/mocks.rb +3 -1
  25. data/spec/unit/file_format/apache_format_spec.rb +102 -95
  26. data/spec/unit/file_format/format_autodetection_spec.rb +35 -0
  27. data/spec/unit/file_format/mysql_format_spec.rb +155 -0
  28. data/spec/unit/file_format/rails_format_spec.rb +9 -12
  29. data/spec/unit/mailer_spec.rb +12 -0
  30. data/spec/unit/tracker/count_tracker_spec.rb +123 -0
  31. data/spec/unit/tracker/tracker_api_spec.rb +1 -1
  32. metadata +125 -110
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Willem van Bergen / Bart ten Brinke
1
+ Copyright (c) 2008-2009 Willem van Bergen & Bart ten Brinke
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,42 +1,37 @@
1
1
  = Request-log-analyzer
2
2
 
3
- This is a simple command line tool to analyze request log files of both Rails and
4
- Merb to produce a performance report. Its purpose is to find what actions are best candidates for optimization.
3
+ This is a simple command line tool to analyze request log files in various formats to produce a performance report. Its purpose is to find what actions are best candidates for optimization.
5
4
 
6
- * Analyzes Rails log files (all versions), Merb logs, or any other log format you specify
7
- * Combines multiple files and decompresses compressed files (handy if you are using logrotate)
8
- * Uses several metrics, including cumulative request time, average request time, process blockers, database and rendering time, HTTP methods and statuses, Rails action cache statistics, etc.) (Sample output: http://wiki.github.com/wvanbergen/request-log-analyzer/sample-output)
9
- * Low memory footprint and reasonably fast (server-safe)
5
+ * Analyzes Rails request logs, Merb request logs, Apache access logs and more, or parses any other log format you specify.
6
+ * Combines multiple files and decompresses compressed files, which comes in handy if you are using logrotate.
7
+ * Uses several metrics, including cumulative request time, mean request time, process blockers, database and rendering time, HTTP methods and statuses, Rails action cache statistics, etc.) (Sample output: http://wiki.github.com/wvanbergen/request-log-analyzer/sample-output)
8
+ * Low memory footprint and reasonably fast, so it is safe to run on a production server.
10
9
  * MIT licensed
11
10
 
12
- Request log analyzer was designed and built by Willem van Bergen and Bart ten Brinke.
11
+ See the project wiki at http://wiki.github.com/wvanbergen/request-log-analyzer for documentation and additional information.
13
12
 
14
- == Installation
13
+ == Installation & basic usage
15
14
 
16
- Install request-log-analyzer as a Ruby gem:
15
+ Install request-log-analyzer as a Ruby gem (you might need to run this command
16
+ as root by prepending +sudo+ to it):
17
17
 
18
- $ sudo gem install request-log-analyzer
18
+ $ gem install request-log-analyzer
19
19
 
20
- Alternatively, use the gem from the GitHub gem server:
21
-
22
- $ sudo gem install wvanbergen-request-log-analyzer --source http://gems.github.com
23
-
24
- To get the best results out of request-log-analyzer, make sure to
25
- set up logging correctly: http://wiki.github.com/wvanbergen/request-log-analyzer/configure-logging
26
- for your application.
27
-
28
- == Usage
29
-
30
- To analyze a log file and produce a performance report, run request-log-analyzer like this:
20
+ To analyze a Rails log file and produce a performance report, run
21
+ request-log-analyzer like this:
31
22
 
32
23
  $ request-log-analyzer log/production.log
33
24
 
34
- For more details and available command line options, see the project's wiki: http://wiki.github.com/wvanbergen/request-log-analyzer/basic-usage
25
+ For more details, other file formats, and available command line options, see the project's wiki at http://wiki.github.com/wvanbergen/request-log-analyzer
35
26
 
36
27
  == Additional information
37
28
 
38
- Do you have a rails application that is not performing as it should?
39
- If you need an expert to analyze your application, feel free to contact either Willem van Bergen (willem@railsdoctors.com) or Bart ten Brinke (bart@railsdoctors.com).
29
+ Request-log-analyzer was designed and built by Willem van Bergen and Bart ten
30
+ Brinke.
31
+
32
+ Do you have a rails application that is not performing as it should? If you need
33
+ an expert to analyze your application, feel free to contact either Willem van
34
+ Bergen (willem@railsdoctors.com) or Bart ten Brinke (bart@railsdoctors.com).
40
35
 
41
36
  * Project wiki at GitHub: http://wiki.github.com/wvanbergen/request-log-analyzer
42
37
  * railsdoctors homepage: http://railsdoctors.com
@@ -27,14 +27,16 @@ begin
27
27
  strip.switch(:keep_junk_lines, :j)
28
28
  end
29
29
 
30
- command_line.option(:format, :alias => :f, :default => 'rails')
30
+ command_line.option(:format, :alias => :f)
31
31
  command_line.option(:apache_format)
32
32
  command_line.option(:rails_format)
33
33
 
34
34
  command_line.option(:file, :alias => :e)
35
35
  command_line.option(:mail, :alias => :m)
36
+ command_line.option(:mailhost, :default => 'localhost')
36
37
  command_line.option(:parse_strategy, :default => 'assume-correct')
37
- command_line.option(:dump)
38
+ command_line.option(:yaml)
39
+ command_line.option(:dump) # To be deprecated
38
40
 
39
41
  command_line.option(:aggregator, :alias => :a, :multiple => true)
40
42
 
@@ -48,7 +50,7 @@ begin
48
50
  command_line.option(:before)
49
51
 
50
52
  command_line.switch(:boring, :b)
51
- command_line.option(:output, :alias => :o, :default => 'FixedWidth')
53
+ command_line.option(:output, :alias => :o, :default => 'fixedwidth')
52
54
  command_line.option(:report_width, :default => terminal_width - 1)
53
55
  command_line.option(:report_amount, :default => 20)
54
56
  command_line.option(:report_sort, :default => 'sum,mean')
@@ -68,7 +70,7 @@ rescue CommandLine::Error => e
68
70
  puts "Usage: request-log-analyzer [LOGFILES*] <OPTIONS>"
69
71
  puts
70
72
  puts "Input options:"
71
- puts " --format <format>, -f: Uses the specified log file format. Defaults to rails."
73
+ puts " --format <format>, -f: Log file format. amazon_s3, apache, merb, mysql, rack or rails (default)."
72
74
  puts " --after <date> Only consider requests from <date> or later."
73
75
  puts " --before <date> Only consider requests before <date>."
74
76
  puts " --select <field> <value> Only consider requests where <field> matches <value>."
@@ -78,10 +80,11 @@ rescue CommandLine::Error => e
78
80
  puts " --boring, -b Output reports without ASCII colors."
79
81
  puts " --database <filename>, -d: Creates an SQLite3 database of all the parsed request information."
80
82
  puts " --debug Print debug information while parsing."
81
- puts " --file <filename> Output to file."
83
+ puts " --file <filename> Redirect output to file."
82
84
  puts " --mail <emailaddress> Send report to an email address."
83
- puts " --output <format> Output format. Supports 'HTML' and 'FixedWidth' (default)"
84
- puts " --dump <filename> Dump the YAML formatted results in the given file"
85
+ puts " --mailhost <server> Use the given server as the SMTP server for sending email."
86
+ puts " --output <format> Output format. Supports 'html' and 'fixed_width' (default)."
87
+ puts " --yaml <filename> Dump the results in YAML format in the given file."
85
88
  puts
86
89
  puts "Examples:"
87
90
  puts " request-log-analyzer development.log"
@@ -49,6 +49,17 @@ module RequestLogAnalyzer::Aggregator
49
49
  end
50
50
  end
51
51
 
52
+ # Track the frequency of a specific category
53
+ # <tt>category_field</tt> Field to track
54
+ # <tt>options</tt> options are passed to new frequency tracker
55
+ def count(category_field, options = {})
56
+ if category_field.kind_of?(Symbol)
57
+ track(:count, options.merge(:category => category_field))
58
+ elsif category_field.kind_of?(Hash)
59
+ track(:count, category_field.merge(options))
60
+ end
61
+ end
62
+
52
63
  # Helper function to initialize a tracker and add it to the tracker array.
53
64
  # <tt>tracker_class</tt> The class to include
54
65
  # <tt>optiont</tt> The options to pass to the trackers.
@@ -90,7 +101,7 @@ module RequestLogAnalyzer::Aggregator
90
101
  # Call finalize on all trackers. Saves a YAML dump if this is set in the options.
91
102
  def finalize
92
103
  @trackers.each { |tracker| tracker.finalize }
93
- save_results_dump(options[:dump]) if options[:dump]
104
+ save_results_dump(options[:yaml]) if options[:yaml]
94
105
  end
95
106
 
96
107
  # Saves the results of all the trackers in YAML format to a file.
@@ -145,7 +156,7 @@ module RequestLogAnalyzer::Aggregator
145
156
  if has_log_ordering_warnings?
146
157
  output.title("Parse warnings")
147
158
 
148
- output.puts "Parseable lines were ancountered without a header line before it. It"
159
+ output.puts "Parseable lines were encountered without a header line before it. It"
149
160
  output.puts "could be that logging is not setup correctly for your application."
150
161
  output.puts "Visit this website for logging configuration tips:"
151
162
  output.puts output.link("http://github.com/wvanbergen/request-log-analyzer/wikis/configure-logging")
@@ -1,14 +1,12 @@
1
1
  module RequestLogAnalyzer
2
2
 
3
3
  # The RequestLogAnalyzer::Controller class creates a LogParser instance for the
4
- # requested file format, and connect it with sources and aggregators.
4
+ # requested file format and connect it with sources and aggregators.
5
5
  #
6
6
  # Sources are streams or files from which the requests will be parsed.
7
7
  # Aggregators will handle every passed request to yield a meaningfull results.
8
8
  #
9
- # - Use the build-function to build a controller instance using command line arguments.
10
- # - Use add_aggregator to register a new aggregator
11
- # - Use add_source to register a new aggregator
9
+ # - Use the build -function to build a new controller instance.
12
10
  # - Use the run! method to start the parser and send the requests to the aggregators.
13
11
  #
14
12
  # Note that the order of sources can be imported if you have log files than succeed
@@ -21,7 +19,6 @@ module RequestLogAnalyzer
21
19
 
22
20
  # Builds a RequestLogAnalyzer::Controller given parsed command line arguments
23
21
  # <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
24
- # <rr>report_with</tt> Width of the report. Defaults to 80.
25
22
  def self.build_from_arguments(arguments)
26
23
 
27
24
  options = {}
@@ -30,13 +27,12 @@ module RequestLogAnalyzer
30
27
  options[:database] = arguments[:database]
31
28
  options[:reset_database] = arguments[:reset_database]
32
29
  options[:debug] = arguments[:debug]
33
- options[:dump] = arguments[:dump]
34
- options[:parse_strategy] = arguments[:parse_strategy]
30
+ options[:yaml] = arguments[:yaml] || arguments[:dump]
31
+ options[:mail] = arguments[:mail]
35
32
  options[:no_progress] = arguments[:no_progress]
36
33
  options[:format] = arguments[:format]
37
- options[:output] = arguments[:output]
34
+ options[:output] = arguments[:output].downcase
38
35
  options[:file] = arguments[:file]
39
- options[:format] = arguments[:format]
40
36
  options[:after] = arguments[:after]
41
37
  options[:before] = arguments[:before]
42
38
  options[:reject] = arguments[:reject]
@@ -46,6 +42,7 @@ module RequestLogAnalyzer
46
42
  options[:report_width] = arguments[:report_width]
47
43
  options[:report_sort] = arguments[:report_sort]
48
44
  options[:report_amount] = arguments[:report_amount]
45
+ options[:mailhost] = arguments[:mailhost]
49
46
 
50
47
  # Apache format workaround
51
48
  if arguments[:rails_format]
@@ -54,6 +51,12 @@ module RequestLogAnalyzer
54
51
  options[:format] = {:apache => arguments[:apache_format]}
55
52
  end
56
53
 
54
+ # Handle output format casing
55
+ if options[:output].class == String
56
+ options[:output] = 'HTML' if options[:output] == 'html'
57
+ options[:output] = 'FixedWidth' if options[:output] == 'fixedwidth' || options[:output] == 'fixed_width'
58
+ end
59
+
57
60
  # Register sources
58
61
  if arguments.parameters.length == 1
59
62
  file = arguments.parameters[0]
@@ -69,38 +72,60 @@ module RequestLogAnalyzer
69
72
  options.store(:source_files, arguments.parameters)
70
73
  end
71
74
 
75
+ # Guess file format
76
+ if !options[:format] && options[:source_files]
77
+ options[:format] = :rails # Default
78
+
79
+ if options[:source_files] != $stdin
80
+ if options[:source_files].class == String
81
+ options[:format] = RequestLogAnalyzer::FileFormat.autodetect(options[:source_files])
82
+
83
+ elsif options[:source_files].class == Array && options[:source_files].first != $stdin
84
+ options[:format] = RequestLogAnalyzer::FileFormat.autodetect(options[:source_files].first)
85
+ end
86
+ end
87
+ end
88
+
72
89
  build(options)
73
90
  end
74
91
 
75
- # Build a new controller using parameters (Base for new API)
76
- # <tt>source</tt> The source file
77
- # Options are passd on to the LogParser.
92
+ # Build a new controller.
93
+ # Returns a new RequestLogAnalyzer::Controller object.
78
94
  #
79
95
  # Options
80
- # * <tt>:database</tt> Database file
81
- # * <tt>:reset_database</tt>
82
- # * <tt>:debug</tt> Enables echo aggregator.
83
- # * <tt>:dump</tt>
84
- # * <tt>:parse_strategy</tt>
85
- # * <tt>:no_progress</tt>
86
- # * <tt>:output</tt> :fixed_width, :html or Output class. Defaults to fixed width.
87
- # * <tt>:file</tt> Filestring or File or StringIO
88
- # * <tt>:format</tt> :rails, {:apache => 'FORMATSTRING'}, :merb, etcetera or Format Class. Defaults to :rails.
89
- # * <tt>:source_files</tt> File or STDIN
90
96
  # * <tt>:after</tt> Drop all requests after this date (Date, DateTime, Time, or a String in "YYYY-MM-DD hh:mm:ss" format)
91
- # * <tt>:before</tt> Drop all requests before this date (Date, DateTime, Time, or a String in "YYYY-MM-DD hh:mm:ss" format)
92
- # * <tt>:reject</tt> Reject specific {:field => :value} combination. Expects single hash.
93
- # * <tt>:select</tt> Select specific {:field => :value} combination. Expects single hash.
94
- # * <tt>:aggregator</tt> Array of aggregators (ATM: STRINGS OR SYMBOLS ONLY!). Defaults to [:summarizer
95
- # * <tt>:boring</tt> Do not show color on STDOUT. Defaults to False.
96
- # * <tt>:report_width</tt> Width or reports in characters. Defaults to 80.
97
+ # * <tt>:aggregator</tt> Array of aggregators (ATM: STRINGS OR SYMBOLS ONLY! - Defaults to [:summarizer]).
98
+ # * <tt>:boring</tt> Do not show color on STDOUT (Defaults to false).
99
+ # * <tt>:before</tt> Drop all requests before this date (Date, DateTime, Time or a String in "YYYY-MM-DD hh:mm:ss" format)
100
+ # * <tt>:database</tt> Database file to insert encountered requests to.
101
+ # * <tt>:debug</tt> Enables echo aggregator which will echo each request analyzed.
102
+ # * <tt>:file</tt> Filestring, File or StringIO.
103
+ # * <tt>:format</tt> :rails, {:apache => 'FORMATSTRING'}, :merb, :amazon_s3, :mysql or RequestLogAnalyzer::FileFormat class. (Defaults to :rails).
104
+ # * <tt>:mail</tt> Email the results to this email address.
105
+ # * <tt>:mailhost</tt> Email the results to this mail server.
106
+ # * <tt>:no_progress</tt> Do not display the progress bar (increases parsing speed).
107
+ # * <tt>:output</tt> 'FixedWidth', 'HTML' or RequestLogAnalyzer::Output class. Defaults to 'FixedWidth'.
108
+ # * <tt>:reject</tt> Reject specific {:field => :value} combination (expects a single hash).
109
+ # * <tt>:report_width</tt> Width of reports in characters for FixedWidth reports. (Defaults to 80)
110
+ # * <tt>:reset_database</tt> Reset the database before starting.
111
+ # * <tt>:select</tt> Select specific {:field => :value} combination (expects a single hash).
112
+ # * <tt>:source_files</tt> Source files to analyze. Provide either File, array of files or STDIN.
113
+ # * <tt>:yaml</tt> Output to YAML file.
97
114
  #
98
- # TODO:
99
- # Check if defaults work (Aggregator defaults seem wrong).
100
- # Refactor :database => options[:database], :dump => options[:dump] away from contoller intialization.
115
+ # === Example
116
+ # RequestLogAnalyzer::Controller.build(
117
+ # :output => :HTML,
118
+ # :mail => 'root@localhost',
119
+ # :after => Time.now - 24*60*60,
120
+ # :source_files => '/var/log/passenger.log'
121
+ # ).run!
122
+ #
123
+ # === Todo
124
+ # * Check if defaults work (Aggregator defaults seem wrong).
125
+ # * Refactor :database => options[:database], :dump => options[:dump] away from contoller intialization.
101
126
  def self.build(options)
102
127
  # Defaults
103
- options[:output] ||= :fixed_width
128
+ options[:output] ||= 'FixedWidth'
104
129
  options[:format] ||= :rails
105
130
  options[:aggregator] ||= [:summarizer]
106
131
  options[:report_width] ||= 80
@@ -108,6 +133,12 @@ module RequestLogAnalyzer
108
133
  options[:report_sort] ||= 'sum,mean'
109
134
  options[:boring] ||= false
110
135
 
136
+ # Deprecation warnings
137
+ if options[:dump]
138
+ warn "[DEPRECATION] `:dump` is deprecated. Please use `:yaml` instead."
139
+ options[:yaml] = options[:dump]
140
+ end
141
+
111
142
  # Set the output class
112
143
  output_args = {}
113
144
  output_object = nil
@@ -124,7 +155,7 @@ module RequestLogAnalyzer
124
155
  output_object = %w[File StringIO].include?(options[:file].class.name) ? options[:file] : File.new(options[:file], "w+")
125
156
  output_args = {:width => 80, :color => false, :characters => :ascii, :sort => output_sort, :amount => output_amount }
126
157
  elsif options[:mail]
127
- output_object = RequestLogAnalyzer::Mailer.new(arguments[:mail])
158
+ output_object = RequestLogAnalyzer::Mailer.new(options[:mail], options[:mailhost])
128
159
  output_args = {:width => 80, :color => false, :characters => :ascii, :sort => output_sort, :amount => output_amount }
129
160
  else
130
161
  output_object = STDOUT
@@ -145,8 +176,9 @@ module RequestLogAnalyzer
145
176
  controller = Controller.new( RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => options[:source_files]),
146
177
  { :output => output_instance,
147
178
  :database => options[:database], # FUGLY!
148
- :dump => options[:dump],
149
- :reset_database => options[:reset_database]})
179
+ :yaml => options[:yaml],
180
+ :reset_database => options[:reset_database],
181
+ :no_progress => options[:no_progress]})
150
182
 
151
183
  # register filters
152
184
  if options[:after] || options[:before]
@@ -188,7 +220,7 @@ module RequestLogAnalyzer
188
220
  # <tt>format</tt> Logfile format. Defaults to :rails
189
221
  # Options are passd on to the LogParser.
190
222
  # * <tt>:database</tt> Database the controller should use.
191
- # * <tt>:dump</tt> Yaml Dump the contrller should use.
223
+ # * <tt>:yaml</tt> Yaml Dump the contrller should use.
192
224
  # * <tt>:output</tt> All report outputs get << through this output.
193
225
  # * <tt>:no_progress</tt> No progress bar
194
226
  def initialize(source, options = {})
@@ -55,7 +55,7 @@ module RequestLogAnalyzer::FileFormat
55
55
 
56
56
  # Creates the access log line definition based on the Apache log format string
57
57
  def self.access_line_definition(format_string)
58
- format_string ||= :combined
58
+ format_string ||= :common
59
59
  format_string = LOG_FORMAT_DEFAULTS[format_string.to_sym] || format_string
60
60
 
61
61
  line_regexp = ''
@@ -0,0 +1,93 @@
1
+ module RequestLogAnalyzer::FileFormat
2
+
3
+ class Mysql < Base
4
+
5
+ line_definition :time do |line|
6
+ line.header = :alternative
7
+ line.teaser = /\# Time: /
8
+ line.regexp = /\# Time: (\d{6}\s+\d{1,2}:\d{2}:\d{2})/
9
+ line.captures << { :name => :timestamp, :type => :timestamp }
10
+ end
11
+
12
+ line_definition :user_host do |line|
13
+ line.header = :alternative
14
+ line.teaser = /\# User\@Host\: /
15
+ line.regexp = /\# User\@Host\: ([\w-]+)\[[\w-]+\] \@ ([\w\.-]*) \[([\d\.]*)\]/
16
+ line.captures << { :name => :user, :type => :string } <<
17
+ { :name => :host, :type => :string } <<
18
+ { :name => :ip, :type => :string }
19
+ end
20
+
21
+ line_definition :query_statistics do |line|
22
+ line.header = :alternative
23
+ line.teaser = /\# Query_time: /
24
+ line.regexp = /\# Query_time: (\d+(?:\.\d+)?)\s+Lock_time: (\d+(?:\.\d+)?)\s+Rows_sent: (\d+)\s+Rows_examined: (\d+)/
25
+ line.captures << { :name => :query_time, :type => :duration, :unit => :sec } <<
26
+ { :name => :lock_time, :type => :duration, :unit => :sec } <<
27
+ { :name => :rows_sent, :type => :integer } <<
28
+ { :name => :rows_examined, :type => :integer }
29
+ end
30
+
31
+ line_definition :use_database do |line|
32
+ line.regexp = /^use (\w+);\s*$/
33
+ line.captures << { :name => :database, :type => :string }
34
+ end
35
+
36
+ line_definition :query_part do |line|
37
+ line.regexp = /^(?!(?:use |\# |SET ))(.*[^;\s])\s*$/
38
+ line.captures << { :name => :query_fragment, :type => :string }
39
+ end
40
+
41
+ line_definition :query do |line|
42
+ line.regexp = /^(?!(?:use |\# |SET ))(.*);\s*$/
43
+ line.captures << { :name => :query, :type => :sql }
44
+ line.footer = true
45
+ end
46
+
47
+ PER_USER = :user
48
+ PER_QUERY = :query
49
+ PER_USER_QUERY = Proc.new { |request| "#{request[:user]}@#{request.host}: #{request[:query]}" }
50
+
51
+ report do |analyze|
52
+ analyze.timespan :line_type => :time
53
+ analyze.frequency :user, :title => "Users with most queries"
54
+ analyze.duration :query_time, :category => PER_USER, :title => 'Query time per user'
55
+ analyze.duration :query_time, :category => PER_USER_QUERY, :title => 'Query time'
56
+ # => analyze.duration :lock_time, :category => PER_USER_QUERY, :title => 'Lock time'
57
+ analyze.count :category => PER_USER_QUERY, :title => "Rows examined", :field => :rows_examined
58
+ analyze.count :category => PER_USER_QUERY, :title => "Rows sent", :field => :rows_sent
59
+ end
60
+
61
+ class Request < RequestLogAnalyzer::Request
62
+
63
+ def convert_sql(value, definition)
64
+
65
+ # Recreate the full SQL query by joining all the previous parts and this last line
66
+ sql = every(:query_fragment).join("\n") + value
67
+
68
+ # Sanitize an SQL query so that it can be used as a category field.
69
+ # sql.gsub!(/\/\*.*\*\//, '') # remove comments
70
+ sql.gsub!(/\s+/, ' ') # remove excessive whitespace
71
+ sql.gsub!(/`([^`]+)`/, '\1') # remove quotes from field names
72
+ sql.gsub!(/'\d{4}-\d{2}-\d{2}'/, ':date') # replace dates
73
+ sql.gsub!(/'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'/, ':datetime') # replace timestamps
74
+ sql.gsub!(/'[^']*'/, ':string') # replace strings
75
+ sql.gsub!(/\b\d+\b/, ':int') # replace integers
76
+ sql.gsub!(/(:int,)+:int/, ':ints') # replace multiple ints by a list
77
+ sql.gsub!(/(:string,)+:string/, ':strings') # replace multiple strings by a list
78
+
79
+ return sql.rstrip
80
+ end
81
+
82
+ def host
83
+ self[:host] == '' || self[:host].nil? ? self[:ip] : self[:host]
84
+ end
85
+
86
+ # Convert the timestamp to an integer
87
+ def convert_timestamp(value, definition)
88
+ all,y,m,d,h,i,s = value.split(/(\d\d)(\d\d)(\d\d)\s+(\d?\d):(\d\d):(\d\d)/)
89
+ ('20%s%s%s%s%s%s' % [y,m,d,h.rjust(2, '0'),i,s]).to_i
90
+ end
91
+ end
92
+ end
93
+ end
@@ -5,7 +5,7 @@ module RequestLogAnalyzer::FileFormat
5
5
  # as well.
6
6
  class RailsDevelopment < Rails
7
7
  def self.create
8
- puts 'DEPRECATED: use --rails-format development instead!'
8
+ # puts 'DEPRECATED: use --rails-format development instead!'
9
9
  super('development')
10
10
  end
11
11
  end
@@ -1,6 +1,6 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
2
 
3
- def self.const_missing(const)
3
+ def self.const_missing(const) # :nodoc:
4
4
  RequestLogAnalyzer::load_default_class_file(self, const)
5
5
  end
6
6
 
@@ -39,10 +39,54 @@ module RequestLogAnalyzer::FileFormat
39
39
 
40
40
  # check the returned klass to see if it can be used
41
41
  raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
42
- raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
42
+ raise "Invalid FileFormat class from #{file_format.inspect}" 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
45
  end
46
+
47
+ # Returns an array of all FileFormat instances that are shipped with request-log-analyzer by default.
48
+ def self.all_formats
49
+ @all_formats ||= Dir[File.dirname(__FILE__) + '/file_format/*.rb'].map do |file|
50
+ self.load(File.basename(file, '.rb'))
51
+ end
52
+ end
53
+
54
+ # Autodetects the filetype of a given file.
55
+ #
56
+ # Returns a FileFormat instance, by parsing the first couple of lines of the provided file
57
+ # with avery known file format and return the most promosing file format based on the parser
58
+ # statistics. The <tt>autodetect_score</tt> method is used to score the fitness of a format.
59
+ #
60
+ # <tt>file</tt>:: The file to detect the file format for.
61
+ # <tt>line_count</tt>:: The number of lines to take into consideration
62
+ def self.autodetect(file, line_count = 50)
63
+
64
+ parsers = all_formats.map { |f| RequestLogAnalyzer::Source::LogParser.new(f, :parse_strategy => 'cautious') }
65
+
66
+ File.open(file, 'r') do |io|
67
+ while io.lineno < line_count && (line = io.gets)
68
+ parsers.each { |parser| parser.parse_line(line) }
69
+ end
70
+ end
71
+
72
+ parsers.select { |p| autodetect_score(p) > 0 }.max { |a, b| autodetect_score(a) <=> autodetect_score(b) }.file_format rescue nil
73
+ end
74
+
75
+ # Calculates a file format auto detection score based on the parser statistics.
76
+ #
77
+ # This method returns a score as an integer. Usually, the score will increase as more
78
+ # lines are parsed. Usually, a file_format with a score of zero or lower should not be
79
+ # considered.
80
+ #
81
+ # <tt>parser</tt>:: The parsed that was use to parse the initial lines of the log file.
82
+ def self.autodetect_score(parser)
83
+ score = 0
84
+ score -= parser.file_format.line_definitions.length
85
+ score -= parser.warnings * 3
86
+ score += parser.parsed_lines * 1
87
+ score += parser.parsed_requests * 10
88
+ score
89
+ end
46
90
 
47
91
  # Base class for all log file format definitions. This class provides functions for subclasses to
48
92
  # define their LineDefinitions and to define a summary report.
@@ -1,38 +1,51 @@
1
1
  module RequestLogAnalyzer
2
2
 
3
+ # Mail report to a specified emailaddress
3
4
  class Mailer
4
5
 
5
- attr_accessor :data, :to, :host
6
+ attr_accessor :data, :to, :host, :port
6
7
 
7
8
  # Initialize a mailer
8
- # <tt>to</tt> to address
9
- # <tt>host</tt> the mailer host
9
+ # <tt>to</tt> to email address to mail to
10
+ # <tt>host</tt> the mailer host (defaults to localhost)
10
11
  # <tt>options</tt> Specific style options
12
+ #
11
13
  # Options
12
- # <tt>:debug</tt> Do not actually mail
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
13
18
  def initialize(to, host = 'localhost', options = {})
14
19
  require 'net/smtp'
15
20
  @to = to
16
21
  @host = host
22
+
23
+ @port = 25
17
24
  @options = options
18
- @data = []
25
+ @host, @port = host.split(':') if @host.include?(':')
26
+ @data = []
19
27
  end
20
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.
21
31
  def mail
22
- from = @options[:from] || 'contact@railsdoctors.com'
23
- from_alias = @options[:from_alias] || 'Request-log-analyzer reporter'
24
- to_alias = @options[:to_alias] || to
25
- subject = @options[:subjeect] || "Request log analyzer report - generated on #{Time.now.to_s}"
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)
26
38
  msg = <<END_OF_MESSAGE
27
39
  From: #{from_alias} <#{from}>
28
40
  To: #{to_alias} <#{@to}>
29
41
  Subject: #{subject}
42
+ #{content_type}
30
43
 
31
- #{@data.to_s}
44
+ #{@data.join("\n")}
32
45
  END_OF_MESSAGE
33
-
46
+
34
47
  unless @options[:debug]
35
- Net::SMTP.start(@host) do |smtp|
48
+ Net::SMTP.start(@host, @port) do |smtp|
36
49
  smtp.send_message msg, from, to
37
50
  end
38
51
  end
@@ -108,7 +108,7 @@ module RequestLogAnalyzer::Output
108
108
  # <tt>url</tt> The url to link to.
109
109
  def link(text, url = nil)
110
110
  if url.nil?
111
- colorize(text, :blue, :bold)
111
+ colorize(text, :red, :bold)
112
112
  else
113
113
  "#{text} (#{colorize(url, :blue, :bold)})"
114
114
  end
@@ -69,6 +69,8 @@ module RequestLogAnalyzer::Output
69
69
 
70
70
  # Genrate HTML header and associated stylesheet
71
71
  def header
72
+ @io.content_type = content_type if @io.respond_to?(:content_type)
73
+
72
74
  @io << "<html>"
73
75
  @io << tag(:head) do |headers|
74
76
  headers << tag(:title, 'Request-log-analyzer report')
@@ -65,6 +65,11 @@ module RequestLogAnalyzer
65
65
  else raise "Unknown duration unit"
66
66
  end
67
67
  end
68
+
69
+ # Convert an epoch to an integer
70
+ def convert_epoch(value, capture_definition)
71
+ Time.at(value.to_i).strftime('%Y%m%d%H%M%S').to_i
72
+ end
68
73
  end
69
74
 
70
75
  # Install the default converter methods