request-log-analyzer 1.3.4 → 1.4.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 (86) hide show
  1. data/LICENSE +3 -3
  2. data/README.rdoc +1 -1
  3. data/bin/request-log-analyzer +17 -14
  4. data/lib/cli/command_line_arguments.rb +51 -51
  5. data/lib/cli/database_console.rb +3 -3
  6. data/lib/cli/database_console_init.rb +4 -3
  7. data/lib/cli/progressbar.rb +10 -10
  8. data/lib/cli/tools.rb +3 -3
  9. data/lib/request_log_analyzer/aggregator/database_inserter.rb +19 -17
  10. data/lib/request_log_analyzer/aggregator/echo.rb +14 -9
  11. data/lib/request_log_analyzer/aggregator/summarizer.rb +26 -26
  12. data/lib/request_log_analyzer/aggregator.rb +11 -15
  13. data/lib/request_log_analyzer/controller.rb +162 -89
  14. data/lib/request_log_analyzer/database/base.rb +22 -21
  15. data/lib/request_log_analyzer/database/connection.rb +3 -3
  16. data/lib/request_log_analyzer/database/request.rb +22 -0
  17. data/lib/request_log_analyzer/database/source.rb +13 -0
  18. data/lib/request_log_analyzer/database/warning.rb +14 -0
  19. data/lib/request_log_analyzer/database.rb +28 -103
  20. data/lib/request_log_analyzer/file_format/amazon_s3.rb +20 -20
  21. data/lib/request_log_analyzer/file_format/apache.rb +37 -30
  22. data/lib/request_log_analyzer/file_format/merb.rb +30 -13
  23. data/lib/request_log_analyzer/file_format/rack.rb +11 -0
  24. data/lib/request_log_analyzer/file_format/rails.rb +143 -73
  25. data/lib/request_log_analyzer/file_format/rails_development.rb +4 -49
  26. data/lib/request_log_analyzer/file_format.rb +16 -28
  27. data/lib/request_log_analyzer/filter/anonymize.rb +8 -7
  28. data/lib/request_log_analyzer/filter/field.rb +9 -9
  29. data/lib/request_log_analyzer/filter/timespan.rb +12 -10
  30. data/lib/request_log_analyzer/filter.rb +12 -16
  31. data/lib/request_log_analyzer/line_definition.rb +15 -14
  32. data/lib/request_log_analyzer/log_processor.rb +27 -29
  33. data/lib/request_log_analyzer/mailer.rb +15 -9
  34. data/lib/request_log_analyzer/output/fixed_width.rb +48 -48
  35. data/lib/request_log_analyzer/output/html.rb +20 -20
  36. data/lib/request_log_analyzer/output.rb +53 -12
  37. data/lib/request_log_analyzer/request.rb +88 -69
  38. data/lib/request_log_analyzer/source/database_loader.rb +10 -14
  39. data/lib/request_log_analyzer/source/log_parser.rb +57 -50
  40. data/lib/request_log_analyzer/source.rb +10 -12
  41. data/lib/request_log_analyzer/tracker/duration.rb +41 -134
  42. data/lib/request_log_analyzer/tracker/frequency.rb +31 -32
  43. data/lib/request_log_analyzer/tracker/hourly_spread.rb +21 -21
  44. data/lib/request_log_analyzer/tracker/timespan.rb +17 -17
  45. data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
  46. data/lib/request_log_analyzer/tracker.rb +139 -32
  47. data/lib/request_log_analyzer.rb +5 -5
  48. data/request-log-analyzer.gemspec +19 -15
  49. data/spec/database.yml +6 -0
  50. data/spec/fixtures/rails_22.log +1 -1
  51. data/spec/integration/command_line_usage_spec.rb +7 -1
  52. data/spec/lib/helpers.rb +7 -7
  53. data/spec/lib/macros.rb +3 -3
  54. data/spec/lib/matchers.rb +41 -27
  55. data/spec/lib/mocks.rb +19 -15
  56. data/spec/lib/testing_format.rb +9 -9
  57. data/spec/spec_helper.rb +6 -6
  58. data/spec/unit/aggregator/database_inserter_spec.rb +16 -16
  59. data/spec/unit/aggregator/summarizer_spec.rb +4 -4
  60. data/spec/unit/controller/controller_spec.rb +2 -2
  61. data/spec/unit/controller/log_processor_spec.rb +1 -1
  62. data/spec/unit/database/base_class_spec.rb +26 -33
  63. data/spec/unit/database/connection_spec.rb +3 -3
  64. data/spec/unit/database/database_spec.rb +33 -38
  65. data/spec/unit/file_format/amazon_s3_format_spec.rb +5 -5
  66. data/spec/unit/file_format/apache_format_spec.rb +48 -11
  67. data/spec/unit/file_format/file_format_api_spec.rb +13 -13
  68. data/spec/unit/file_format/line_definition_spec.rb +24 -17
  69. data/spec/unit/file_format/merb_format_spec.rb +41 -45
  70. data/spec/unit/file_format/rails_format_spec.rb +157 -117
  71. data/spec/unit/filter/anonymize_filter_spec.rb +2 -2
  72. data/spec/unit/filter/field_filter_spec.rb +13 -13
  73. data/spec/unit/filter/filter_spec.rb +1 -1
  74. data/spec/unit/filter/timespan_filter_spec.rb +15 -15
  75. data/spec/unit/mailer_spec.rb +30 -0
  76. data/spec/unit/{source/request_spec.rb → request_spec.rb} +30 -30
  77. data/spec/unit/source/log_parser_spec.rb +27 -27
  78. data/spec/unit/tracker/duration_tracker_spec.rb +115 -78
  79. data/spec/unit/tracker/frequency_tracker_spec.rb +74 -63
  80. data/spec/unit/tracker/hourly_spread_spec.rb +28 -20
  81. data/spec/unit/tracker/timespan_tracker_spec.rb +25 -13
  82. data/spec/unit/tracker/tracker_api_spec.rb +117 -42
  83. data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
  84. data/tasks/github-gem.rake +125 -75
  85. data/tasks/request_log_analyzer.rake +2 -2
  86. metadata +19 -10
