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.
Files changed (144) hide show
  1. data/.gitignore +10 -0
  2. data/DESIGN.rdoc +41 -0
  3. data/LICENSE +4 -4
  4. data/README.rdoc +38 -0
  5. data/Rakefile +6 -3
  6. data/bin/request-log-analyzer +70 -72
  7. data/lib/cli/command_line_arguments.rb +53 -53
  8. data/lib/cli/database_console.rb +26 -0
  9. data/lib/cli/database_console_init.rb +43 -0
  10. data/lib/cli/progressbar.rb +166 -189
  11. data/lib/cli/tools.rb +49 -0
  12. data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
  13. data/lib/request_log_analyzer/aggregator/echo.rb +17 -12
  14. data/lib/request_log_analyzer/aggregator/summarizer.rb +101 -63
  15. data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +17 -13
  16. data/lib/request_log_analyzer/controller.rb +251 -98
  17. data/lib/request_log_analyzer/database/base.rb +114 -0
  18. data/lib/request_log_analyzer/database/connection.rb +38 -0
  19. data/lib/request_log_analyzer/database/request.rb +22 -0
  20. data/lib/request_log_analyzer/database/source.rb +13 -0
  21. data/lib/request_log_analyzer/database/warning.rb +14 -0
  22. data/lib/request_log_analyzer/database.rb +102 -0
  23. data/lib/request_log_analyzer/file_format/amazon_s3.rb +74 -0
  24. data/lib/request_log_analyzer/file_format/apache.rb +147 -0
  25. data/lib/request_log_analyzer/file_format/delayed_job.rb +55 -0
  26. data/lib/request_log_analyzer/file_format/merb.rb +65 -29
  27. data/lib/request_log_analyzer/file_format/mysql.rb +101 -0
  28. data/lib/request_log_analyzer/file_format/postgresql.rb +68 -0
  29. data/lib/request_log_analyzer/file_format/rack.rb +9 -0
  30. data/lib/request_log_analyzer/file_format/rails.rb +164 -78
  31. data/lib/request_log_analyzer/file_format/rails3.rb +86 -0
  32. data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
  33. data/lib/request_log_analyzer/file_format.rb +252 -58
  34. data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
  35. data/lib/request_log_analyzer/filter/field.rb +19 -13
  36. data/lib/request_log_analyzer/filter/timespan.rb +25 -12
  37. data/lib/request_log_analyzer/filter.rb +30 -0
  38. data/lib/request_log_analyzer/line_definition.rb +69 -96
  39. data/lib/request_log_analyzer/log_processor.rb +31 -53
  40. data/lib/request_log_analyzer/mailer.rb +65 -0
  41. data/lib/request_log_analyzer/output/fancy_html.rb +49 -0
  42. data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
  43. data/lib/request_log_analyzer/output/html.rb +187 -0
  44. data/lib/request_log_analyzer/output.rb +117 -0
  45. data/lib/request_log_analyzer/request.rb +125 -40
  46. data/lib/request_log_analyzer/source/database_loader.rb +87 -0
  47. data/lib/request_log_analyzer/source/log_parser.rb +297 -0
  48. data/lib/request_log_analyzer/source.rb +72 -0
  49. data/lib/request_log_analyzer/tracker/duration.rb +28 -64
  50. data/lib/request_log_analyzer/tracker/frequency.rb +108 -0
  51. data/lib/request_log_analyzer/tracker/hourly_spread.rb +76 -49
  52. data/lib/request_log_analyzer/tracker/numeric_value.rb +223 -0
  53. data/lib/request_log_analyzer/tracker/timespan.rb +54 -27
  54. data/lib/request_log_analyzer/tracker/traffic.rb +40 -0
  55. data/lib/request_log_analyzer/tracker.rb +101 -0
  56. data/lib/request_log_analyzer.rb +43 -13
  57. data/request-log-analyzer.gemspec +41 -0
  58. data/spec/database.yml +23 -0
  59. data/spec/fixtures/apache_combined.log +5 -0
  60. data/spec/fixtures/apache_common.log +10 -0
  61. data/spec/fixtures/decompression.log +12 -0
  62. data/spec/fixtures/decompression.log.bz2 +0 -0
  63. data/spec/fixtures/decompression.log.gz +0 -0
  64. data/spec/fixtures/decompression.log.zip +0 -0
  65. data/spec/fixtures/decompression.tar.gz +0 -0
  66. data/spec/fixtures/decompression.tgz +0 -0
  67. data/spec/fixtures/header_and_footer.log +6 -0
  68. data/spec/fixtures/merb_prefixed.log +9 -0
  69. data/spec/fixtures/mysql_slow_query.log +110 -0
  70. data/spec/fixtures/postgresql.log +2980 -0
  71. data/spec/fixtures/rails.db +0 -0
  72. data/spec/fixtures/rails_22.log +1 -1
  73. data/spec/fixtures/sinatra.log +99 -0
  74. data/spec/integration/command_line_usage_spec.rb +84 -0
  75. data/spec/integration/mailer_spec.rb +179 -0
  76. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  77. data/spec/integration/scout_spec.rb +152 -0
  78. data/spec/lib/helpers.rb +72 -0
  79. data/spec/lib/macros.rb +18 -0
  80. data/spec/lib/matchers.rb +77 -0
  81. data/spec/lib/mocks.rb +77 -0
  82. data/spec/lib/testing_format.rb +46 -0
  83. data/spec/spec_helper.rb +16 -59
  84. data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
  85. data/spec/unit/aggregator/summarizer_spec.rb +26 -0
  86. data/spec/unit/controller/controller_spec.rb +41 -0
  87. data/spec/unit/controller/log_processor_spec.rb +18 -0
  88. data/spec/unit/database/base_class_spec.rb +183 -0
  89. data/spec/unit/database/connection_spec.rb +34 -0
  90. data/spec/unit/database/database_spec.rb +133 -0
  91. data/spec/unit/file_format/amazon_s3_format_spec.rb +67 -0
  92. data/spec/unit/file_format/apache_format_spec.rb +203 -0
  93. data/spec/unit/file_format/common_regular_expressions_spec.rb +53 -0
  94. data/spec/unit/file_format/delayed_job_format_spec.rb +83 -0
  95. data/spec/unit/file_format/file_format_api_spec.rb +69 -0
  96. data/spec/unit/file_format/format_autodetection_spec.rb +40 -0
  97. data/spec/unit/file_format/line_definition_spec.rb +75 -0
  98. data/spec/unit/file_format/merb_format_spec.rb +52 -0
  99. data/spec/unit/file_format/mysql_format_spec.rb +154 -0
  100. data/spec/unit/file_format/postgresql_format_spec.rb +65 -0
  101. data/spec/unit/file_format/rack_format_spec.rb +50 -0
  102. data/spec/unit/file_format/rails3_format_spec.rb +120 -0
  103. data/spec/unit/file_format/rails_format_spec.rb +180 -0
  104. data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
  105. data/spec/unit/filter/field_filter_spec.rb +66 -0
  106. data/spec/unit/filter/filter_spec.rb +17 -0
  107. data/spec/unit/filter/timespan_filter_spec.rb +58 -0
  108. data/spec/unit/mailer_spec.rb +42 -0
  109. data/spec/unit/request_spec.rb +111 -0
  110. data/spec/unit/source/log_parser_spec.rb +119 -0
  111. data/spec/unit/tracker/duration_tracker_spec.rb +49 -0
  112. data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
  113. data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
  114. data/spec/unit/tracker/numeric_value_tracker_spec.rb +166 -0
  115. data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
  116. data/spec/unit/tracker/tracker_api_spec.rb +125 -0
  117. data/spec/unit/tracker/traffic_tracker_spec.rb +28 -0
  118. data/tasks/github-gem.rake +290 -139
  119. data/tasks/request_log_analyzer.rake +23 -7
  120. metadata +221 -94
  121. data/DESIGN +0 -14
  122. data/HACKING +0 -7
  123. data/README.textile +0 -36
  124. data/lib/cli/bashcolorizer.rb +0 -60
  125. data/lib/request_log_analyzer/aggregator/database.rb +0 -148
  126. data/lib/request_log_analyzer/filter/base.rb +0 -29
  127. data/lib/request_log_analyzer/log_parser.rb +0 -173
  128. data/lib/request_log_analyzer/source/base.rb +0 -42
  129. data/lib/request_log_analyzer/source/log_file.rb +0 -170
  130. data/lib/request_log_analyzer/tracker/base.rb +0 -54
  131. data/lib/request_log_analyzer/tracker/category.rb +0 -71
  132. data/spec/controller_spec.rb +0 -40
  133. data/spec/database_inserter_spec.rb +0 -101
  134. data/spec/file_format_spec.rb +0 -78
  135. data/spec/file_formats/spec_format.rb +0 -26
  136. data/spec/filter_spec.rb +0 -137
  137. data/spec/line_definition_spec.rb +0 -124
  138. data/spec/log_parser_spec.rb +0 -68
  139. data/spec/log_processor_spec.rb +0 -57
  140. data/spec/merb_format_spec.rb +0 -38
  141. data/spec/rails_format_spec.rb +0 -76
  142. data/spec/request_spec.rb +0 -72
  143. data/spec/summarizer_spec.rb +0 -9
  144. data/tasks/rspec.rake +0 -6
