indy 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.autotest +18 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/History.txt +11 -0
  5. data/README.md +132 -0
  6. data/Rakefile +68 -0
  7. data/autotest/discover.rb +2 -0
  8. data/cucumber.yml +6 -0
  9. data/features/after_time.feature +41 -0
  10. data/features/application.feature +33 -0
  11. data/features/around_time.feature +34 -0
  12. data/features/before_time.feature +34 -0
  13. data/features/custom_pattern.feature +52 -0
  14. data/features/exact_log_level.feature +35 -0
  15. data/features/exact_message.feature +33 -0
  16. data/features/exact_mulitple_fields.feature +38 -0
  17. data/features/exact_time.feature +28 -0
  18. data/features/file.feature +30 -0
  19. data/features/log_levels.feature +40 -0
  20. data/features/message.feature +39 -0
  21. data/features/multiple_fields.feature +38 -0
  22. data/features/step_definitions/find_by.steps.rb +55 -0
  23. data/features/step_definitions/log_file.steps.rb +8 -0
  24. data/features/step_definitions/support/env.rb +1 -0
  25. data/features/step_definitions/support/transforms.rb +28 -0
  26. data/features/step_definitions/test_setup.steps.rb +4 -0
  27. data/features/step_definitions/test_teardown.steps.rb +0 -0
  28. data/features/step_definitions/time.steps.rb +29 -0
  29. data/features/within_time.feature +41 -0
  30. data/indy.gemspec +61 -0
  31. data/lib/indy.rb +5 -0
  32. data/lib/indy/indy.rb +463 -0
  33. data/lib/indy/result_set.rb +8 -0
  34. data/performance/helper.rb +5 -0
  35. data/performance/profile_spec.rb +35 -0
  36. data/spec/data.log +2 -0
  37. data/spec/helper.rb +4 -0
  38. data/spec/indy_spec.rb +212 -0
  39. data/spec/result_set_spec.rb +9 -0
  40. data/spec/search_spec.rb +97 -0
  41. data/spec/time_spec.rb +80 -0
  42. metadata +126 -0