@@ -1,50 +1,67 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
-
2
+
3
+ # The Merb file format parses the request header with the timestamp, the params line
4
+ # with the most important request information and the durations line which contains
5
+ # the different request durations that can be used for analysis.
3
6
  class Merb < Base
4
7
 
5
8
  # ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
6
9
  line_definition :started do |line|
7
10
  line.header = true
8
- line.teaser = /Started/
11
+ line.teaser = /Started request handling\:/
9
12
  line.regexp = /Started request handling\:\ (.+)/
10
13
  line.captures << { :name => :timestamp, :type => :timestamp }
11
- end
12
-
14
+ end
15
+
13
16
  # ~ Params: {"action"=>"create", "controller"=>"session"}
14
17
  # ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
15
18
  line_definition :params do |line|
16
- line.teaser = /Params/
19
+ line.teaser = /Params\:\ /
17
20
  line.regexp = /Params\:\ (\{.+\})/
18
- line.captures << { :name => :params, :type => :eval, :provides => {
21
+ line.captures << { :name => :params, :type => :eval, :provides => {
19
22
  :namespace => :string, :controller => :string, :action => :string, :format => :string, :method => :string } }
20
23
  end
21
24
 
22
25
  # ~ {:dispatch_time=>0.006117, :after_filters_time=>6.1e-05, :before_filters_time=>0.000712, :action_time=>0.005833}
23
26
  line_definition :completed do |line|
24
27
  line.footer = true
28
+ # line.teaser = Regexp.new(Regexp.quote('~ {:'))
25
29
  line.regexp = /(\{.*\:dispatch_time\s*=>\s*\d+\.\d+.*\})/
26
30
  line.captures << { :name => :times_hash, :type => :eval, :provides => {
27
31
  :dispatch_time => :duration, :after_filters_time => :duration,
28
32
  :before_filters_time => :duration, :action_time => :duration } }
29
33
  end
30
-
31
- REQUEST_CATEGORIZER = Proc.new do |request|
34
+
35
+ REQUEST_CATEGORIZER = Proc.new do |request|
32
36
  category = "#{request[:controller]}##{request[:action]}"
33
37
  category = "#{request[:namespace]}::#{category}" if request[:namespace]
34
38
  category = "#{category}.#{request[:format]}" if request[:format]
35
39
  category
36
40
  end
37
-
41
+
38
42
  report do |analyze|
39
- analyze.timespan :line_type => :started
40
- analyze.frequency :category => REQUEST_CATEGORIZER, :amount => 20, :title => "Top 20 by hits"
41
- analyze.hourly_spread :line_type => :started
43
+
44
+ analyze.timespan
45
+ analyze.hourly_spread
46
+
47
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => "Top 20 by hits"
42
48
  analyze.duration :dispatch_time, :category => REQUEST_CATEGORIZER, :title => 'Request dispatch duration'
49
+
43
50
  # analyze.duration :action_time, :category => REQUEST_CATEGORIZER, :title => 'Request action duration'
44
51
  # analyze.duration :after_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request after_filter duration'
45
52
  # analyze.duration :before_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request before_filter duration'
46
53
  end
47
-
54
+
55
+ class Request < RequestLogAnalyzer::Request
56
+
57
+ MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
58
+ 'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
59
+
60
+ # Speed up timestamp conversion
61
+ def convert_timestamp(value, definition)
62
+ "#{value[26,4]}#{MONTHS[value[4,3]]}#{value[8,2]}#{value[11,2]}#{value[14,2]}#{value[17,2]}".to_i
63
+ end
64
+ end
48
65
  end
49
66
 
50
67
  end
@@ -0,0 +1,11 @@
1
+ module RequestLogAnalyzer::FileFormat
2
+
3
+ class Rack < Apache
4
+
5
+ def self.create(*args)
6
+ super(:rack, *args)
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -1,103 +1,173 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
-
2
+
3
+ # Default FileFormat class for Rails logs.
4
+ #
5
+ # Instances will be created dynamically based on the lines you want it to parse. You can
6
+ # specify what lines should be included in the parser by providing a list to the create
7
+ # method as first argument.
3
8
  class Rails < Base
4
-
5
- # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
6
- line_definition :processing do |line|
7
- line.header = true # this line is the first log line for a request
8
- line.teaser = /Processing /
9
- line.regexp = /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/
10
- line.captures << { :name => :controller, :type => :string } \
11
- << { :name => :action, :type => :string } \
12
- << { :name => :format, :type => :format } \
13
- << { :name => :ip, :type => :string } \
14
- << { :name => :timestamp, :type => :timestamp } \
15
- << { :name => :method, :type => :string }
16
- end
17
9
 
18
- # Filter chain halted as [#<ActionController::Caching::Actions::ActionCacheFilter:0x2a999ad620 @check=nil, @options={:store_options=>{}, :layout=>nil, :cache_path=>#<Proc:0x0000002a999b8890@/app/controllers/cached_controller.rb:8>}>] rendered_or_redirected.
19
- line_definition :cache_hit do |line|
20
- line.regexp = /Filter chain halted as \[\#<ActionController::Caching::Actions::ActionCacheFilter:.+>\] rendered_or_redirected/
21
- end
22
-
23
- # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
24
- line_definition :failed do |line|
25
- line.footer = true
26
- line.regexp = /((?:[A-Z]\w+\:\:)*[A-Z]\w+) \((.*)\)(?: on line #(\d+) of .+)?\:(.*)/
27
- line.captures << { :name => :error, :type => :string } \
28
- << { :name => :message, :type => :string } \
29
- << { :name => :line, :type => :integer } \
30
- << { :name => :file, :type => :string } \
31
- << { :name => :stack_trace, :type => :string }
10
+ # Creates a Rails FileFormat instance.
11
+ #
12
+ # The lines that will be parsed can be defined by the argument to this function,
13
+ # which should be an array of line names, or a list of line names as comma separated
14
+ # string. The resulting report depends on the lines that will be parsed. You can
15
+ # also provide s string that describes a common set of lines, like "production",
16
+ # "development" or "production".
17
+ def self.create(lines = 'production')
18
+ definitions_hash = line_definer.line_definitions.clone
19
+
20
+ lines = lines.to_s.split(',') if lines.kind_of?(String)
21
+ lines = [lines.to_s] if lines.kind_of?(Symbol)
22
+
23
+ lines.each do |line|
24
+ line = line.to_sym
25
+ if LINE_COLLECTIONS.has_key?(line)
26
+ LINE_COLLECTIONS[line].each { |l| definitions_hash[l] = LINE_DEFINITIONS[l] }
27
+ elsif LINE_DEFINITIONS.has_key?(line)
28
+ definitions_hash[line] = LINE_DEFINITIONS[line]
29
+ else
30
+ raise "Unrecognized Rails log line name: #{line.inspect}!"
31
+ end
32
+ end
33
+
34
+ return self.new(definitions_hash, report_trackers(definitions_hash))
32
35
  end
36
+
37
+ # Creates trackers based on the specified line definitions.
38
+ #
39
+ # The more lines that will be parsed, the more information will appear in the report.
40
+ def self.report_trackers(lines)
41
+ analyze = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
42
+
43
+ analyze.timespan
44
+ analyze.hourly_spread
45
+
46
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Most requested'
47
+ analyze.frequency :method, :title => 'HTTP methods'
48
+ analyze.frequency :status, :title => 'HTTP statuses returned'
49
+
50
+ if lines.has_key?(:cache_hit)
51
+ analyze.frequency(:category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' },
52
+ :title => 'Rails action cache hits')
53
+ end
54
+
55
+ analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
56
+ analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
57
+ analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
58
+
59
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
60
+ :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
61
+
62
+ if lines.has_key?(:failure)
63
+ analyze.frequency :error, :title => 'Failed requests', :line_type => :failure
64
+ end
65
+
66
+ if lines.has_key?(:rendered)
67
+ analyze.duration :render_duration, :category => :render_file, :multiple_per_request => true, :title => 'Partial rendering duration'
68
+ end
33
69
 
70
+ if lines.has_key?(:query_executed)
71
+ analyze.duration :query_duration, :category => :query_sql, :multiple_per_request => true, :title => 'Query duration'
72
+ end
73
+
74
+ return analyze.trackers + report_definer.trackers
75
+ end
34
76
 
35
77
  # Rails < 2.1 completed line example
36
78
  # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
37
79
  RAILS_21_COMPLETED = /Completed in (\d+\.\d{5}) \(\d+ reqs\/sec\) (?:\| Rendering: (\d+\.\d{5}) \(\d+\%\) )?(?:\| DB: (\d+\.\d{5}) \(\d+\%\) )?\| (\d\d\d).+\[(http.+)\]/
38
80
 
39
81
  # Rails > 2.1 completed line example
40
- # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
82
+ # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
41
83
  RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
42
84
 
43
- # The completed line uses a kind of hack to ensure that both old style logs and new style logs
44
- # are both parsed by the same regular expression. The format in Rails 2.2 was slightly changed,
45
- # but the line contains exactly the same information.
46
- line_definition :completed do |line|
47
-
48
- line.footer = true
49
- line.teaser = /Completed in /
50
- line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
51
-
52
- line.captures << { :name => :duration, :type => :duration, :unit => :sec } \
53
- << { :name => :view, :type => :duration, :unit => :sec } \
54
- << { :name => :db, :type => :duration, :unit => :sec } \
55
- << { :name => :status, :type => :integer } \
56
- << { :name => :url, :type => :string } # Old variant
57
-
58
- line.captures << { :name => :duration, :type => :duration, :unit => :msec } \
59
- << { :name => :view, :type => :duration, :unit => :msec } \
60
- << { :name => :db, :type => :duration, :unit => :msec } \
61
- << { :name => :status, :type => :integer } \
62
- << { :name => :url, :type => :string } # 2.2 variant
63
- end
85
+ # A hash of definitions for all common lines in Rails logs.
86
+ LINE_DEFINITIONS = {
87
+ :processing => RequestLogAnalyzer::LineDefinition.new(:processing, :header => true,
88
+ :teaser => /Processing /,
89
+ :regexp => /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/,
90
+ :captures => [{ :name => :controller, :type => :string },
91
+ { :name => :action, :type => :string },
92
+ { :name => :format, :type => :string, :default => 'html' },
93
+ { :name => :ip, :type => :string },
94
+ { :name => :timestamp, :type => :timestamp },
95
+ { :name => :method, :type => :string }]),
96
+
97
+ :completed => RequestLogAnalyzer::LineDefinition.new(:completed, :footer => true,
98
+ :teaser => /Completed in /,
99
+ :regexp => Regexp.union(RAILS_21_COMPLETED, RAILS_22_COMPLETED),
100
+ :captures => [{ :name => :duration, :type => :duration, :unit => :sec }, # First old variant capture
101
+ { :name => :view, :type => :duration, :unit => :sec },
102
+ { :name => :db, :type => :duration, :unit => :sec },
103
+ { :name => :status, :type => :integer },
104
+ { :name => :url, :type => :string }, # Last old variant capture
105
+ { :name => :duration, :type => :duration, :unit => :msec }, # First new variant capture
106
+ { :name => :view, :type => :duration, :unit => :msec },
107
+ { :name => :db, :type => :duration, :unit => :msec },
108
+ { :name => :status, :type => :integer },
109
+ { :name => :url, :type => :string }]), # Last new variant capture
110
+
111
+ :failure => RequestLogAnalyzer::LineDefinition.new(:failure, :footer => true,
112
+ :teaser => /((?:[A-Z]\w*[a-z]\w+\:\:)*[A-Z]\w*[a-z]\w+) \((.*)\)(?: on line #(\d+) of (.+))?\:/,
113
+ :regexp => /((?:[A-Z]\w*[a-z]\w+\:\:)*[A-Z]\w*[a-z]\w+) \((.*)\)(?: on line #(\d+) of (.+))?\:\s*$/,
114
+ :captures => [{ :name => :error, :type => :string },
115
+ { :name => :message, :type => :string },
116
+ { :name => :line, :type => :integer },
117
+ { :name => :file, :type => :string }]),
118
+
119
+ :cache_hit => RequestLogAnalyzer::LineDefinition.new(:cache_hit,
120
+ :regexp => /Filter chain halted as \[\#<ActionController::Caching::Actions::ActionCacheFilter/),
121
+
122
+ :parameters => RequestLogAnalyzer::LineDefinition.new(:parameters,
123
+ :teaser => / Parameters:/,
124
+ :regexp => / Parameters:\s+(\{.*\})/,
125
+ :captures => [{ :name => :params, :type => :eval }]),
126
+
127
+ :rendered => RequestLogAnalyzer::LineDefinition.new(:rendered,
128
+ :teaser => /Rendered /,
129
+ :regexp => /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/,
130
+ :captures => [{ :name => :render_file, :type => :string },
131
+ { :name => :render_duration, :type => :duration, :unit => :msec }]),
132
+
133
+ :query_executed => RequestLogAnalyzer::LineDefinition.new(:query_executed,
134
+ :regexp => /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?([^\e]+) ?(?:\e\[0m)?/,
135
+ :captures => [{ :name => :query_class, :type => :string },
136
+ { :name => :query_duration, :type => :duration, :unit => :msec },
137
+ { :name => :query_sql, :type => :sql }]),
138
+
139
+ :query_cached => RequestLogAnalyzer::LineDefinition.new(:query_cached,
140
+ :regexp => /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?([^\e]+) ?(?:\e\[0m)?/,
141
+ :captures => [{ :name => :cached_duration, :type => :duration, :unit => :msec },
142
+ { :name => :cached_sql, :type => :sql }])
143
+ }
64
144
 
145
+ # Definitions of common combinations of lines that can be parsed
146
+ LINE_COLLECTIONS = {
147
+ :minimal => [:processing, :completed],
148
+ :production => [:processing, :completed, :failure, :cache_hit],
149
+ :development => [:processing, :completed, :failure, :rendered, :query_executed, :query_cached],
150
+ :all => LINE_DEFINITIONS.keys
151
+ }
65
152
 
66
153
 
154
+ # Simple function to categorize Rails requests using controller/actions/format and method.
67
155
  REQUEST_CATEGORIZER = Proc.new do |request|
68
156
  "#{request[:controller]}##{request[:action]}.#{request[:format]} [#{request[:method]}]"
69
157
  end
70
158
 
71
- report do |analyze|
72
- analyze.timespan :line_type => :processing
73
- analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
74
- analyze.frequency :method, :title => 'HTTP methods'
75
- analyze.frequency :status, :title => 'HTTP statuses returned'
76
- analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
77
-
78
- analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
79
- analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
80
- analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
81
-
82
- analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
83
- :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
84
-
85
- analyze.hourly_spread :line_type => :processing
86
- analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
87
- end
88
-
89
159
  # Define a custom Request class for the Rails file format to speed up timestamp handling
90
160
  # and to ensure that a format is always set.
91
161
  class Request < RequestLogAnalyzer::Request
92
162
 
93
163
  # Do not use DateTime.parse
94
164
  def convert_timestamp(value, definition)
95
- value.gsub(/[^0-9]/, '')[0...14].to_i unless value.nil?
165
+ value.gsub(/[^0-9]/, '')[0...14].to_i
96
166
  end
97
-
98
- # Set 'html' as default format for a request
99
- def convert_format(value, definition)
100
- value || 'html'
167
+
168
+ # Sanitizes SQL queries so that they can be grouped
169
+ def convert_sql(sql, definition)
170
+ sql.gsub(/\b\d+\b/, ':int').gsub(/`([^`]+)`/, '\1').gsub(/'[^']*'/, ':string').rstrip
101
171
  end
102
172
  end
103
173
 
@@ -1,57 +1,12 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
-
2
+
3
3
  # The RailsDevelopment FileFormat is an extention to the default Rails file format. It includes
4
4
  # all lines of the normal Rails file format, but parses SQL queries and partial rendering lines
5
5
  # as well.
6
6
  class RailsDevelopment < Rails
7
-
8
- # Parameters: {"action"=>"demo", "controller"=>"page"}
9
- line_definition :parameters do |line|
10
- line.teaser = /Parameters/
11
- line.regexp = /\s+Parameters:\s+(\{.*\})/
12
- line.captures << { :name => :params, :type => :eval }
13
- end
14
-
15
- # Rendered layouts/_footer (2.9ms)
16
- line_definition :rendered do |line|
17
- line.regexp = /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/
18
- line.captures << { :name => :render_file, :type => :string } \
19
- << { :name => :render_duration, :type => :duration, :unit => :msec }
20
- end
21
-
22
- # User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) 
23
- line_definition :query_executed do |line|
24
- line.regexp = /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?([^\e]+) ?(?:\e\[0m)?/
25
- line.captures << { :name => :query_class, :type => :string } \
26
- << { :name => :query_duration, :type => :duration, :unit => :msec } \
27
- << { :name => :query_sql, :type => :sql }
28
- end
29
-
30
- # CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) 
31
- line_definition :query_cached do |line|
32
- line.regexp = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?([^\e]+) ?(?:\e\[0m)?/
33
- line.captures << { :name => :cached_duration, :type => :duration, :unit => :msec } \
34
- << { :name => :cached_sql, :type => :sql }
35
- end
36
-
37
- # Define the reporting for the additional parsed lines
38
- report(:append) do |analyze|
39
-
40
- analyze.duration :render_duration, :category => :render_file, :multiple_per_request => true,
41
- :amount => 20, :title => 'Partial rendering duration'
42
-
43
- analyze.duration :query_duration, :category => :query_sql, :multiple_per_request => true,
44
- :amount => 20, :title => 'Query duration'
45
-
46
- end
47
-
48
- # Add a converter method for the SQL fields the the Rails request class
49
- class Request < Rails::Request
50
-
51
- # Sanitizes SQL queries so that they can be grouped
52
- def convert_sql(sql, definition)
53
- sql.gsub(/\b\d+\b/, ':int').gsub(/`([^`]+)`/, '\1').gsub(/'[^']*'/, ':string').rstrip
54
- end
7
+ def self.create
8
+ puts 'DEPRECATED: use --rails-format development instead!'
9
+ super('development')
55
10
  end
56
11
  end
57
12
  end
@@ -1,9 +1,9 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
-
2
+
3
3
  def self.const_missing(const)
4
4
  RequestLogAnalyzer::load_default_class_file(self, const)
5
- end
6
-
5
+ end
6
+
7
7
  # Loads a FileFormat::Base subclass instance.
8
8
  # You can provide:
9
9
  # * A FileFormat instance (which will return itself)
@@ -34,58 +34,46 @@ module RequestLogAnalyzer::FileFormat
34
34
 
35
35
  else
36
36
  # load a provided file format
37
- klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
37
+ klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
38
38
  end
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
42
  raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
43
-
43
+
44
44
  @current_file_format = klass.create(*args) # return an instance of the class
45
- end
46
-
47
- # Makes classes aware of a file format by registering the file_format variable
48
- module Awareness
49
-
50
- def self.included(base)
51
- base.send(:attr_reader, :file_format)
52
- end
53
-
54
- def register_file_format(format)
55
- @file_format = format
56
- end
57
- end
45
+ end
58
46
 
59
- # Base class for all log file format definitions. This class provides functions for subclasses to
47
+ # Base class for all log file format definitions. This class provides functions for subclasses to
60
48
  # define their LineDefinitions and to define a summary report.
61
49
  #
62
50
  # A subclass of this class is instantiated when request-log-analyzer is started and this instance
63
51
  # is shared with all components of the application so they can act on the specifics of the format
64
52
  class Base
65
-
53
+
66
54
  attr_reader :line_definitions, :report_trackers
67
-
55
+
68
56
  ####################################################################################
69
57
  # CLASS METHODS for format definition
70
58
  ####################################################################################
71
-
59
+
72
60
  # Registers the line definer instance for a subclass.
73
61
  def self.inherited(subclass)
74
62
  if subclass.superclass == RequestLogAnalyzer::FileFormat::Base
75
63
 
76
64
  # Create aline and report definer for this class
77
- subclass.class_eval do
65
+ subclass.class_eval do
78
66
  instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
79
67
  instance_variable_set(:@report_definer, RequestLogAnalyzer::Aggregator::Summarizer::Definer.new)
80
68
  class << self; attr_accessor :line_definer, :report_definer; end
81
- end
69
+ end
82
70
 
83
71
  # Create a custom Request class for this file format
84
72
  subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request)) unless subclass.const_defined?('Request')
85
73
  else
86
74
 
87
75
  # Copy the line and report definer from the parent class.
88
- subclass.class_eval do
76
+ subclass.class_eval do
89
77
  instance_variable_set(:@line_definer, superclass.line_definer.clone)
90
78
  instance_variable_set(:@report_definer, superclass.report_definer.clone)
91
79
  class << self; attr_accessor :line_definer, :report_definer; end
@@ -125,7 +113,7 @@ module RequestLogAnalyzer::FileFormat
125
113
  return self.new(line_definer.line_definitions, report_definer.trackers)
126
114
  end
127
115
 
128
- def initialize(line_definitions = [], report_trackers = [])
116
+ def initialize(line_definitions = {}, report_trackers = [])
129
117
  @line_definitions, @report_trackers = line_definitions, report_trackers
130
118
  end
131
119
 
@@ -165,7 +153,7 @@ module RequestLogAnalyzer::FileFormat
165
153
  match = definition.matches(line, &warning_handler)
166
154
  return match if match
167
155
  end
168
-
156
+
169
157
  return nil
170
158
  end
171
159
  end
@@ -1,12 +1,12 @@
1
1
  module RequestLogAnalyzer::Filter
2
-
2
+
3
3
  # Filter to anonymize parsed values
4
4
  # Options
5
5
  # * <tt>:mode</tt> :reject or :accept.
6
6
  # * <tt>:field</tt> Specific field to accept or reject.
7
7
  # * <tt>:value</tt> Value that the field should match to be accepted or rejected.
8
8
  class Anonymize < Base
9
-
9
+
10
10
  def generate_random_ip
11
11
  "#{rand(256)}.#{rand(256)}.#{rand(256)}.#{rand(256)}"
12
12
  end
@@ -14,12 +14,13 @@ module RequestLogAnalyzer::Filter
14
14
  def anonymize_url(value)
15
15
  return value.sub(/^https?\:\/\/[A-Za-z0-9\.-]+\//, "http://example.com/")
16
16
  end
17
-
17
+
18
18
  def fuzz(value)
19
19
  value * ((75 + rand(50)) / 100.0)
20
20
  end
21
21
 
22
- def filter(request)
22
+ def filter(request)
23
+ # TODO: request.attributes is bad practice
23
24
  request.attributes.each do |key, value|
24
25
  if key == :ip
25
26
  request.attributes[key] = generate_random_ip
@@ -30,9 +31,9 @@ module RequestLogAnalyzer::Filter
30
31
  request.attributes[key] = fuzz(value)
31
32
  end
32
33
  end
33
-
34
+
34
35
  return request
35
- end
36
+ end
36
37
  end
37
-
38
+
38
39
  end
@@ -1,24 +1,24 @@
1
1
  module RequestLogAnalyzer::Filter
2
-
2
+
3
3
  # Filter to select or reject a specific field
4
4
  # Options
5
5
  # * <tt>:mode</tt> :reject or :accept.
6
6
  # * <tt>:field</tt> Specific field to accept or reject.
7
7
  # * <tt>:value</tt> Value that the field should match to be accepted or rejected.
8
8
  class Field < Base
9
-
9
+
10
10
  attr_reader :field, :value, :mode
11
-
11
+
12
12
  def initialize(file_format, options = {})
13
13
  super(file_format, options)
14
14
  setup_filter
15
15
  end
16
-
16
+
17
17
  # Setup mode, field and value.
18
18
  def setup_filter
19
19
  @mode = (@options[:mode] || :accept).to_sym
20
20
  @field = @options[:field].to_sym
21
-
21
+
22
22
  # Convert the timestamp to the correct formats for quick timestamp comparisons
23
23
  if @options[:value].kind_of?(String) && @options[:value][0, 1] == '/' && @options[:value][-1, 1] == '/'
24
24
  @value = Regexp.new(@options[:value][1..-2])
@@ -26,17 +26,17 @@ module RequestLogAnalyzer::Filter
26
26
  @value = @options[:value] # TODO: convert value?
27
27
  end
28
28
  end
29
-
29
+
30
30
  # Keep request if @mode == :select and request has the field and value.
31
31
  # Drop request if @mode == :reject and request has the field and value.
32
32
  # Returns nil otherwise.
33
33
  # <tt>request</tt> Request Object
34
34
  def filter(request)
35
- found_field = request.every(@field).any? { |value| @value === value.to_s }
35
+ found_field = request.every(@field).any? { |value| @value === value.to_s }
36
36
  return nil if !found_field && @mode == :select
37
37
  return nil if found_field && @mode == :reject
38
38
  return request
39
- end
39
+ end
40
40
  end
41
-
41
+
42
42
  end
@@ -1,26 +1,28 @@
1
1
  module RequestLogAnalyzer::Filter
2
-
2
+
3
3
  # Reject all requests not in given timespan
4
4
  # Options
5
5
  # * <tt>:after</tt> Only keep requests after this DateTime.
6
6
  # * <tt>:before</tt> Only keep requests before this DateTime.
7
7
  class Timespan < Base
8
-
8
+
9
9
  attr_reader :before, :after
10
-
10
+
11
11
  def initialize(file_format, options = {})
12
+ @after = nil
13
+ @before = nil
12
14
  super(file_format, options)
13
15
  setup_filter
14
16
  end
15
-
16
-
17
+
18
+
17
19
  # Convert the timestamp to the correct formats for quick timestamp comparisons.
18
20
  # These are stored in the before and after attr_reader fields.
19
21
  def setup_filter
20
- @after = @options[:after].strftime('%Y%m%d%H%M%S').to_i if options[:after]
22
+ @after = @options[:after].strftime('%Y%m%d%H%M%S').to_i if options[:after]
21
23
  @before = @options[:before].strftime('%Y%m%d%H%M%S').to_i if options[:before]
22
24
  end
23
-
25
+
24
26
  # Returns request if:
25
27
  # * @after <= request.timestamp <= @before
26
28
  # * @after <= request.timestamp
@@ -32,12 +34,12 @@ module RequestLogAnalyzer::Filter
32
34
  return request
33
35
  elsif @after && @before.nil? && @after <= request.timestamp
34
36
  return request
35
- elsif @before && @after.nil? && request.timestamp <= @before
37
+ elsif @before && @after.nil? && request.timestamp <= @before
36
38
  return request
37
39
  end
38
40
 
39
41
  return nil
40
- end
42
+ end
41
43
  end
42
-
44
+
43
45
  end