@@ -1,90 +1,176 @@
1
- class RequestLogAnalyzer::FileFormat::Rails < RequestLogAnalyzer::FileFormat
2
-
3
- # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
4
- line_definition :processing do |line|
5
- line.header = true # this line is the first log line for a request
6
- line.teaser = /Processing /
7
- 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]+)\]/
8
- line.captures << { :name => :controller, :type => :string } \
9
- << { :name => :action, :type => :string } \
10
- << { :name => :format, :type => :string } \
11
- << { :name => :ip, :type => :string, :anonymize => :ip } \
12
- << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
13
- << { :name => :method, :type => :string }
14
- end
15
-
16
- # 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.
17
- line_definition :cache_hit do |line|
18
- line.regexp = /Filter chain halted as \[\#<ActionController::Caching::Actions::ActionCacheFilter:.+>\] rendered_or_redirected/
19
- end
20
-
21
- # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
22
- line_definition :failed do |line|
23
- line.footer = true
24
- line.regexp = /((?:[A-Z]\w+\:\:)*[A-Z]\w+) \((.*)\)(?: on line #(\d+) of .+)?\:(.*)/
25
- line.captures << { :name => :error, :type => :string } \
26
- << { :name => :message, :type => :string } \
27
- << { :name => :line, :type => :integer } \
28
- << { :name => :file, :type => :string } \
29
- << { :name => :stack_trace, :type => :string, :anonymize => true }
30
- end
1
+ module RequestLogAnalyzer::FileFormat
31
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.
8
+ class Rails < Base
32
9
 
33
- # Rails < 2.1 completed line example
34
- # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
35
- 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.+)\]/
10
+ extend CommonRegularExpressions
36
11
 
