indy 0.1.1

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 (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