request-log-analyzer 1.0.2

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 (60) hide show
  1. data/DESIGN +14 -0
  2. data/HACKING +7 -0
  3. data/LICENSE +20 -0
  4. data/README.textile +36 -0
  5. data/Rakefile +5 -0
  6. data/bin/request-log-analyzer +123 -0
  7. data/lib/cli/bashcolorizer.rb +60 -0
  8. data/lib/cli/command_line_arguments.rb +301 -0
  9. data/lib/cli/progressbar.rb +236 -0
  10. data/lib/request_log_analyzer.rb +14 -0
  11. data/lib/request_log_analyzer/aggregator/base.rb +45 -0
  12. data/lib/request_log_analyzer/aggregator/database.rb +148 -0
  13. data/lib/request_log_analyzer/aggregator/echo.rb +25 -0
  14. data/lib/request_log_analyzer/aggregator/summarizer.rb +116 -0
  15. data/lib/request_log_analyzer/controller.rb +201 -0
  16. data/lib/request_log_analyzer/file_format.rb +81 -0
  17. data/lib/request_log_analyzer/file_format/merb.rb +33 -0
  18. data/lib/request_log_analyzer/file_format/rails.rb +90 -0
  19. data/lib/request_log_analyzer/filter/base.rb +29 -0
  20. data/lib/request_log_analyzer/filter/field.rb +36 -0
  21. data/lib/request_log_analyzer/filter/timespan.rb +32 -0
  22. data/lib/request_log_analyzer/line_definition.rb +159 -0
  23. data/lib/request_log_analyzer/log_parser.rb +173 -0
  24. data/lib/request_log_analyzer/log_processor.rb +121 -0
  25. data/lib/request_log_analyzer/request.rb +95 -0
  26. data/lib/request_log_analyzer/source/base.rb +42 -0
  27. data/lib/request_log_analyzer/source/log_file.rb +170 -0
  28. data/lib/request_log_analyzer/tracker/base.rb +54 -0
  29. data/lib/request_log_analyzer/tracker/category.rb +71 -0
  30. data/lib/request_log_analyzer/tracker/duration.rb +81 -0
  31. data/lib/request_log_analyzer/tracker/hourly_spread.rb +80 -0
  32. data/lib/request_log_analyzer/tracker/timespan.rb +54 -0
  33. data/spec/controller_spec.rb +40 -0
  34. data/spec/database_inserter_spec.rb +101 -0
  35. data/spec/file_format_spec.rb +78 -0
  36. data/spec/file_formats/spec_format.rb +26 -0
  37. data/spec/filter_spec.rb +137 -0
  38. data/spec/fixtures/merb.log +84 -0
  39. data/spec/fixtures/multiple_files_1.log +5 -0
  40. data/spec/fixtures/multiple_files_2.log +2 -0
  41. data/spec/fixtures/rails_1x.log +59 -0
  42. data/spec/fixtures/rails_22.log +12 -0
  43. data/spec/fixtures/rails_22_cached.log +10 -0
  44. data/spec/fixtures/rails_unordered.log +24 -0
  45. data/spec/fixtures/syslog_1x.log +5 -0
  46. data/spec/fixtures/test_file_format.log +13 -0
  47. data/spec/fixtures/test_language_combined.log +14 -0
  48. data/spec/fixtures/test_order.log +16 -0
  49. data/spec/line_definition_spec.rb +124 -0
  50. data/spec/log_parser_spec.rb +68 -0
  51. data/spec/log_processor_spec.rb +57 -0
  52. data/spec/merb_format_spec.rb +38 -0
  53. data/spec/rails_format_spec.rb +76 -0
  54. data/spec/request_spec.rb +72 -0
  55. data/spec/spec_helper.rb +67 -0
  56. data/spec/summarizer_spec.rb +9 -0
  57. data/tasks/github-gem.rake +177 -0
  58. data/tasks/request_log_analyzer.rake +10 -0
  59. data/tasks/rspec.rake +6 -0
  60. metadata +135 -0
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'request_log_analyzer/log_processor'
3
+
4
+ describe RequestLogAnalyzer::LogProcessor, 'anonymization' do
5
+
6
+ include RequestLogAnalyzerSpecHelper
7
+
8
+ before(:each) do
9
+ @log_anonymizer = RequestLogAnalyzer::LogProcessor.new(spec_format, :anonymize, {})
10
+ @alternate_log_anonymizer = RequestLogAnalyzer::LogProcessor.new(spec_format, :anonymize, {:keep_junk_lines => true, :discard_teaser_lines => true})
11
+ end
12
+
13
+ it "should keep a junk line if :keep_junk_lines is true" do
14
+ @alternate_log_anonymizer.anonymize_line("junk line\n").should == "junk line\n"
15
+ end
16
+
17
+ it "should remove a junk line" do
18
+ @log_anonymizer.anonymize_line("junk line\n").should be_empty
19
+ end
20
+
21
+ it "should keep a teaser line intact" do
22
+ @log_anonymizer.anonymize_line("processing 1234\n").should == "processing 1234\n"
23
+ end
24
+
25
+ it "should discard a teaser line if discard_teaser_line is true" do
26
+ @alternate_log_anonymizer.anonymize_line("processing 1234\n").should be_empty
27
+ end
28
+
29
+ it "should keep a matching line intact if no anonymizing is declared" do
30
+ @alternate_log_anonymizer.anonymize_line("finishing request 130\n").should == "finishing request 130\n"
31
+ end
32
+
33
+ it "should anonymize values completely if requested" do
34
+ @alternate_log_anonymizer.anonymize_line("testing is great\n").should == "testing is ***\n"
35
+ end
36
+
37
+ it "should anonymize values slightly if requested" do
38
+ @alternate_log_anonymizer.anonymize_line("finishing request 130\n").should =~ /^finishing request 1\d\d\n$/
39
+ end
40
+ end
41
+
42
+ describe RequestLogAnalyzer::LogProcessor, 'stripping log files' do
43
+
44
+ include RequestLogAnalyzerSpecHelper
45
+
46
+ before(:each) do
47
+ @log_stripper = RequestLogAnalyzer::LogProcessor.new(spec_format, :strip, {})
48
+ end
49
+
50
+ it "should remove a junk line" do
51
+ @log_stripper.strip_line("junk line\n").should be_empty
52
+ end
53
+
54
+ it "should keep a teaser line intact" do
55
+ @log_stripper.strip_line("processing 1234\n").should be_empty
56
+ end
57
+ end
@@ -0,0 +1,38 @@
1
+ # require File.dirname(__FILE__) + '/spec_helper'
2
+ #
3
+ # describe RequestLogAnalyzer::LogParser, "Merb" do
4
+ # include RequestLogAnalyzerSpecHelper
5
+ #
6
+ # before(:each) do
7
+ # @log_parser = RequestLogAnalyzer::LogParser.new(:merb)
8
+ # end
9
+ #
10
+ # it "should have a valid language definitions" do
11
+ # @log_parser.file_format.should be_valid
12
+ # end
13
+ #
14
+ # it "should parse a stream and find valid requests" do
15
+ # File.open(log_fixture(:merb), 'r') do |io|
16
+ # @log_parser.parse_io(io) do |request|
17
+ # request.should be_kind_of(RequestLogAnalyzer::Request)
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # it "should find 11 completed requests" do
23
+ # @log_parser.should_receive(:handle_request).exactly(11).times
24
+ # @log_parser.parse_file(log_fixture(:merb))
25
+ # end
26
+ #
27
+ # it "should parse all details from a request correctly" do
28
+ # request = nil
29
+ # @log_parser.parse_file(log_fixture(:merb)) { |found_request| request ||= found_request }
30
+ #
31
+ # request.should be_completed
32
+ # request[:timestamp].should == DateTime.parse('Fri Aug 29 11:10:23 +0200 2008')
33
+ # request[:dispatch_time].should == 0.243424
34
+ # request[:after_filters_time].should == 6.9e-05
35
+ # request[:before_filters_time].should == 0.213213
36
+ # request[:action_time].should == 0.241652
37
+ # end
38
+ # end
@@ -0,0 +1,76 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RequestLogAnalyzer::LogParser, "Rails" do
4
+ include RequestLogAnalyzerSpecHelper
5
+
6
+ before(:each) do
7
+ @log_parser = RequestLogAnalyzer::LogParser.new(RequestLogAnalyzer::FileFormat.load(:rails))
8
+ end
9
+
10
+ it "should have a valid language definitions" do
11
+ @log_parser.file_format.should be_valid
12
+ end
13
+
14
+ it "should parse a stream and find valid requests" do
15
+ io = File.new(log_fixture(:rails_1x), 'r')
16
+ @log_parser.parse_io(io) do |request|
17
+ request.should be_kind_of(RequestLogAnalyzer::Request)
18
+ end
19
+ io.close
20
+ end
21
+
22
+ it "should find 4 completed requests" do
23
+ @log_parser.should_not_receive(:warn)
24
+ @log_parser.should_receive(:handle_request).exactly(4).times
25
+ @log_parser.parse_file(log_fixture(:rails_1x))
26
+ end
27
+
28
+ it "should parse a Rails 2.2 request properly" do
29
+ @log_parser.should_not_receive(:warn)
30
+ @log_parser.parse_file(log_fixture(:rails_22)) do |request|
31
+ request.should =~ :processing
32
+ request.should =~ :completed
33
+
34
+ request[:controller].should == 'PageController'
35
+ request[:action].should == 'demo'
36
+ request[:url].should == 'http://www.example.coml/demo'
37
+ request[:status].should == 200
38
+ request[:duration].should == 0.614
39
+ request[:db].should == 0.031
40
+ request[:view].should == 0.120
41
+ end
42
+ end
43
+
44
+ it "should parse a syslog file with prefix correctly" do
45
+ @log_parser.should_not_receive(:warn)
46
+ @log_parser.parse_file(log_fixture(:syslog_1x)) do |request|
47
+
48
+ request.should be_completed
49
+
50
+ request[:controller].should == 'EmployeeController'
51
+ request[:action].should == 'index'
52
+ request[:url].should == 'http://example.com/employee.xml'
53
+ request[:status].should == 200
54
+ request[:duration].should == 0.21665
55
+ request[:db].should == 0.0
56
+ request[:view].should == 0.00926
57
+ end
58
+ end
59
+
60
+ it "should parse cached requests" do
61
+ @log_parser.should_not_receive(:warn)
62
+ @log_parser.parse_file(log_fixture(:rails_22_cached)) do |request|
63
+ request.should be_completed
64
+ request =~ :cache_hit
65
+ end
66
+ end
67
+
68
+ it "should detect unordered requests in the logs" do
69
+ @log_parser.should_not_receive(:handle_request)
70
+ # the first Processing-line will not give a warning, but the next one will
71
+ @log_parser.should_receive(:warn).with(:unclosed_request, anything).once
72
+ # Both Completed ;ines will give a warning
73
+ @log_parser.should_receive(:warn).with(:no_current_request, anything).twice
74
+ @log_parser.parse_file(log_fixture(:rails_unordered))
75
+ end
76
+ end
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Request, :incomplete_request do
4
+
5
+ include RequestLogAnalyzerSpecHelper
6
+
7
+ before(:each) do
8
+ @incomplete_request = RequestLogAnalyzer::Request.new(spec_format)
9
+ @incomplete_request << { :line_type => :test, :lineno => 1, :test_capture => 'awesome!' }
10
+ end
11
+
12
+ it "should be single if only one line has been added" do
13
+ @incomplete_request.should_not be_empty
14
+ end
15
+
16
+ it "should not be a completed request" do
17
+ @incomplete_request.should_not be_completed
18
+ end
19
+
20
+ it "should take the line type of the first line as global line_type" do
21
+ @incomplete_request.lines[0][:line_type].should == :test
22
+ @incomplete_request.should =~ :test
23
+ end
24
+
25
+ it "should return the first field value" do
26
+ @incomplete_request[:test_capture].should == 'awesome!'
27
+ end
28
+
29
+ it "should return nil if no such field is present" do
30
+ @incomplete_request[:nonexisting].should be_nil
31
+ end
32
+ end
33
+
34
+
35
+ describe RequestLogAnalyzer::Request, :completed_request do
36
+
37
+ include RequestLogAnalyzerSpecHelper
38
+
39
+ before(:each) do
40
+ @completed_request = RequestLogAnalyzer::Request.new(spec_format)
41
+ @completed_request << { :line_type => :first, :lineno => 1, :name => 'first line!' }
42
+ @completed_request << { :line_type => :test, :lineno => 4, :test_capture => 'testing' }
43
+ @completed_request << { :line_type => :test, :lineno => 7, :test_capture => 'testing some more' }
44
+ @completed_request << { :line_type => :last, :lineno => 10, :time => 0.03 }
45
+ end
46
+
47
+ it "should not be empty when multiple liness are added" do
48
+ @completed_request.should_not be_empty
49
+ end
50
+
51
+ it "should be a completed request" do
52
+ @completed_request.should be_completed
53
+ end
54
+
55
+ it "should recognize all line types" do
56
+ [:first, :test, :last].each { |type| @completed_request.should =~ type }
57
+ end
58
+
59
+ it "should detect the correct field value" do
60
+ @completed_request[:name].should == 'first line!'
61
+ @completed_request[:time].should == 0.03
62
+ end
63
+
64
+ it "should detect the first matching field value" do
65
+ @completed_request.first(:test_capture).should == 'testing'
66
+ end
67
+
68
+ it "should detect the every matching field value" do
69
+ @completed_request.every(:test_capture).should == ['testing', "testing some more"]
70
+ end
71
+
72
+ end
@@ -0,0 +1,67 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'request_log_analyzer'
6
+
7
+ module RequestLogAnalyzerSpecHelper
8
+
9
+ def format_file(format)
10
+ File.dirname(__FILE__) + "/file_formats/#{format}.rb"
11
+ end
12
+
13
+ def spec_format
14
+ @spec_format ||= begin
15
+ require format_file(:spec_format)
16
+ SpecFormat.new
17
+ end
18
+ end
19
+
20
+ def log_fixture(name)
21
+ File.dirname(__FILE__) + "/fixtures/#{name}.log"
22
+ end
23
+
24
+ def request(fields, format = TestFileFormat)
25
+ if fields.kind_of?(Array)
26
+ RequestLogAnalyzer::Request.create(format, *fields)
27
+ else
28
+ RequestLogAnalyzer::Request.create(format, fields)
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ module TestFileFormat
35
+
36
+ module Summarizer
37
+ def self.included(base)
38
+ # monkey patching for summarizer here :-)
39
+ end
40
+ end
41
+
42
+ module LogParser
43
+ def self.included(base)
44
+ # monkey patching for log parser here :-)
45
+ end
46
+ end
47
+
48
+ LINE_DEFINITIONS = {
49
+ :first => {
50
+ :header => true,
51
+ :teaser => /processing /,
52
+ :regexp => /processing request (\d+)/,
53
+ :captures => [{ :name => :request_no, :type => :integer, :anonymize => :slightly }]
54
+ },
55
+ :test => {
56
+ :teaser => /testing /,
57
+ :regexp => /testing is (\w+)/,
58
+ :captures => [{ :name => :test_capture, :type => :string, :anonymize => true}]
59
+ },
60
+ :last => {
61
+ :footer => true,
62
+ :teaser => /finishing /,
63
+ :regexp => /finishing request (\d+)/,
64
+ :captures => [{ :name => :request_no, :type => :integer}]
65
+ }
66
+ }
67
+ end
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'request_log_analyzer/aggregator/summarizer'
3
+
4
+ describe RequestLogAnalyzer::Aggregator::Summarizer do
5
+
6
+ before(:each) do
7
+ end
8
+
9
+ end
@@ -0,0 +1,177 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/tasklib'
4
+ require 'date'
5
+
6
+ module Rake
7
+
8
+ class GithubGem < TaskLib
9
+
10
+ attr_accessor :name
11
+ attr_accessor :specification
12
+
13
+ def self.define_tasks!
14
+ gem_task_builder = Rake::GithubGem.new
15
+ gem_task_builder.register_all_tasks!
16
+ end
17
+
18
+
19
+ def initialize
20
+ reload_gemspec!
21
+ end
22
+
23
+ def register_all_tasks!
24
+ namespace(:gem) do
25
+ desc "Updates the file lists for this gem"
26
+ task(:manifest) { manifest_task }
27
+
28
+ desc "Builds a ruby gem for #{@name}"
29
+ task(:build => [:manifest]) { build_task }
30
+
31
+ desc "Installs the ruby gem for #{@name} locally"
32
+ task(:install => [:build]) { install_task }
33
+
34
+ desc "Uninstalls the ruby gem for #{@name} locally"
35
+ task(:uninstall) { uninstall_task }
36
+
37
+ desc "Releases a new version of #{@name}"
38
+ task(:release) { release_task }
39
+ end
40
+ end
41
+
42
+
43
+
44
+ protected
45
+
46
+ def reload_gemspec!
47
+ raise "No gemspec file found!" if gemspec_file.nil?
48
+ spec = File.read(gemspec_file)
49
+ @specification = eval(spec)
50
+ @name = specification.name
51
+ end
52
+
53
+ def run_command(command)
54
+ lines = []
55
+ IO.popen(command) { |f| lines = f.readlines }
56
+ return lines
57
+ end
58
+
59
+ def git_modified?(file)
60
+ return !run_command('git status').detect { |line| Regexp.new(Regexp.quote(file)) =~ line }.nil?
61
+ end
62
+
63
+ def git_commit_file(file, message, branch = nil)
64
+ verify_current_branch(branch) unless branch.nil?
65
+ if git_modified?(file)
66
+ sh "git add #{file}"
67
+ sh "git commit -m \"#{message}\""
68
+ else
69
+ raise "#{file} is not modified and cannot be committed!"
70
+ end
71
+ end
72
+
73
+ def git_create_tag(tag_name, message)
74
+ sh "git tag -a \"#{tag_name}\" -m \"#{message}\""
75
+ end
76
+
77
+ def git_push(remote = 'origin', branch = 'master', options = [])
78
+ verify_clean_status(branch)
79
+ options_str = options.map { |o| "--#{o}"}.join(' ')
80
+ sh "git push #{options_str} #{remote} #{branch}"
81
+ end
82
+
83
+ def gemspec_version=(new_version)
84
+ spec = File.read(gemspec_file)
85
+ spec.gsub!(/^(\s*s\.version\s*=\s*)('|")(.+)('|")(\s*)$/) { "#{$1}'#{new_version}'#{$5}" }
86
+ spec.gsub!(/^(\s*s\.date\s*=\s*)('|")(.+)('|")(\s*)$/) { "#{$1}'#{Date.today.strftime('%Y-%m-%d')}'#{$5}" }
87
+ File.open(gemspec_file, 'w') { |f| f << spec }
88
+ reload_gemspec!
89
+ end
90
+
91
+ def gemspec_date=(new_date)
92
+ spec = File.read(gemspec_file)
93
+ spec.gsub!(/^(\s*s\.date\s*=\s*)('|")(.+)('|")(\s*)$/) { "#{$1}'#{new_date.strftime('%Y-%m-%d')}'#{$5}" }
94
+ File.open(gemspec_file, 'w') { |f| f << spec }
95
+ reload_gemspec!
96
+ end
97
+
98
+ def gemspec_file
99
+ @gemspec_file ||= Dir['*.gemspec'].first
100
+ end
101
+
102
+ def verify_current_branch(branch)
103
+ run_command('git branch').detect { |line| /^\* (.+)/ =~ line }
104
+ raise "You are currently not working in the master branch!" unless branch == $1
105
+ end
106
+
107
+ def verify_clean_status(on_branch = nil)
108
+ sh "git fetch"
109
+ lines = run_command('git status')
110
+ raise "You don't have the most recent version available. Run git pull first." if /^\# Your branch is behind/ =~ lines[1]
111
+ raise "You are currently not working in the #{on_branch} branch!" unless on_branch.nil? || (/^\# On branch (.+)/ =~ lines.first && $1 == on_branch)
112
+ raise "Your master branch contains modifications!" unless /^nothing to commit \(working directory clean\)/ =~ lines.last
113
+ end
114
+
115
+ def verify_version(new_version)
116
+ newest_version = run_command('git tag').map { |tag| tag.split(name + '-').last }.compact.map { |v| Gem::Version.new(v) }.max
117
+ raise "This version number (#{new_version}) is not higher than the highest tagged version (#{newest_version})" if !newest_version.nil? && newest_version >= Gem::Version.new(new_version.to_s)
118
+ end
119
+
120
+ def manifest_task
121
+ verify_current_branch('master')
122
+
123
+ list = Dir['**/*'].sort
124
+ list -= [gemspec_file]
125
+
126
+ if File.exist?('.gitignore')
127
+ File.read('.gitignore').each_line do |glob|
128
+ glob = glob.chomp.sub(/^\//, '')
129
+ list -= Dir[glob]
130
+ list -= Dir["#{glob}/**/*"] if File.directory?(glob) and !File.symlink?(glob)
131
+ end
132
+ end
133
+
134
+ # update the spec file
135
+ spec = File.read(gemspec_file)
136
+ spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \) )/mx do
137
+ assignment = $1
138
+ bunch = $2 ? list.grep(/^(test.*_test\.rb|spec.*_spec.rb)$/) : list
139
+ '%s%%w(%s)' % [assignment, bunch.join(' ')]
140
+ end
141
+
142
+ File.open(gemspec_file, 'w') { |f| f << spec }
143
+ reload_gemspec!
144
+ end
145
+
146
+ def build_task
147
+ sh "gem build #{gemspec_file}"
148
+ end
149
+
150
+ def install_task
151
+ raise "#{name} .gem file not found" unless File.exist?("#{name}-#{specification.version}.gem")
152
+ sh "gem install #{name}-#{specification.version}.gem"
153
+ end
154
+
155
+ def uninstall_task
156
+ raise "#{name} .gem file not found" unless File.exist?("#{name}-#{specification.version}.gem")
157
+ sh "gem uninstall #{name}"
158
+ end
159
+
160
+ def release_task
161
+ verify_clean_status('master')
162
+ verify_version(ENV['VERSION'] || @specification.version)
163
+
164
+ # update gemspec file
165
+ self.gemspec_version = ENV['VERSION'] if Gem::Version.correct?(ENV['VERSION'])
166
+ self.gemspec_date = Date.today
167
+ manifest_task
168
+ git_commit_file(gemspec_file, "Updated #{gemspec_file} for release of version #{@specification.version}") if git_modified?(gemspec_file)
169
+
170
+ # create tag and push changes
171
+ git_create_tag("#{@name}-#{@specification.version}", "Tagged version #{@specification.version}")
172
+ git_push('origin', 'master', [:tags])
173
+ end
174
+ end
175
+ end
176
+
177
+ Rake::GithubGem.define_tasks!