37
- # Rails > 2.1 completed line example
38
- # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
39
- RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
12
+ # Creates a Rails FileFormat instance.
13
+ #
14
+ # The lines that will be parsed can be defined by the argument to this function,
15
+ # which should be an array of line names, or a list of line names as comma separated
16
+ # string. The resulting report depends on the lines that will be parsed. You can
17
+ # also provide s string that describes a common set of lines, like "production",
18
+ # "development" or "production".
19
+ def self.create(lines = 'production')
20
+ definitions_hash = line_definer.line_definitions.clone
21
+
22
+ lines = lines.to_s.split(',') if lines.kind_of?(String)
23
+ lines = [lines.to_s] if lines.kind_of?(Symbol)
24
+
25
+ lines.each do |line|
26
+ line = line.to_sym
27
+ if LINE_COLLECTIONS.has_key?(line)
28
+ LINE_COLLECTIONS[line].each { |l| definitions_hash[l] = LINE_DEFINITIONS[l] }
29
+ elsif LINE_DEFINITIONS.has_key?(line)
30
+ definitions_hash[line] = LINE_DEFINITIONS[line]
31
+ else
32
+ raise "Unrecognized Rails log line name: #{line.inspect}!"
33
+ end
34
+ end
40
35
 
41
- # The completed line uses a kind of hack to ensure that both old style logs and new style logs
42
- # are both parsed by the same regular expression. The format in Rails 2.2 was slightly changed,
43
- # but the line contains exactly the same information.
44
- line_definition :completed do |line|
45
-
46
- line.footer = true
47
- line.teaser = /Completed in /
48
- line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
36
+ return self.new(definitions_hash, report_trackers(definitions_hash))
37
+ end
49
38
 