@@ -0,0 +1,8 @@
1
+ #
2
+ # The current implementation extends an Array. However, as this
3
+ # has not been tested with a large data set it may need some new
4
+ # Enumerable object that is multi-threaded and performance friendly
5
+ #
6
+ class ResultSet < Array
7
+
8
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../lib/indy") unless
2
+ $:.include? File.expand_path("#{File.dirname(__FILE__)}/../lib/indy")
3
+
4
+ gem 'rspec-prof'
5
+ require 'rspec-prof'
@@ -0,0 +1,35 @@
1
+ require "#{File.dirname(__FILE__)}/helper"
2
+
3
+ describe "Search Performance" do
4
+
5
+
6
+ context "with a small data set" do
7
+
8
+ longer_subject = [
9
+ "2000-09-07 14:07:41 INFO MyApp - Entering application.\n",
10
+ "2000-09-07 14:07:42 DEBUG MyApp - Focusing application.\n",
11
+ "2000-09-07 14:07:43 DEBUG MyApp - Blurring application.\n",
12
+ "2000-09-07 14:07:44 WARN MyApp - Low on Memory.\n",
13
+ "2000-09-07 14:07:45 ERROR MyApp - Out of Memory.\n",
14
+ "2000-09-07 14:07:46 INFO MyApp - Exiting application.\n"
15
+ ].collect {|line| line * 70 }.join
16
+
17
+ profile :file => STDOUT, :printer => :flat, :min_percent => 1 do
18
+
19
+ it "should perform well using #for(:all)" do
20
+ Indy.search(longer_subject.dup).for(:all)
21
+ end
22
+
23
+ it "should perform well using #for(:field => 'value')" do
24
+ Indy.search(longer_subject.dup).for(:severity => 'INFO')
25
+ end
26
+
27
+ it "should perform well using #time()" do
28
+ Indy.search(longer_subject.dup).after(:time => "2000-09-07 14:07:45").for(:all)
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,2 @@
1
+ 2000-09-07 14:07:41 INFO MyApp - Entering application.
2
+ 2000-09-07 14:07:41 INFO MyApp - Exiting application.
@@ -0,0 +1,4 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../lib/indy") unless
2
+ $:.include? File.expand_path("#{File.dirname(__FILE__)}/../lib/indy")
3
+
4
+ require 'rspec'
@@ -0,0 +1,212 @@
1
+ require "#{File.dirname(__FILE__)}/helper"
2
+
3
+ describe Indy do
4
+
5
+ context :initialize do
6
+
7
+ # http://log4r.rubyforge.org/rdoc/Log4r/rdoc/patternformatter.html
8
+ it "should accept a log4r pattern string without error" do
9
+ lambda { Indy.new(:pattern => ["(%d) (%i) (%c) - (%m)", :time, :info, :class, :message]) }.should_not raise_error
10
+ end
11
+
12
+ # http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html
13
+ it "should accept a log4j pattern string without error" do
14
+ lambda { Indy.new(:pattern => ["%d [%M] %p %C{1} - %m", :time, :info, :class, :message])}
15
+ end
16
+
17
+ it "should not raise error with non-conforming data" do
18
+ @indy = Indy.new(:source => " \nfoobar\n\n baz", :pattern => ['([^\s]+) (\w+)', :time, :message])
19
+ lambda{ @indy.for(:all) }.should_not raise_error
20
+ end
21
+
22
+ it "should accept time_format parameter" do
23
+ @indy = Indy.new(:time_format => '%d-%m-%Y', :source => "1-13-2000 yes", :pattern => ['^([^\s]+) (\w+)$', :time, :message])
24
+ lambda{ @indy.for(:all) }.should_not raise_error
25
+ @indy.instance_variable_get(:@time_format).should == '%d-%m-%Y'
26
+ end
27
+
28
+ it "should accept an initialization hash passed to #search" do
29
+ hash = {:time_format => '%d-%m-%Y',
30
+ :source => "1-13-2000 yes",
31
+ :pattern => ['^([^\s]+) (\w+)$', :time, :message]}
32
+ lambda{ @indy = Indy.search( hash ) }.should_not raise_error
33
+ @indy.for(:all).count.should == 1
34
+ end
35
+
36
+
37
+ end
38
+
39
+ context 'instance' do
40
+
41
+ before(:all) do
42
+ @indy = Indy.new(:source => '1/2/2002 string', :pattern => ['([^\s]+) (\w+)', :time, :message])
43
+ end
44
+
45
+ context "method" do
46
+
47
+ it "parse_line() should return a hash" do
48
+ @indy.send(:parse_line, "1/2/2002 string").class.should == Hash
49
+ end
50
+
51
+ it "parse_line() should return :time and :message" do
52
+ hash = @indy.send(:parse_line, "1/2/2002 string")
53
+ hash[:time] == "1/2/2002"
54
+ hash[:message] == "string"
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ context :search do
62
+
63
+ it "should be a class method" do
64
+ Indy.should respond_to(:search)
65
+ end
66
+
67
+ it "should accept a string parameter" do
68
+ lambda{ Indy.search("String Log") }.should_not raise_error
69
+ end
70
+
71
+ it "should accept a :cmd symbol and a command string parameter" do
72
+ lambda{ Indy.search(:cmd =>"ls") }.should_not raise_error
73
+ end
74
+
75
+ it "should return an instance of Indy" do
76
+ Indy.search("source string").should be_kind_of(Indy)
77
+ Indy.search(:cmd => "ls").should be_kind_of(Indy)
78
+ end
79
+
80
+ it "the instance should have the source specified" do
81
+ Indy.search("source string").source.should_not be_nil
82
+ Indy.search(:cmd => "ls").source.should_not be_nil
83
+ end
84
+
85
+ context "for a String" do
86
+
87
+ let(:log_file) { "#{File.dirname(__FILE__)}/data.log" }
88
+
89
+ context "treat it first like a file" do
90
+
91
+ it "should attempt to open the file" do
92
+ IO.should_receive(:open).with("possible_file.ext").ordered
93
+ Indy.search("possible_file.ext")
94
+ end
95
+
96
+ it "should not throw an error for an invalid file" do
97
+ lambda { Indy.search("possible_file.ext") }.should_not raise_error
98
+ end
99
+
100
+ it "should return an IO object when there is a file" do
101
+ IO.should_receive(:open).with("file_exists.ext").and_return(StringIO.new("2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION."))
102
+ Indy.search("file_exists.ext").for(:application => 'MyApp').length.should == 1
103
+ end
104
+
105
+ it "should handle a real file" do
106
+ Indy.search(log_file).for(:application => 'MyApp').length.should == 2
107
+ end
108
+
109
+ end
110
+
111
+ context "treat it second like a string" do
112
+
113
+ it "should attempt to treat it as a string" do
114
+ expecting_string = mock("String With Expectation")
115
+ expecting_string.should_receive(:[])
116
+ expecting_string.should_receive(:to_s).and_return("2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.")
117
+
118
+ IO.should_receive(:open).with(expecting_string).ordered
119
+
120
+ Indy.search(expecting_string).for(:application => 'MyApp').length.should == 1
121
+ end
122
+
123
+ end
124
+
125
+
126
+ context "treat it optionally like a command" do
127
+
128
+ it "should attempt open the command" do
129
+ IO.stub!(:popen).with('ssh user@system "bash --login -c \"cat /var/log/standard.log\" "')
130
+ Indy.search(:cmd => 'ssh user@system "bash --login -c \"cat /var/log/standard.log\" "')
131
+ end
132
+
133
+ it "should not throw an error for an invalid command" do
134
+ IO.stub!(:popen).with('an invalid command').and_return('')
135
+ lambda { Indy.search(:cmd => "an invalid command") }.should_not raise_error
136
+ end
137
+
138
+ it "should return an IO object upon a successful command" do
139
+ IO.stub!(:popen).with("a command").and_return(StringIO.new("2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION."))
140
+ Indy.search(:cmd => "a command").for(:application => 'MyApp').length.should == 1
141
+ end
142
+
143
+ it "should handle a real command" do
144
+ Indy.search(:cmd => "cat #{log_file}").for(:application => 'MyApp').length.should == 2
145
+ end
146
+
147
+ end
148
+
149
+ end
150
+
151
+ end
152
+
153
+ context "instance" do
154
+
155
+ before(:each) do
156
+ log = "2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.\n \n2000-09-07 14:07:41 INFO MyApp Entering APPLICATION.\n2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.\n\n"
157
+ @indy = Indy.search(log)
158
+ end
159
+
160
+ it "with() should be a method" do
161
+ @indy.should respond_to(:with)
162
+ end
163
+
164
+ # http://log4r.rubyforge.org/rdoc/Log4r/rdoc/patternformatter.html
165
+ it "with() should accept a log4r pattern string without error" do
166
+ lambda { @indy.with(["(%d) (%i) (%c) - (%m)", :time, :info, :class, :message]) }.should_not raise_error
167
+ end
168
+
169
+ # http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html
170
+ it "with() should accept a log4j pattern string without error" do
171
+ lambda { @indy.with(["(%d) (%i) (%c) - (%m)", :time, :info, :class, :message])}.should_not raise_error
172
+ end
173
+
174
+ it "should return itself" do
175
+ @indy.with(["(%d) (%i) (%c) - (%m)", :time, :info, :class, :message]).should == @indy
176
+ end
177
+
178
+ [:for, :search, :like, :matching].each do |method|
179
+ it "#{method}() should exist" do
180
+ @indy.should respond_to(method)
181
+ end
182
+
183
+ it "#{method}() should accept a hash of search criteria" do
184
+ lambda { @indy.send(method,:severity => "INFO") }.should_not raise_error
185
+ end
186
+
187
+ it "#{method}() should return a set of results" do
188
+ @indy.send(method,:severity => "DEBUG").should be_kind_of(Array)
189
+ end
190
+
191
+ end
192
+
193
+ context "_search when given source, param and value" do
194
+
195
+ before(:each) do
196
+ @results = @indy.send(:_search, StringIO.new("2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION."),[Indy::DEFAULT_LOG_PATTERN, Indy::DEFAULT_LOG_FIELDS].flatten) {|result| result if result[:application] == "MyApp" }
197
+ end
198
+
199
+ it "should not return nil" do
200
+ @results.should_not be_nil
201
+ @results.should be_kind_of(Array)
202
+ @results.should_not be_empty
203
+ end
204
+
205
+ it "should return an array of results" do
206
+ @results.first[:application].should == "MyApp"
207
+ end
208
+
209
+ end
210
+
211
+ end
212
+ end
@@ -0,0 +1,9 @@
1
+ require "#{File.dirname(__FILE__)}/helper"
2
+
3
+ describe ResultSet do
4
+
5
+ it "should be Enumerable" do
6
+ ResultSet.new.should be_kind_of(Enumerable)
7
+ end
8
+
9
+ end
@@ -0,0 +1,97 @@
1
+ require "#{File.dirname(__FILE__)}/helper"
2
+ require 'tempfile'
3
+
4
+ describe Indy do
5
+
6
+ context "search with string" do
7
+
8
+ before(:each) do
9
+ log_string = ["2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.",
10
+ "2000-09-07 14:08:41 INFO MyOtherApp - Exiting APPLICATION.",
11
+ "2000-09-07 14:10:55 INFO MyApp - Exiting APPLICATION."].join("\n")
12
+ @indy = Indy.search(log_string)
13
+ end
14
+
15
+ it "should return 2 records" do
16
+ @indy.for(:application => 'MyApp').length.should == 2
17
+ end
18
+
19
+ it "should search entire string on each successive search" do
20
+ @indy.for(:application => 'MyApp').length.should == 2
21
+ @indy.for(:severity => 'INFO').length.should == 3
22
+ @indy.for(:application => 'MyApp').length.should == 2
23
+ end
24
+
25
+ end
26
+
27
+ context "search file" do
28
+
29
+ before(:all) do
30
+ @file = Tempfile.new('file_search_spec')
31
+ @file_path = @file.path
32
+ @file.write([ "2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.",
33
+ "2000-09-07 14:08:41 INFO MyOtherApp - Exiting APPLICATION.",
34
+ "2000-09-07 14:10:55 INFO MyApp - Exiting APPLICATION."
35
+ ].join("\n"))
36
+ @file.flush
37
+ @indy = Indy.search(@file_path)
38
+ end
39
+
40
+ it "should return 2 records" do
41
+ @indy.for(:application => 'MyApp').length.should == 2
42
+ end
43
+
44
+ it "should search entire file on each successive search" do
45
+ @indy.for(:application => 'MyApp').length.should == 2
46
+ @indy.for(:severity => 'INFO').length.should == 3
47
+ @indy.for(:application => 'MyApp').length.should == 2
48
+ end
49
+
50
+ it "should search reopened file on each successive search" do
51
+ @file.write("\n2000-09-07 14:10:55 INFO MyApp - really really Exiting APPLICATION.\n")
52
+ @file.flush
53
+ @indy.for(:application => 'MyApp').length.should == 3
54
+ @indy.for(:severity => 'INFO').length.should == 4
55
+ @indy.for(:application => 'MyApp').length.should == 3
56
+ end
57
+
58
+ end
59
+
60
+ context "search using cmd" do
61
+
62
+ before(:all) do
63
+ @file = Tempfile.new('file_search_spec')
64
+ @file_path = @file.path
65
+ @file.write([ "2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.",
66
+ "2000-09-07 14:08:41 INFO MyOtherApp - Exiting APPLICATION.",
67
+ "2000-09-07 14:10:55 INFO MyApp - Exiting APPLICATION."
68
+ ].join("\n"))
69
+ @file.flush
70
+
71
+ cmd = "ruby -e 'puts File.open(\"#{@file_path}\").read'"
72
+
73
+ @indy = Indy.search(:cmd => cmd)
74
+ end
75
+
76
+ it "should return 2 records" do
77
+ @indy.for(:application => 'MyApp').length.should == 2
78
+ end
79
+
80
+ it "should execute cmd on each successive search" do
81
+ @indy.for(:application => 'MyApp').length.should == 2
82
+ @indy.for(:severity => 'INFO').length.should == 3
83
+ @indy.for(:application => 'MyApp').length.should == 2
84
+ end
85
+
86
+ it "should execute cmd on each successive search" do
87
+ @file.write("\n2000-09-07 14:10:55 INFO MyApp - really really Exiting APPLICATION.\n")
88
+ @file.flush
89
+ @indy.for(:application => 'MyApp').length.should == 3
90
+ @indy.for(:severity => 'INFO').length.should == 4
91
+ @indy.for(:application => 'MyApp').length.should == 3
92
+ end
93
+
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,80 @@
1
+ require "#{File.dirname(__FILE__)}/helper"
2
+
3
+ describe Indy do
4
+
5
+ context "default time handling" do
6
+
7
+ before(:all) do
8
+ @indy = Indy.search("2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.")
9
+ end
10
+
11
+ it "should parse a standard date" do
12
+ line_hash = {:time => "2000-09-07 14:07:41", :message => "Entering APPLICATION"}
13
+ @indy.send(:parse_date, line_hash).class.should == DateTime
14
+ end
15
+
16
+ end
17
+
18
+ context "non-default time handling" do
19
+
20
+ before(:all) do
21
+ pattern = "(\w+) (\d{4}-\d{2}-\d{2}) (\w+) - (.*)"
22
+ @indy = Indy.new(:source => "INFO 2000-09-07 MyApp - Entering APPLICATION.", :pattern => [pattern, :severity, :time, :application, :message])
23
+ end
24
+
25
+ it "should parse a non-standard date" do
26
+ line_hash = {:time => "2000/09/07", :message => "Entering APPLICATION"}
27
+ @indy.send(:parse_date, line_hash).class.should == DateTime
28
+ end
29
+
30
+ end
31
+
32
+ context "explicit time format" do
33
+
34
+ before(:each) do
35
+ pattern = "^([^\s]+) (.*)$"
36
+ @indy = Indy.new(:time_format => '%m-%d-%Y', :source => "1-13-2002 message\n1-14-2002 another message\n1-15-2002 another message", :pattern => [pattern, :time, :message])
37
+ end
38
+
39
+ it "should parse a US style date when given a time format" do
40
+ line_hash = {:time => '1-13-2002', :message => 'message'}
41
+ @indy.send(:parse_date, line_hash).class.should == DateTime
42
+ end
43
+
44
+ it "should accept standard time format searches even while using an explicit log time format" do
45
+ @indy.after(:time => 'Jan 13 2002').for(:all).count.should == 2
46
+ @indy.after(:time => 'Jan 14 2002').for(:all).last._time.mday.should == 15
47
+ end
48
+
49
+ end
50
+
51
+ context "built-in _time field" do
52
+
53
+ before(:all) do
54
+ log_string = ["2000-09-07 14:07:41 INFO MyApp - Entering APPLICATION.",
55
+ "2000-09-07 14:08:41 INFO MyApp - Exiting APPLICATION.",
56
+ "2000-09-07 14:10:55 INFO MyApp - Exiting APPLICATION."].join("\n")
57
+ @search_result = Indy.search(log_string).for(:application => 'MyApp')
58
+ @time_search_result = Indy.search(log_string).before(:time => "2100-09-07").for(:application => 'MyApp')
59
+ end
60
+
61
+ it "should not exist as an attribute when unless performing a time search" do
62
+ @search_result.first._time.class.should == NilClass
63
+ @time_search_result.first._time.class.should == DateTime
64
+ end
65
+
66
+ it "should be accurate" do
67
+ @time_search_result.first._time.to_s.should == "2000-09-07T14:07:41+00:00"
68
+ end
69
+
70
+ it "should allow for time range calculations" do
71
+ time_span = @time_search_result.last._time - @time_search_result.first._time
72
+ hours,minutes,seconds,frac = Date.day_fraction_to_time( time_span )
73
+ hours.should == 0
74
+ minutes.should == 3
75
+ seconds.should == 14
76
+ end
77
+
78
+ end
79
+
80
+ end