indy 0.3.4 → 0.4.0.pre
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +8 -0
- data/Guardfile +12 -0
- data/History.txt +6 -0
- data/README.md +43 -14
- data/Rakefile +7 -0
- data/features/step_definitions/find_by.steps.rb +1 -1
- data/features/step_definitions/log_file.steps.rb +1 -1
- data/features/step_definitions/time.steps.rb +6 -6
- data/indy.gemspec +21 -13
- data/lib/indy.rb +10 -2
- data/lib/indy/indy.rb +87 -408
- data/lib/indy/log_definition.rb +115 -0
- data/lib/indy/log_formats.rb +15 -7
- data/lib/indy/search.rb +147 -0
- data/lib/indy/source.rb +143 -50
- data/lib/indy/time.rb +78 -0
- data/lib/indy/version.rb +1 -1
- data/performance/large.log +40000 -0
- data/performance/profile_spec.rb +7 -7
- data/performance/time_large_file_spec.rb +18 -0
- data/spec/helper.rb +5 -3
- data/spec/indy_private_spec.rb +24 -0
- data/spec/indy_spec.rb +153 -226
- data/spec/indy_struct_spec.rb +43 -0
- data/spec/log_definition_spec.rb +75 -0
- data/spec/log_format_spec.rb +62 -50
- data/spec/search_spec.rb +15 -25
- data/spec/source_spec.rb +43 -35
- data/spec/time_scope_spec.rb +162 -0
- data/spec/time_spec.rb +26 -192
- metadata +264 -164
- data/.autotest +0 -18
- data/.rvmrc +0 -1
- data/autotest/discover.rb +0 -2
- data/lib/indy/formats.rb +0 -3
- data/lib/indy/notes.txt +0 -9
- data/lib/indy/result_set.rb +0 -8
- data/lib/scanf.rb +0 -13
- data/spec/last_spec.rb +0 -42
- data/spec/result_set_spec.rb +0 -36
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: daf391715ba62b54e26aceed291bb60ca7e4b44c
|
4
|
+
data.tar.gz: f02335c95b62f424f1325e87b95729da4d3c77ce
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b5390c28c66aa47a30848e4fd72ea40753b8ae46cbc6c7f66b9f130c8adafbf9909e22d742f76271a311f48a21a3c4f416999de1cecda07485cf9c33a640ce6c
|
7
|
+
data.tar.gz: 5a6328e2c45a0872098192d5a3c73224cd487c54838ab4b7524483182bbe9b4d3dcce521d7065e6ad4782a35e8da45deee988d076fb9b8167980ed8dcb2da4e5
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
guard 'rspec', :cli => "--color --format p" do
|
2
|
+
watch(%r{^spec/.+_spec\.rb$})
|
3
|
+
watch(%r{^lib/(.+)\.rb$}) { "spec" }
|
4
|
+
watch('spec/helper.rb') { "spec" }
|
5
|
+
end
|
6
|
+
|
7
|
+
guard 'cucumber', :cli => "--color --format progress" do
|
8
|
+
watch(%r{^features/.+\.feature$})
|
9
|
+
watch(%r{^features/step_definitions/.+\.rb$}) { "features" }
|
10
|
+
watch(%r{^features/step_definitions/support/.+\.rb$}) { "features" }
|
11
|
+
watch(%r{^lib/(.+)\.rb$}) { "features" }
|
12
|
+
end
|
data/History.txt
CHANGED
data/README.md
CHANGED
@@ -15,12 +15,31 @@ To install Indy use the following command:
|
|
15
15
|
|
16
16
|
(Add `sudo` if you're installing under a POSIX system as root)
|
17
17
|
|
18
|
+
Compatibility
|
19
|
+
-------------
|
20
|
+
|
21
|
+
[](http://travis-ci.org/bfaloona/Indy)
|
22
|
+
|
23
|
+
Indy supports MacOS, *nix, and MS Windows and runs on the following ruby flavors:
|
24
|
+
|
25
|
+
- 1.8.7
|
26
|
+
- 1.9.3
|
27
|
+
- 2.0.0
|
28
|
+
- ree
|
29
|
+
- jruby-19mode
|
30
|
+
- rbx-19mode
|
31
|
+
|
18
32
|
Usage
|
19
33
|
-----
|
20
34
|
|
21
|
-
##
|
35
|
+
## Example
|
22
36
|
|
23
37
|
require 'indy'
|
38
|
+
log = Indy.search(File.open('my_log.txt','r')).with(Indy::LOG4R_FORMAT)
|
39
|
+
puts log.all.first.raw_entry
|
40
|
+
# => "2012-09-07 10:01:40 INFO MyApp - Entering APPLICATION."
|
41
|
+
puts log.after(:time => '2012-09-07 20:00:00').for(:application => 'MyApp').first.message
|
42
|
+
# => "Exiting Application"
|
24
43
|
|
25
44
|
## Specify your Source
|
26
45
|
|
@@ -32,6 +51,7 @@ Usage
|
|
32
51
|
|
33
52
|
Indy.search(file_object).for(:application => 'MyApp')
|
34
53
|
Indy.search(:file => file_object).for(:application => 'MyApp')
|
54
|
+
Indy.search(:file => '/log/data.log').for(:application => 'MyApp')
|
35
55
|
|
36
56
|
### As a string
|
37
57
|
|
@@ -46,15 +66,19 @@ Usage
|
|
46
66
|
### Default Log Format
|
47
67
|
|
48
68
|
The default log format follows this form:
|
49
|
-
YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
|
50
69
|
|
51
|
-
|
70
|
+
YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
|
71
|
+
|
72
|
+
which uses this regular expression:
|
73
|
+
|
52
74
|
/^(\d{4}.\d{2}.\d{2}\s+\d{2}.\d{2}.\d{2})\s+(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s+(\w+)\s+-\s+(.+)$/
|
53
75
|
|
54
76
|
and specifies these fields:
|
77
|
+
|
55
78
|
[:time, :severity, :application, :message]
|
56
79
|
|
57
|
-
|
80
|
+
allowing searches like so:
|
81
|
+
|
58
82
|
Indy.search(log_file).for(:severity => 'INFO')
|
59
83
|
Indy.search(log_file).for(:application => 'MyApp', :severity => 'DEBUG')
|
60
84
|
|
@@ -120,10 +144,15 @@ Example:
|
|
120
144
|
|
121
145
|
By default, Indy tries to guess your time format (courtesy of DateTime#parse). If you supply an explicit time format, it will use DateTime#strptime, as well as try to guess.
|
122
146
|
|
123
|
-
This is required when log data uses a non-standard date format, e.g.: U.S. format 12-31-2000
|
147
|
+
This is required when log data uses a non-standard date format, e.g.: U.S. format 12-31-2000, and must be used in
|
148
|
+
conjunction with :entry_regexp and :entry_fields parameters.
|
124
149
|
|
125
|
-
# 12-31-2011
|
126
|
-
Indy.new(:time_format => '%m-%d-%Y
|
150
|
+
# 12-31-2011 Application starting
|
151
|
+
Indy.new( :time_format => '%m-%d-%Y',
|
152
|
+
:source => LOG_FILE,
|
153
|
+
:entry_regexp => /\d\d-\d\d-\d\d\d\d .*?/,
|
154
|
+
:entry_fields => [:time, :message]
|
155
|
+
).all
|
127
156
|
|
128
157
|
## Match Criteria
|
129
158
|
|
@@ -152,28 +181,28 @@ Multiple scope methods can be called on an instance. Use #reset_scope to remove
|
|
152
181
|
### Time Scope
|
153
182
|
|
154
183
|
# After Dec 1
|
155
|
-
Indy.search(source).after(:time => '2010-12-01 23:59:59').
|
184
|
+
Indy.search(source).after(:time => '2010-12-01 23:59:59').all
|
156
185
|
|
157
186
|
# 20 minutes Around New Year's eve
|
158
|
-
Indy.search(source).around(:time => '2011-01-01 00:00:00', :span => 20).
|
187
|
+
Indy.search(source).around(:time => '2011-01-01 00:00:00', :span => 20).all
|
159
188
|
|
160
189
|
# After Jan 1 but Before Feb 1
|
161
190
|
@log = Indy.search(source)
|
162
191
|
@log.after(:time => '2011-01-01 00:00:00').before(:time => '2011-02-01 00:00:00')
|
163
|
-
@log.
|
192
|
+
@log.all
|
164
193
|
|
165
194
|
# Within Jan 1 and Feb 1 (same time scope as above)
|
166
|
-
Indy.search(source).within(:
|
195
|
+
Indy.search(source).within(:start_time => '2011-01-01 00:00:00', :end_time =>'2011-02-01 00:00:00').all
|
167
196
|
|
168
197
|
# After Jan 1
|
169
198
|
@log = Indy.search(source)
|
170
199
|
@log.after(:time => '2011-01-01 00:00:00')
|
171
|
-
@log.
|
200
|
+
@log.all)
|
172
201
|
# Reset the time scope to include entries before Jan 1
|
173
202
|
@log.reset_scope
|
174
203
|
# Before Feb 1
|
175
204
|
@log.before(:time => '2011-02-01 00:00:00')
|
176
|
-
@log.
|
205
|
+
@log.all
|
177
206
|
|
178
207
|
## Process the Results
|
179
208
|
|
@@ -233,4 +262,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
233
262
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
234
263
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
235
264
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
236
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
265
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -20,6 +20,13 @@ RSpec::Core::RakeTask.new(:perf) do |t|
|
|
20
20
|
t.rspec_opts = ['-f d']
|
21
21
|
end
|
22
22
|
|
23
|
+
desc "Run code coverage"
|
24
|
+
task :coverage do
|
25
|
+
ENV['COVERAGE'] = '1'
|
26
|
+
Rake::Task['spec'].invoke
|
27
|
+
Rake::Task['features'].invoke
|
28
|
+
end
|
29
|
+
|
23
30
|
desc "Run features"
|
24
31
|
Cucumber::Rake::Task.new(:features) do |task|
|
25
32
|
task.cucumber_opts = ["features", "-f progress"]
|
@@ -30,6 +30,6 @@ When /^searching the log for the exact match of custom field ([^"]+)\s*"([^"]+)"
|
|
30
30
|
end
|
31
31
|
|
32
32
|
Then /^I expect the (first|last|\d+(?:st|nd|rd|th)) entry to be:$/ do |position,expected|
|
33
|
-
@results[position].
|
33
|
+
@results[position].raw_entry.should == expected
|
34
34
|
end
|
35
35
|
|
@@ -8,5 +8,5 @@ Given /^the following log:$/ do |string|
|
|
8
8
|
end
|
9
9
|
|
10
10
|
And /^the custom pattern \(([^\)]+)\):$/ do |fields,pattern|
|
11
|
-
@indy = @indy.with(
|
11
|
+
@indy = @indy.with({ :entry_regexp => pattern, :entry_fields => fields.split(',').map{|f| f.to_sym} })
|
12
12
|
end
|
@@ -4,26 +4,26 @@ When /^searching the log for the time (.+)$/ do |time|
|
|
4
4
|
end
|
5
5
|
|
6
6
|
When /^searching the log for all entries after( and including)? the time (.+)$/ do |inclusive,time|
|
7
|
-
@results = @indy.after(:time => time, :inclusive => (inclusive ? true : false)).
|
7
|
+
@results = @indy.after(:time => time, :inclusive => (inclusive ? true : false)).all
|
8
8
|
end
|
9
9
|
|
10
10
|
When /^searching the log for all entries before( and including)? the time (.+)$/ do |inclusive,time|
|
11
|
-
@results = @indy.before(:time => time, :inclusive => (inclusive ? true : false)).
|
11
|
+
@results = @indy.before(:time => time, :inclusive => (inclusive ? true : false)).all
|
12
12
|
end
|
13
13
|
|
14
14
|
When /^searching the log for all entries between( and including)? the times? (.+) and (.+)$/ do |inclusive,start,stop|
|
15
|
-
@results = @indy.within(:
|
15
|
+
@results = @indy.within(:start_time => start, :end_time => stop, :inclusive => (inclusive ? true : false)).all
|
16
16
|
end
|
17
17
|
|
18
18
|
When /^searching the log for all entries (\d+) minutes around( and including)? the time (.+)$/ do |time_span,inclusive,time|
|
19
|
-
@results = @indy.around(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).
|
19
|
+
@results = @indy.around(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).all
|
20
20
|
end
|
21
21
|
|
22
22
|
When /^searching the log for all entries (\d+) minutes after( and including)? the time (.+)$/ do |time_span,inclusive,time|
|
23
|
-
@results = @indy.after(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).
|
23
|
+
@results = @indy.after(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).all
|
24
24
|
end
|
25
25
|
|
26
26
|
When /^searching the log for all entries (\d+) minutes before( and including)? the time (.+)$/ do |time_span,inclusive,time|
|
27
|
-
@results = @indy.before(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).
|
27
|
+
@results = @indy.before(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).all
|
28
28
|
end
|
29
29
|
|
data/indy.gemspec
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
2
|
require 'indy/version'
|
3
3
|
|
4
|
-
|
5
4
|
Gem::Specification.new do |s|
|
6
5
|
s.name = 'indy'
|
7
6
|
s.version = ::Indy::VERSION
|
8
7
|
s.authors = ["Franklin Webber","Brandon Faloona"]
|
9
|
-
s.description = %{ Indy is a log
|
8
|
+
s.description = %{ Indy is a log archaeology library that treats logs like data structures. Search standard or custom log formats by field and/or time. }
|
10
9
|
s.summary = "indy-#{s.version}"
|
11
10
|
s.email = 'brandon@faloona.net'
|
12
11
|
s.homepage = "http://github.com/bfaloona/Indy"
|
@@ -16,17 +15,27 @@ Gem::Specification.new do |s|
|
|
16
15
|
s.required_ruby_version = '>= 1.8.5'
|
17
16
|
s.add_dependency('activesupport', '>= 2.3.5')
|
18
17
|
|
18
|
+
s.add_development_dependency('rake')
|
19
19
|
s.add_development_dependency('i18n')
|
20
|
-
s.add_development_dependency('cucumber', '>=
|
20
|
+
s.add_development_dependency('cucumber', '>= 1.1.0')
|
21
21
|
s.add_development_dependency('yard', '>= 0.7.2')
|
22
|
-
s.add_development_dependency('
|
23
|
-
s.add_development_dependency('rspec', '>= 2.
|
24
|
-
s.add_development_dependency('
|
25
|
-
s.add_development_dependency('
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
s.add_development_dependency('rspec', '>= 2.9.0')
|
23
|
+
s.add_development_dependency('rspec-mocks', '>= 2.9.0')
|
24
|
+
s.add_development_dependency('rb-fsevent')
|
25
|
+
s.add_development_dependency('ruby_gntp')
|
26
|
+
s.add_development_dependency('growl')
|
27
|
+
|
28
|
+
unless ENV['TRAVIS'] == 'true'
|
29
|
+
s.add_development_dependency('yard-cucumber', '>= 2.1.1')
|
30
|
+
s.add_development_dependency('flog', '>= 2.5.0')
|
31
|
+
s.add_development_dependency('guard')
|
32
|
+
unless ENV['RUBY_VERSION'] && ENV['RUBY_VERSION'].match(/jruby|rbx/)
|
33
|
+
s.add_development_dependency('guard-rspec')
|
34
|
+
s.add_development_dependency('guard-cucumber')
|
35
|
+
s.add_development_dependency('rspec-prof', '>= 0.0.3')
|
36
|
+
s.add_development_dependency('simplecov', '>= 0.4.0')
|
37
|
+
end
|
38
|
+
end
|
30
39
|
|
31
40
|
changes = Indy.show_version_changes(::Indy::VERSION)
|
32
41
|
|
@@ -43,8 +52,7 @@ Gem::Specification.new do |s|
|
|
43
52
|
|
44
53
|
}
|
45
54
|
|
46
|
-
|
47
|
-
s.rubygems_version = "1.6.1"
|
55
|
+
# s.rubygems_version = "1.6.1"
|
48
56
|
|
49
57
|
exclusions = [File.join("performance", "large.log")]
|
50
58
|
s.files = `git ls-files`.split("\n") - exclusions
|
data/lib/indy.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
begin
|
2
2
|
require 'simplecov'
|
3
|
-
|
3
|
+
if ENV["COVERAGE"]
|
4
|
+
SimpleCov.start do
|
5
|
+
add_filter "/spec/"
|
6
|
+
add_filter "/performance/"
|
7
|
+
add_filter "/features/"
|
8
|
+
end
|
9
|
+
end
|
4
10
|
rescue Exception => e
|
5
11
|
# ignore
|
6
12
|
end
|
@@ -8,7 +14,9 @@ end
|
|
8
14
|
$:.unshift(File.dirname(__FILE__)) unless
|
9
15
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
10
16
|
|
17
|
+
require 'indy/log_definition'
|
11
18
|
require 'indy/source'
|
19
|
+
require 'indy/search'
|
12
20
|
require 'indy/indy'
|
13
|
-
require 'indy/result_set'
|
14
21
|
require 'indy/log_formats'
|
22
|
+
require 'indy/time'
|
data/lib/indy/indy.rb
CHANGED
@@ -1,200 +1,126 @@
|
|
1
|
-
require 'active_support/core_ext'
|
2
|
-
|
3
1
|
class Indy
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
$VERBOSE = nil
|
8
|
-
yield block
|
9
|
-
$VERBOSE = verbose
|
10
|
-
end
|
11
|
-
|
12
|
-
# hash with one key (:string, :file, or :cmd) set to the string that defines the log
|
13
|
-
attr_accessor :source
|
14
|
-
|
15
|
-
# array with regexp string and capture groups followed by log field
|
16
|
-
# name symbols. :time field is required to use time scoping
|
17
|
-
attr_accessor :log_format
|
18
|
-
|
19
|
-
# format string for explicit date/time format (optional)
|
20
|
-
attr_accessor :time_format
|
21
|
-
|
22
|
-
# initialization flag (true || nil) to enable multiline log entries. See README
|
23
|
-
attr_accessor :multiline
|
3
|
+
# search object
|
4
|
+
attr_accessor :search
|
24
5
|
|
25
6
|
#
|
26
|
-
# Initialize Indy. Also see class method Indy.
|
7
|
+
# Initialize Indy. Also see class method Indy#search.
|
27
8
|
#
|
28
9
|
# @example
|
29
10
|
#
|
30
|
-
# Indy.new(:source => LOG_FILE)
|
31
11
|
# Indy.new(:source => LOG_CONTENTS_STRING)
|
32
12
|
# Indy.new(:source => {:cmd => LOG_COMMAND_STRING})
|
33
|
-
# Indy.new(:
|
34
|
-
# Indy.new(:time_format => '%m-%d-%Y'
|
13
|
+
# Indy.new(:entry_regexp => LOG_REGEX_PATTERN, :entry_fields => [:time,:application,:message], :source => LOG_FILE)
|
14
|
+
# Indy.new(:time_format => '%m-%d-%Y', :entry_regexp => LOG_REGEX_PATTERN, :entry_fields => [:time,:application,:message], :source => LOG_FILE)
|
35
15
|
#
|
36
16
|
def initialize(args)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
#
|
48
|
-
# Create an Indy::Source object to manage the log source
|
49
|
-
#
|
50
|
-
# @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
|
51
|
-
#
|
52
|
-
def source=(param)
|
53
|
-
@source = Source.new(param)
|
17
|
+
params_hash = args.dup
|
18
|
+
raise ArgumentError, "Source parameter not specified" unless (params_hash.respond_to?(:keys) && params_hash.keys.include?(:source))
|
19
|
+
source_param = params_hash[:source]
|
20
|
+
params_hash.delete :source
|
21
|
+
log_definition = LogDefinition.new(params_hash)
|
22
|
+
@search = Search.new(:log_definition => log_definition)
|
23
|
+
@search.source = Source.new(source_param,log_definition)
|
54
24
|
end
|
55
25
|
|
56
26
|
class << self
|
57
27
|
|
58
28
|
#
|
59
|
-
# Create a new instance of Indy
|
60
|
-
# specified. This allows for a more fluent creation that moves
|
61
|
-
# into the execution.
|
29
|
+
# Create a new instance of Indy specifying source, or multiple parameters.
|
62
30
|
#
|
63
|
-
# @param [String,Hash] params To specify
|
64
|
-
#
|
65
|
-
# Alternately, a
|
66
|
-
# provide multiple initialization parameters.
|
31
|
+
# @param [String,Hash] params To specify a source directly, provide log contents
|
32
|
+
# as a string. Using a hash you can specify source with a :cmd or :file key.
|
33
|
+
# Alternately, a hash with a :source key (among others) can be used to
|
34
|
+
# provide multiple initialization parameters. See Indy#new.
|
67
35
|
#
|
68
|
-
# @example filename source
|
69
|
-
# Indy.search("apache.log").for(:severity => "INFO")
|
70
|
-
#
|
71
36
|
# @example string source
|
72
|
-
# Indy.search("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.").
|
37
|
+
# Indy.search("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.").all
|
73
38
|
#
|
74
39
|
# @example command source
|
75
|
-
# Indy.search(:cmd => "cat apache.log").
|
40
|
+
# Indy.search(:cmd => "cat apache.log").all
|
41
|
+
#
|
42
|
+
# @example file source
|
43
|
+
# Indy.search(:file => "/logs/apache.log").all
|
76
44
|
#
|
77
|
-
# @example source as well as other
|
78
|
-
# Indy.search(:source => {:cmd => "cat apache.log"}, :
|
45
|
+
# @example source as well as other parameters
|
46
|
+
# Indy.search(:source => {:cmd => "cat apache.log"}, :entry_regexp => REGEXP, :entry_fields => [:field_one, :field_two], :time_format => MY_TIME_FORMAT).all
|
79
47
|
#
|
80
48
|
def search(params=nil)
|
81
|
-
|
82
49
|
if params.respond_to?(:keys) && params[:source]
|
83
50
|
Indy.new(params)
|
84
51
|
else
|
85
|
-
Indy.new(:source => params, :
|
52
|
+
Indy.new(:source => params, :entry_regexp => LogFormats::DEFAULT_ENTRY_REGEXP, :entry_fields => LogFormats::DEFAULT_ENTRY_FIELDS)
|
86
53
|
end
|
87
54
|
end
|
88
55
|
|
89
|
-
#
|
90
|
-
# Return a Struct::Line object from a hash of values from a log entry
|
91
|
-
#
|
92
|
-
# @param [Hash] line_hash a hash of :field_name => value pairs for one log line
|
93
|
-
#
|
94
|
-
def create_struct( line_hash )
|
95
|
-
params = line_hash.keys.sort_by{|e|e.to_s}.collect {|k| line_hash[k]}
|
96
|
-
Struct::Line.new( *params )
|
97
|
-
end
|
98
|
-
|
99
56
|
end
|
100
57
|
|
101
|
-
|
102
58
|
#
|
103
|
-
# Specify the log format to use as the comparison against each
|
59
|
+
# Specify the log format to use as the comparison against each entry within
|
104
60
|
# the log file that has been specified.
|
105
61
|
#
|
106
|
-
# @param [Array]
|
62
|
+
# @param [Array,LogDefinition] log_definition either a LogDefinition object or an Array with the regular expression as the first element
|
107
63
|
# followed by list of fields (Symbols) in the log entry
|
108
|
-
# to use for comparison against each log
|
64
|
+
# to use for comparison against each log entry.
|
109
65
|
#
|
110
66
|
# @example Log formatted as - HH:MM:SS Message
|
111
67
|
#
|
112
68
|
# Indy.search(LOG_FILE).with(/^(\d{2}.\d{2}.\d{2})\s*(.+)$/,:time,:message)
|
113
69
|
#
|
114
|
-
def with(
|
115
|
-
|
70
|
+
def with(params=:default)
|
71
|
+
@search.log_definition = LogDefinition.new(params)
|
116
72
|
self
|
117
73
|
end
|
118
|
-
|
74
|
+
|
119
75
|
#
|
120
|
-
#
|
76
|
+
# Return all entries
|
121
77
|
#
|
122
|
-
|
123
|
-
|
124
|
-
# supports symbol :all to return all messages
|
125
|
-
#
|
126
|
-
def for(search_criteria)
|
127
|
-
results = ResultSet.new
|
128
|
-
case search_criteria
|
129
|
-
when Enumerable
|
130
|
-
results += _search do |result|
|
131
|
-
result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
|
132
|
-
yield result_struct if block_given? and result_struct
|
133
|
-
result_struct
|
134
|
-
end
|
135
|
-
|
136
|
-
when :all
|
137
|
-
results += _search do |result|
|
138
|
-
result_struct = Indy.create_struct(result)
|
139
|
-
yield result_struct if block_given?
|
140
|
-
result_struct
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
results.compact
|
78
|
+
def all(&block)
|
79
|
+
@search.iterate_and_compare(:all,nil,&block)
|
145
80
|
end
|
146
81
|
|
82
|
+
#
|
83
|
+
# Search the source and make an == comparison
|
84
|
+
#
|
85
|
+
# @param [Hash] search_criteria the field to search for as the key and the
|
86
|
+
# value to compare against the log entries.
|
87
|
+
#
|
88
|
+
def for(search_criteria,&block)
|
89
|
+
@search.iterate_and_compare(:for,search_criteria,&block)
|
90
|
+
end
|
147
91
|
|
148
92
|
#
|
149
93
|
# Search the source and make a regular expression comparison
|
150
94
|
#
|
151
95
|
# @param [Hash] search_criteria the field to search for as the key and the
|
152
|
-
#
|
96
|
+
# value to compare against the log entries.
|
97
|
+
# The value will be treated as a regular expression.
|
153
98
|
#
|
154
99
|
# @example For all applications that end with Service
|
155
100
|
#
|
156
101
|
# Indy.search(LOG_FILE).like(:application => '.+service')
|
157
102
|
#
|
158
|
-
def like(search_criteria)
|
159
|
-
|
160
|
-
|
161
|
-
results += _search do |result|
|
162
|
-
result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/i }.empty?
|
163
|
-
yield result_struct if block_given? and result_struct
|
164
|
-
result_struct
|
165
|
-
end
|
166
|
-
|
167
|
-
results.compact
|
103
|
+
def like(search_criteria,&block)
|
104
|
+
@search.iterate_and_compare(:like,search_criteria,&block)
|
168
105
|
end
|
169
|
-
|
170
106
|
alias_method :matching, :like
|
171
107
|
|
172
|
-
|
173
108
|
#
|
174
|
-
# Scopes the eventual search to the last N
|
109
|
+
# Scopes the eventual search to the last N minutes of entries.
|
175
110
|
#
|
176
111
|
# @param [Hash] scope_criteria hash describing the amount of time at
|
177
|
-
#
|
112
|
+
# the last portion of the source
|
178
113
|
#
|
179
114
|
# @example For last 10 minutes worth of entries
|
180
115
|
#
|
181
|
-
# Indy.search(LOG_FILE).last(:span =>
|
116
|
+
# Indy.search(LOG_FILE).last(:span => 10).all
|
182
117
|
#
|
183
118
|
def last(scope_criteria)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
span = (scope_criteria[:span].to_i * 60).seconds
|
190
|
-
starttime = parse_date(last_entry[:_time]) - span
|
191
|
-
|
192
|
-
within(:time => [starttime, forever])
|
193
|
-
end
|
194
|
-
else
|
195
|
-
raise ArgumentError, "Invalid parameter: #{scope_criteria.inspect}"
|
196
|
-
end
|
197
|
-
|
119
|
+
raise ArgumentError, "Unsupported parameter to last(): #{scope_criteria.inspect}" unless scope_criteria.respond_to?(:keys) and scope_criteria[:span]
|
120
|
+
span = (scope_criteria[:span].to_i * 60).seconds
|
121
|
+
entry = last_entries(1)[0]
|
122
|
+
start_time = Indy::Time.parse_date(entry[:time],@search.log_definition.time_format) - span
|
123
|
+
within(:start_time => start_time, :end_time => Indy::Time.forever(@search.log_definition.time_format))
|
198
124
|
self
|
199
125
|
end
|
200
126
|
|
@@ -207,32 +133,14 @@ class Indy
|
|
207
133
|
#
|
208
134
|
# @example For all messages after specified date
|
209
135
|
#
|
210
|
-
# Indy.search(LOG_FILE).after(:time => time).
|
136
|
+
# Indy.search(LOG_FILE).after(:time => time).all
|
211
137
|
#
|
212
138
|
def after(scope_criteria)
|
213
|
-
|
214
|
-
|
215
|
-
@inclusive = @inclusive || scope_criteria[:inclusive] || nil
|
216
|
-
|
217
|
-
if scope_criteria[:span]
|
218
|
-
span = (scope_criteria[:span].to_i * 60).seconds
|
219
|
-
within(:time => [time, time + span])
|
220
|
-
else
|
221
|
-
@start_time = time
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
139
|
+
params = scope_criteria.merge({:direction => :after})
|
140
|
+
within(params)
|
225
141
|
self
|
226
142
|
end
|
227
143
|
|
228
|
-
#
|
229
|
-
# Removes any existing start and end times from the instance
|
230
|
-
# Otherwise consecutive search calls retain time scope state
|
231
|
-
#
|
232
|
-
def reset_scope
|
233
|
-
@inclusive = @start_time = @end_time = nil
|
234
|
-
end
|
235
|
-
|
236
144
|
#
|
237
145
|
# Scopes the eventual search to all entries prior to this point.
|
238
146
|
#
|
@@ -241,36 +149,25 @@ class Indy
|
|
241
149
|
#
|
242
150
|
# @example For all messages before specified date
|
243
151
|
#
|
244
|
-
# Indy.search(LOG_FILE).before(:time => time).
|
245
|
-
# Indy.search(LOG_FILE).before(:time => time, :span => 10).
|
152
|
+
# Indy.search(LOG_FILE).before(:time => time).all
|
153
|
+
# Indy.search(LOG_FILE).before(:time => time, :span => 10).all
|
246
154
|
#
|
247
155
|
def before(scope_criteria)
|
248
|
-
|
249
|
-
|
250
|
-
@inclusive = @inclusive || scope_criteria[:inclusive] || nil
|
251
|
-
|
252
|
-
if scope_criteria[:span]
|
253
|
-
span = (scope_criteria[:span].to_i * 60).seconds
|
254
|
-
within(:time => [time - span, time], :inclusive => scope_criteria[:inclusive])
|
255
|
-
else
|
256
|
-
@end_time = time
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
self
|
156
|
+
params = scope_criteria.merge({:direction => :before})
|
157
|
+
within(params)
|
261
158
|
end
|
262
159
|
|
160
|
+
#
|
161
|
+
# Scopes the eventual search to all entries near this point.
|
162
|
+
#
|
163
|
+
# @param [Hash] scope_criteria the hash containing :time and :span (in minutes) to scope the log.
|
164
|
+
# :span defaults to 5 minutes.
|
165
|
+
#
|
263
166
|
def around(scope_criteria)
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
warn "Ignoring inclusive scope_criteria" if scope_criteria[:inclusive]
|
269
|
-
|
270
|
-
half_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
|
271
|
-
within(:time => [time - half_span, time + half_span])
|
272
|
-
end
|
273
|
-
|
167
|
+
raise ArgumentError unless scope_criteria.respond_to?(:keys) and scope_criteria[:time]
|
168
|
+
time = Indy::Time.parse_date(scope_criteria[:time])
|
169
|
+
mid_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
|
170
|
+
within(:start_time => time - mid_span, :end_time => time + mid_span, :inclusive => nil)
|
274
171
|
self
|
275
172
|
end
|
276
173
|
|
@@ -278,264 +175,46 @@ class Indy
|
|
278
175
|
#
|
279
176
|
# Scopes the eventual search to all entries between two times.
|
280
177
|
#
|
281
|
-
# @param [Hash]
|
282
|
-
# value to compare against the other log messages
|
178
|
+
# @param [Hash] params the :start_time, :end_time and :inclusive key/value pairs
|
283
179
|
#
|
284
180
|
# @example For all messages within the specified dates
|
285
181
|
#
|
286
|
-
# Indy.search(LOG_FILE).within(:
|
182
|
+
# Indy.search(LOG_FILE).within(:start_time => start_time, :end_time => end_time, :inclusive => true).all
|
287
183
|
#
|
288
|
-
def within(
|
289
|
-
|
290
|
-
@start_time, @end_time = scope_criteria[:time].collect {|str| parse_date(str) }
|
291
|
-
|
292
|
-
@inclusive = @inclusive || scope_criteria[:inclusive] || nil
|
293
|
-
end
|
294
|
-
|
184
|
+
def within(params)
|
185
|
+
@search.time_scope(params)
|
295
186
|
self
|
296
187
|
end
|
297
188
|
|
298
|
-
|
299
|
-
private
|
300
|
-
|
301
|
-
#
|
302
|
-
# Set @pattern as well as @log_regexp, @log_fields, and @time_field
|
303
|
-
#
|
304
|
-
# @param [Array] pattern_array an Array with the regular expression as the first element
|
305
|
-
# followed by list of fields (Symbols) in the log entry
|
306
|
-
# to use for comparison against each log line.
|
307
|
-
#
|
308
|
-
def update_log_format( log_format )
|
309
|
-
|
310
|
-
case log_format
|
311
|
-
when :default, nil
|
312
|
-
@log_format = DEFAULT_LOG_FORMAT
|
313
|
-
else
|
314
|
-
@log_format = log_format
|
315
|
-
end
|
316
|
-
|
317
|
-
@log_regexp, *@log_fields = @log_format
|
318
|
-
|
319
|
-
@time_field = ( @log_fields.include?(:time) ? :time : nil )
|
320
|
-
|
321
|
-
# now that we know the fields
|
322
|
-
define_struct
|
323
|
-
|
324
|
-
end
|
325
|
-
|
326
|
-
#
|
327
|
-
# Search the @source and yield to the block the line that was found
|
328
|
-
# with @log_regexp and @log_fields
|
329
|
-
#
|
330
|
-
# This method is supposed to be used internally.
|
331
|
-
#
|
332
|
-
def _search(&block)
|
333
|
-
|
334
|
-
line_matched = nil
|
335
|
-
time_search = use_time_criteria?
|
336
|
-
|
337
|
-
source_io = @source.open(time_search)
|
338
|
-
|
339
|
-
if @multiline
|
340
|
-
results = source_io.read.scan(Regexp.new(@log_regexp, Regexp::MULTILINE)).collect do |entry|
|
341
|
-
|
342
|
-
hash = parse_line(entry)
|
343
|
-
hash ? (line_matched = true) : next
|
344
|
-
|
345
|
-
if time_search
|
346
|
-
set_time(hash)
|
347
|
-
next unless inside_time_window?(hash)
|
348
|
-
else
|
349
|
-
hash[:_time] = nil if hash
|
350
|
-
end
|
351
|
-
|
352
|
-
block_given? ? block.call(hash) : nil
|
353
|
-
end
|
354
|
-
|
355
|
-
else
|
356
|
-
results = source_io.collect do |line|
|
357
|
-
hash = parse_line(line)
|
358
|
-
hash ? (line_matched = true) : next
|
359
|
-
|
360
|
-
if time_search
|
361
|
-
set_time(hash)
|
362
|
-
next unless inside_time_window?(hash)
|
363
|
-
else
|
364
|
-
hash[:_time] = nil if hash
|
365
|
-
end
|
366
|
-
|
367
|
-
block_given? ? block.call(hash) : nil
|
368
|
-
end
|
369
|
-
|
370
|
-
end
|
371
|
-
|
372
|
-
# warn "No matching lines found in source: #{source_io.class}" unless line_matched
|
373
|
-
|
374
|
-
results.compact
|
375
|
-
end
|
376
|
-
|
377
|
-
#
|
378
|
-
# Return a hash of field=>value pairs for the log line
|
379
|
-
#
|
380
|
-
# @param [String] line The log line
|
381
|
-
# @param [Array] pattern_array The match regexp string, followed by log fields
|
382
|
-
# see Class method search
|
383
|
-
#
|
384
|
-
def parse_line( line )
|
385
|
-
|
386
|
-
if line.kind_of? String
|
387
|
-
match_data = nil
|
388
|
-
Indy.suppress_warnings { match_data = /#{@log_regexp}/.match(line) }
|
389
|
-
return nil unless match_data
|
390
|
-
|
391
|
-
values = match_data.captures
|
392
|
-
entire_line = line.strip
|
393
|
-
|
394
|
-
elsif line.kind_of? Enumerable
|
395
|
-
|
396
|
-
entire_line = line.shift
|
397
|
-
values = line
|
398
|
-
end
|
399
|
-
|
400
|
-
raise "Field mismatch between log pattern and log data. The data is: '#{values.join(':::')}'" unless values.length == @log_fields.length
|
401
|
-
|
402
|
-
hash = Hash[ *@log_fields.zip( values ).flatten ]
|
403
|
-
hash[:line] = entire_line.strip
|
404
|
-
|
405
|
-
hash
|
406
|
-
end
|
407
|
-
|
408
|
-
#
|
409
|
-
# Return true if start or end time has been set, and a :time field exists
|
410
|
-
#
|
411
|
-
def use_time_criteria?
|
412
|
-
if @start_time || @end_time
|
413
|
-
# ensure both boundaries are set
|
414
|
-
@start_time = @start_time || forever_ago
|
415
|
-
@end_time = @end_time || forever
|
416
|
-
end
|
417
|
-
|
418
|
-
return (@time_field && @start_time && @end_time)
|
419
|
-
end
|
420
|
-
|
421
|
-
|
422
|
-
#
|
423
|
-
# Set the :_time value in the hash
|
424
|
-
#
|
425
|
-
# @param [Hash] hash The log line hash to modify
|
426
|
-
#
|
427
|
-
def set_time(hash)
|
428
|
-
hash[:_time] = parse_date( hash ) if hash
|
429
|
-
end
|
430
|
-
|
431
|
-
#
|
432
|
-
# Evaluate if a log line satisfies the configured time conditions
|
433
|
-
#
|
434
|
-
# @param [Hash] line_hash The log line hash to be evaluated
|
435
|
-
#
|
436
|
-
def inside_time_window?( line_hash )
|
437
|
-
|
438
|
-
if line_hash && line_hash[:_time]
|
439
|
-
if @inclusive
|
440
|
-
true unless line_hash[:_time] > @end_time or line_hash[:_time] < @start_time
|
441
|
-
else
|
442
|
-
true unless line_hash[:_time] >= @end_time or line_hash[:_time] <= @start_time
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
end
|
447
|
-
|
448
|
-
#
|
449
|
-
# Return a valid DateTime object for the log line or string
|
450
|
-
#
|
451
|
-
# @param [String, Hash] param The log line hash, or string to be evaluated
|
452
|
-
#
|
453
|
-
def parse_date(param)
|
454
|
-
return nil unless @time_field
|
455
|
-
return param if param.kind_of? Time or param.kind_of? DateTime
|
456
|
-
|
457
|
-
time_string = param.is_a?(Hash) ? param[@time_field] : param.to_s
|
458
|
-
|
459
|
-
if @time_format
|
460
|
-
begin
|
461
|
-
# Attempt the appropriate parse method
|
462
|
-
DateTime.strptime(time_string, @time_format)
|
463
|
-
rescue
|
464
|
-
# If appropriate, fall back to simple parse method
|
465
|
-
DateTime.parse(time_string) rescue nil
|
466
|
-
end
|
467
|
-
else
|
468
|
-
begin
|
469
|
-
Time.parse(time_string)
|
470
|
-
rescue Exception => e
|
471
|
-
raise "Failed to create time object. The error was: #{e.message}"
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
end
|
476
|
-
|
477
|
-
#
|
478
|
-
# Return a time or datetime object way in the future
|
479
|
-
#
|
480
|
-
def forever
|
481
|
-
@time_format ? DateTime.new(4712) : Time.at(0x7FFFFFFF)
|
482
|
-
end
|
483
|
-
|
484
189
|
#
|
485
|
-
#
|
190
|
+
# Removes any existing start and end times from the instance
|
191
|
+
# Otherwise consecutive search calls retain time scope state
|
486
192
|
#
|
487
|
-
def
|
488
|
-
|
489
|
-
@time_format ? DateTime.new(-4712) : Time.at(-0x7FFFFFFF)
|
490
|
-
rescue
|
491
|
-
# Windows Ruby Time can't handle dates prior to 1969
|
492
|
-
@time_format ? DateTime.new(-4712) : Time.at(0)
|
493
|
-
end
|
193
|
+
def reset_scope
|
194
|
+
@search.reset_scope
|
494
195
|
end
|
495
196
|
|
496
|
-
#
|
497
|
-
# Define Struct::Line with the fields configured with @pattern
|
498
|
-
#
|
499
|
-
def define_struct
|
500
|
-
fields = (@log_fields + [:_time, :line]).sort_by{|e|e.to_s}
|
501
|
-
Indy.suppress_warnings { Struct.new( "Line", *fields ) }
|
502
|
-
end
|
503
197
|
|
504
|
-
|
505
|
-
# Return a Struct::Line for the last valid entry from the source
|
506
|
-
#
|
507
|
-
def last_entry
|
508
|
-
last_entries(1)
|
509
|
-
end
|
198
|
+
private
|
510
199
|
|
511
200
|
#
|
512
|
-
# Return an array of Struct::
|
201
|
+
# Return an array of Struct::Entry objects for the last N valid entries from the source
|
513
202
|
#
|
514
|
-
# @param [Fixnum] num the number of
|
203
|
+
# @param [Fixnum] num the number of entries to retrieve
|
515
204
|
#
|
516
205
|
def last_entries(num)
|
517
|
-
|
518
206
|
num_entries = 0
|
519
207
|
result = []
|
520
|
-
|
521
|
-
source_io
|
522
|
-
|
523
|
-
source_io.reverse_each do |line|
|
524
|
-
|
525
|
-
hash = parse_line(line)
|
526
|
-
|
527
|
-
set_time(hash) if @time_field
|
528
|
-
|
208
|
+
source_io = @search.source.open
|
209
|
+
source_io.reverse_each do |entry|
|
210
|
+
hash = @search.log_definition.parse_entry(entry)
|
529
211
|
if hash
|
530
212
|
num_entries += 1
|
531
213
|
result << hash
|
532
214
|
break if num_entries >= num
|
533
215
|
end
|
534
216
|
end
|
535
|
-
|
536
|
-
warn "#last_entries found no matching lines in source." if result.empty?
|
537
|
-
|
538
|
-
num == 1 ? Indy.create_struct(result.first) : result.collect{|e| Indy.create_struct(e)}
|
217
|
+
result.collect{|entry| @search.log_definition.create_struct(entry)}
|
539
218
|
end
|
540
219
|
|
541
220
|
end
|