50
- line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
51
- << { :name => :view, :type => :sec, :anonymize => :slightly } \
52
- << { :name => :db, :type => :sec, :anonymize => :slightly } \
53
- << { :name => :status, :type => :integer } \
54
- << { :name => :url, :type => :string, :anonymize => :url } # Old variant
55
-
56
- line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
57
- << { :name => :view, :type => :msec, :anonymize => :slightly } \
58
- << { :name => :db, :type => :msec, :anonymize => :slightly } \
59
- << { :name => :status, :type => :integer} \
60
- << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
61
- end
39
+ # Creates trackers based on the specified line definitions.
40
+ #
41
+ # The more lines that will be parsed, the more information will appear in the report.
42
+ def self.report_trackers(lines)
43
+ analyze = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
44
+
45
+ analyze.timespan
46
+ analyze.hourly_spread
47
+
48
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Most requested'
49
+ analyze.frequency :method, :title => 'HTTP methods'
50
+ analyze.frequency :status, :title => 'HTTP statuses returned'
51
+
52
+ if lines.has_key?(:cache_hit)
53
+ analyze.frequency(:category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' },
54
+ :title => 'Rails action cache hits')
55
+ end
56
+
57
+ analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
58
+ analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
59
+ analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
60
+
61
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
62
+ :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
63
+
64
+ if lines.has_key?(:failure)
65
+ analyze.frequency :error, :title => 'Failed requests', :line_type => :failure
66
+ end
62
67
 
68
+ if lines.has_key?(:rendered)
69
+ analyze.duration :render_duration, :category => :render_file, :multiple => true, :title => 'Partial rendering duration'
70
+ end
63
71
 
72
+ if lines.has_key?(:query_executed)
73
+ analyze.duration :query_duration, :category => :query_sql, :multiple => true, :title => 'Query duration'
74
+ end
75
+
76
+ return analyze.trackers + report_definer.trackers
77
+ end
64
78
 
65
- REQUEST_CATEGORIZER = Proc.new do |request|
66
- format = request[:format] || 'html'
67
- "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
68
- end
79
+ # Rails < 2.1 completed line example
80
+ # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
81
+ 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.+)\]/
69
82
 
70
- report do |analyze|
71
- analyze.timespan :line_type => :processing
72
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
73
- analyze.category :method, :title => 'HTTP methods'
74
- analyze.category :status, :title => 'HTTP statuses returned'
75
- analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
76
-
77
- analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
78
- analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
79
- analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
80
-
81
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
82
- :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
83
-
84
- analyze.hourly_spread :line_type => :processing
85
- analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
86
- end
83
+ # Rails > 2.1 completed line example
84
+ # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
85
+ RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
86
+
87
+ # A hash of definitions for all common lines in Rails logs.
88
+ LINE_DEFINITIONS = {
89
+ :processing => RequestLogAnalyzer::LineDefinition.new(:processing, :header => true,
90
+ :teaser => /Processing /,
91
+ :regexp => /Processing ((?:\w+::)*\w+)#(\w+)(?: to (\w+))? \(for (#{ip_address}) at (#{timestamp('%Y-%m-%d %H:%M:%S')})\) \[([A-Z]+)\]/,
92
+ :captures => [{ :name => :controller, :type => :string },
93
+ { :name => :action, :type => :string },
94
+ { :name => :format, :type => :string, :default => 'html' },
95
+ { :name => :ip, :type => :string },
96
+ { :name => :timestamp, :type => :timestamp },
97
+ { :name => :method, :type => :string }]),
87
98
 
99
+ :completed => RequestLogAnalyzer::LineDefinition.new(:completed, :footer => true,
100
+ :teaser => /Completed in /,
101
+ :regexp => Regexp.union(RAILS_21_COMPLETED, RAILS_22_COMPLETED),
102
+ :captures => [{ :name => :duration, :type => :duration, :unit => :sec }, # First old variant capture
103
+ { :name => :view, :type => :duration, :unit => :sec },
104
+ { :name => :db, :type => :duration, :unit => :sec },
105
+ { :name => :status, :type => :integer },
106
+ { :name => :url, :type => :string }, # Last old variant capture
107
+ { :name => :duration, :type => :duration, :unit => :msec }, # First new variant capture
108
+ { :name => :view, :type => :duration, :unit => :msec },
109
+ { :name => :db, :type => :duration, :unit => :msec },
110
+ { :name => :status, :type => :integer },
111
+ { :name => :url, :type => :string }]), # Last new variant capture
88
112
 
113
+ :failure => RequestLogAnalyzer::LineDefinition.new(:failure, :footer => true,
114
+ :teaser => /((?:[A-Z]\w*[a-z]\w+\:\:)*[A-Z]\w*[a-z]\w+) \((.*)\)(?: on line #(\d+) of (.+))?\:/,
115
+ :regexp => /((?:[A-Z]\w*[a-z]\w+\:\:)*[A-Z]\w*[a-z]\w+) \((.*)\)(?: on line #(\d+) of (.+))?\:\s*$/,
116
+ :captures => [{ :name => :error, :type => :string },
117
+ { :name => :message, :type => :string },
118
+ { :name => :line, :type => :integer },
119
+ { :name => :file, :type => :string }]),
89
120
 
90
- end
121
+ :cache_hit => RequestLogAnalyzer::LineDefinition.new(:cache_hit,
122
+ :regexp => /Filter chain halted as \[\#<ActionController::Filters::AroundFilter.*\@method=.*(?:Caching::Actions::ActionCacheFilter|action_controller\/caching\/actions\.rb).*\] did_not_yield/),
123
+
124
+ :parameters => RequestLogAnalyzer::LineDefinition.new(:parameters,
125
+ :teaser => / Parameters:/,
126
+ :regexp => / Parameters:\s+(\{.*\})/,
127
+ :captures => [{ :name => :params, :type => :eval }]),
128
+
129
+ :rendered => RequestLogAnalyzer::LineDefinition.new(:rendered,
130
+ :teaser => /Rendered /,
131
+ :regexp => /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/,
132
+ :captures => [{ :name => :render_file, :type => :string },
133
+ { :name => :render_duration, :type => :duration, :unit => :msec }]),
134
+
135
+ :query_executed => RequestLogAnalyzer::LineDefinition.new(:query_executed,
136
+ :regexp => /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?([^\e]+) ?(?:\e\[0m)?/,
137
+ :captures => [{ :name => :query_class, :type => :string },
138
+ { :name => :query_duration, :type => :duration, :unit => :msec },
139
+ { :name => :query_sql, :type => :sql }]),
140
+
141
+ :query_cached => RequestLogAnalyzer::LineDefinition.new(:query_cached,
142
+ :regexp => /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?([^\e]+) ?(?:\e\[0m)?/,
143
+ :captures => [{ :name => :cached_duration, :type => :duration, :unit => :msec },
144
+ { :name => :cached_sql, :type => :sql }])
145
+ }
146
+
147
+ # Definitions of common combinations of lines that can be parsed
148
+ LINE_COLLECTIONS = {
149
+ :minimal => [:processing, :completed],
150
+ :production => [:processing, :completed, :failure, :cache_hit],
151
+ :development => [:processing, :completed, :failure, :rendered, :query_executed, :query_cached],
152
+ :all => LINE_DEFINITIONS.keys
153
+ }
154
+
155
+
156
+ # Simple function to categorize Rails requests using controller/actions/format and method.
157
+ REQUEST_CATEGORIZER = Proc.new do |request|
158
+ "#{request[:controller]}##{request[:action]}.#{request[:format]} [#{request[:method]}]"
159
+ end
160
+
161
+ # Define a custom Request class for the Rails file format to speed up timestamp handling
162
+ # and to ensure that a format is always set.
163
+ class Request < RequestLogAnalyzer::Request
164
+
165
+ # Do not use DateTime.parse
166
+ def convert_timestamp(value, definition)
167
+ value.gsub(/[^0-9]/, '')[0...14].to_i
168
+ end
169
+
170
+ # Sanitizes SQL queries so that they can be grouped
171
+ def convert_sql(sql, definition)
172
+ sql.gsub(/\b\d+\b/, ':int').gsub(/`([^`]+)`/, '\1').gsub(/'[^']*'/, ':string').rstrip
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,86 @@
1
+ module RequestLogAnalyzer::FileFormat
2
+
3
+ # Default FileFormat class for Rails 3 logs.
4
+ #
5
+ # For now, this is just a basic implementation. It will probaby change after
6
+ # Rails 3 final has been released.
7
+ class Rails3 < Base
8
+
9
+ extend CommonRegularExpressions
10
+
11
+ # Started GET "/queries" for 127.0.0.1 at 2010-02-25 16:15:18
12
+ line_definition :started do |line|
13
+ line.header = true
14
+ line.teaser = /Started /
15
+ line.regexp = /Started ([A-Z]+) "([^"]+)" for (#{ip_address}) at (#{timestamp('%Y-%m-%d %H:%M:%S')})/
16
+
17
+ line.capture(:method)
18
+ line.capture(:path)
19
+ line.capture(:ip)
20
+ line.capture(:timestamp).as(:timestamp)
21
+ end
22
+
23
+ # Processing by QueriesController#index as HTML
24
+ line_definition :processing do |line|
25
+ line.teaser = /Processing by /
26
+ line.regexp = /Processing by (\w+)\#(\w+) as (\w+)/
27
+
28
+ line.capture(:controller)
29
+ line.capture(:action)
30
+ line.capture(:format)
31
+ end
32
+
33
+ # Completed in 9ms (Views: 4.9ms | ActiveRecord: 0.5ms) with 200
34
+ line_definition :completed do |line|
35
+ line.footer = true
36
+ line.teaser = /Completed /
37
+ line.regexp = /Completed (\d+) .* in (\d+)ms \([^\)]*\)/
38
+
39
+ line.capture(:status).as(:integer)
40
+ line.capture(:duration).as(:duration, :unit => :msec)
41
+ end
42
+
43
+ # ActionView::Template::Error (undefined local variable or method `field' for #<Class>) on line #3 of /Users/willem/Code/warehouse/app/views/queries/execute.csv.erb:
44
+ line_definition :failure do |line|
45
+ line.footer = true
46
+ line.regexp = /((?:[A-Z]\w*[a-z]\w+\:\:)*[A-Z]\w*[a-z]\w+) \((.*)\)(?: on line #(\d+) of (.+))?\:\s*$/
47
+
48
+ line.capture(:error)
49
+ line.capture(:message)
50
+ line.capture(:line).as(:integer)
51
+ line.capture(:file)
52
+ end
53
+
54
+ # # Not parsed at the moment:
55
+ # SQL (0.2ms) SET SQL_AUTO_IS_NULL=0
56
+ # Query Load (0.4ms) SELECT `queries`.* FROM `queries`
57
+ # Rendered collection (0.0ms)
58
+ # Rendered queries/index.html.erb (0.6ms)
59
+
60
+ REQUEST_CATEGORIZER = lambda { |request| "#{request[:controller]}##{request[:action]}.#{request[:format]}" }
61
+
62
+ report do |analyze|
63
+
64
+ analyze.timespan
65
+ analyze.hourly_spread
66
+
67
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Most requested'
68
+ analyze.frequency :method, :title => 'HTTP methods'
69
+ analyze.frequency :status, :title => 'HTTP statuses returned'
70
+
71
+ analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
72
+ # analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
73
+ # analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
74
+
75
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
76
+ :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
77
+ end
78
+
79
+ class Request < RequestLogAnalyzer::Request
80
+ def convert_timestamp(value, defintion)
81
+ value.gsub(/[^0-9]/, '')[0...14].to_i
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,12 @@
1
+ module RequestLogAnalyzer::FileFormat
2
+
3
+ # The RailsDevelopment FileFormat is an extention to the default Rails file format. It includes
4
+ # all lines of the normal Rails file format, but parses SQL queries and partial rendering lines
5
+ # as well.
6
+ class RailsDevelopment < Rails
7
+ def self.create
8
+ # puts 'DEPRECATED: use --rails-format development instead!'
9
+ super('development')
10
+ end
11
+ end
12
+ end