indy 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/README.md +29 -22
- data/Rakefile +6 -12
- data/features/file.feature +1 -1
- data/features/step_definitions/find_by.steps.rb +0 -12
- data/features/step_definitions/log_file.steps.rb +4 -0
- data/features/step_definitions/support/env.rb +4 -0
- data/features/step_definitions/support/transforms.rb +0 -4
- data/indy.gemspec +3 -2
- data/lib/indy.rb +10 -7
- data/lib/indy/formats.rb +3 -0
- data/lib/indy/indy.rb +112 -189
- data/lib/indy/log_formats.rb +26 -4
- data/lib/indy/notes.txt +9 -0
- data/lib/indy/result_set.rb +8 -8
- data/lib/indy/source.rb +129 -0
- data/lib/scanf.rb +13 -0
- data/performance/profile_spec.rb +3 -3
- data/spec/helper.rb +5 -1
- data/spec/indy_spec.rb +128 -56
- data/spec/log_format_spec.rb +11 -11
- data/spec/multiline.log +10 -0
- data/spec/search_spec.rb +2 -25
- data/spec/source_spec.rb +85 -0
- data/spec/time_spec.rb +9 -4
- metadata +26 -70
- data/lib/indy/patterns.rb +0 -25
data/History.txt
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
=== 0.3.0 / 2011-08-02
|
2
|
+
|
3
|
+
* To search a file, specify a File object as the source.
|
4
|
+
(Support for file paths will be restored in the future.)
|
5
|
+
* Use :log_format as the key to specify your custom format and fields (:pattern is deprecated).
|
6
|
+
|
1
7
|
=== 0.2.0 / 2011-03-08
|
2
8
|
|
3
9
|
* Support for Multiline log entries. See README.md
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Indy: A Log Archaeology Tool
|
|
4
4
|
Synopsis
|
5
5
|
--------
|
6
6
|
|
7
|
-
Log files are often searched for particular strings but
|
7
|
+
Log files are often searched for particular strings but are not often treated as data structures. Indy attempts to deliver log content via more powerful features by allowing the ability to collect segments of a log from particular time; find a particular event; or monitor/reflect on a log to see if a particular event occurred (or not occurred).
|
8
8
|
|
9
9
|
Installation
|
10
10
|
------------
|
@@ -30,7 +30,8 @@ Usage
|
|
30
30
|
|
31
31
|
### As a file
|
32
32
|
|
33
|
-
Indy.search(
|
33
|
+
Indy.search(file_object).for(:application => 'MyApp')
|
34
|
+
Indy.search(:file => file_object).for(:application => 'MyApp')
|
34
35
|
|
35
36
|
### As a string
|
36
37
|
|
@@ -42,42 +43,47 @@ Usage
|
|
42
43
|
|
43
44
|
## Log Pattern
|
44
45
|
|
45
|
-
### Default Log
|
46
|
+
### Default Log Format
|
46
47
|
|
47
|
-
The default
|
48
|
+
The default log format follows this form:
|
48
49
|
YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
|
49
50
|
|
50
|
-
Which uses this
|
51
|
+
Which uses this Regexp:
|
51
52
|
/^(\d{4}.\d{2}.\d{2}\s+\d{2}.\d{2}.\d{2})\s+(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s+(\w+)\s+-\s+(.+)$/
|
52
53
|
|
53
54
|
and specifies these fields:
|
54
55
|
[:time, :severity, :application, :message]
|
55
56
|
|
56
57
|
For example:
|
57
|
-
Indy.search(
|
58
|
-
Indy.search(
|
58
|
+
Indy.search(log_file).for(:severity => 'INFO')
|
59
|
+
Indy.search(log_file).for(:application => 'MyApp', :severity => 'DEBUG')
|
59
60
|
|
60
|
-
### Custom Log
|
61
|
+
### Custom Log Format
|
61
62
|
|
62
|
-
If
|
63
|
-
To do so, specify a pattern
|
63
|
+
If you have a different log format you can brew your own.
|
64
|
+
To do so, specify a Regexp pattern that captures each field you want to reference.
|
65
|
+
Include it as the first item of your log format array, followed by a list of symbols that name the captured fields.
|
64
66
|
|
67
|
+
# If your log format is:
|
65
68
|
# HH:MM:SS SEVERITY APPLICATION#METHOD - MESSAGE
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
+
# Build an appropriate regexp
|
70
|
+
custom_regexp = /^(\d{2}:\d{2}:\d{2})\s*(INFO|DEBUG|WARN|ERROR)\s*([^#]+)#([^\s]+)\s*-\s*(.+)$/
|
71
|
+
# Combine the pattern and the list of fields
|
72
|
+
custom_log_format = [custom_regexp,:time,:severity,:application,:method,:message]
|
73
|
+
# Use Indy#with to define your format
|
74
|
+
Indy.search(source).with(custom_log_format).for(:severity => 'INFO', :method => 'allocate')
|
69
75
|
|
70
|
-
### Predefined Log
|
76
|
+
### Predefined Log Format
|
71
77
|
|
72
|
-
Several log formats have been predefined for ease of configuration. See indy/
|
78
|
+
Several log formats have been predefined for ease of configuration. See indy/formats.rb
|
73
79
|
|
74
|
-
# Indy::
|
75
|
-
# Indy::
|
76
|
-
# Indy::
|
80
|
+
# Indy::COMMON_LOG_FORMAT
|
81
|
+
# Indy::COMBINED_LOG_FORMAT
|
82
|
+
# Indy::LOG4R_DEFAULT_FORMAT
|
77
83
|
#
|
78
84
|
# Example (Log4r)
|
79
85
|
# INFO mylog: This is a message with level INFO
|
80
|
-
Indy.new(:source =>
|
86
|
+
Indy.new(:source => log_file, :log_format => Indy::LOG4R_DEFAULT_FORMAT).for(:application => 'mylog')
|
81
87
|
|
82
88
|
### Multiline log entries
|
83
89
|
|
@@ -97,9 +103,10 @@ Example:
|
|
97
103
|
|
98
104
|
# Given this log containing two entries:
|
99
105
|
#
|
100
|
-
# INFO MyApp - Multiline message begins here
|
106
|
+
# INFO MyApp - Multiline message begins here...
|
101
107
|
# and ends here
|
102
|
-
# DEBUG MyOtherApp - Single line message
|
108
|
+
# DEBUG MyOtherApp - Single line message.
|
109
|
+
# WARN MyOtherApp - Another single line message.
|
103
110
|
|
104
111
|
severity_string = 'DEBUG|INFO|WARN|ERROR|FATAL'
|
105
112
|
|
@@ -107,7 +114,7 @@ Example:
|
|
107
114
|
# /^(#{severity_string}) (\w+) - (.*)$/
|
108
115
|
multiline_regexp = /^(#{severity_string}) (\w+) - (.*?)(?=^#{severity_string}|\z)/
|
109
116
|
|
110
|
-
Indy.new( :multiline => true, :
|
117
|
+
Indy.new( :multiline => true, :log_format => [multiline_regexp, :severity, :application, :message], :source => MY_LOG)
|
111
118
|
|
112
119
|
### Explicit Time Format
|
113
120
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rspec/core'
|
3
3
|
require 'rspec/core/rake_task'
|
4
|
-
require 'rcov/rcovtask'
|
5
4
|
require "cucumber/rake/task"
|
6
5
|
require "yard"
|
7
6
|
|
7
|
+
|
8
8
|
desc 'Default: run tests'
|
9
9
|
task :default => :test
|
10
10
|
|
@@ -25,12 +25,10 @@ Cucumber::Rake::Task.new(:features) do |task|
|
|
25
25
|
task.cucumber_opts = ["features", "-f progress"]
|
26
26
|
end
|
27
27
|
|
28
|
-
desc "Run
|
28
|
+
desc "Run perf, flog, specs and features"
|
29
29
|
task :all do
|
30
30
|
puts "\nProfiling report"
|
31
31
|
Rake::Task['perf'].invoke
|
32
|
-
puts "\nCode Coverage report"
|
33
|
-
Rake::Task['coverage'].invoke
|
34
32
|
puts "\nFlog results"
|
35
33
|
Rake::Task['flog'].invoke
|
36
34
|
puts "\nSpec Tests"
|
@@ -45,6 +43,7 @@ task :test do
|
|
45
43
|
Rake::Task['features'].invoke
|
46
44
|
end
|
47
45
|
|
46
|
+
|
48
47
|
desc "Flog the code! (*nix only)"
|
49
48
|
task :flog do
|
50
49
|
system('find lib -name \*.rb | xargs flog')
|
@@ -55,16 +54,11 @@ task :flog_detail do
|
|
55
54
|
system('find lib -name \*.rb | xargs flog -d')
|
56
55
|
end
|
57
56
|
|
58
|
-
# Task :rcov -- Run RCOV to Generate code coverage report
|
59
|
-
Rcov::RcovTask.new do |t|
|
60
|
-
t.libs << "lib"
|
61
|
-
t.test_files = FileList['spec/*.rb']
|
62
|
-
t.rcov_opts = ['--exclude', 'spec', '--exclude', 'gems', '-T']
|
63
|
-
t.verbose = true
|
64
|
-
end
|
65
57
|
|
66
58
|
# Task :yard -- Generate yard + yard-cucumber docs
|
67
59
|
YARD::Rake::YardocTask.new do |t|
|
68
60
|
t.files = ['features/**/*', 'lib/**/*.rb']
|
69
61
|
t.options = ['--private']
|
70
|
-
end
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "\nTo create a report in /coverage, execute:\nCOVERAGE=true rake test\n\n"
|
data/features/file.feature
CHANGED
@@ -29,18 +29,6 @@ When /^searching the log for the exact match of custom field ([^"]+)\s*"([^"]+)"
|
|
29
29
|
@results = @indy.for(field.strip.gsub(/\s/,'_').to_sym => value)
|
30
30
|
end
|
31
31
|
|
32
|
-
When /^searching the log for entries in the (\w+ \w+), by (.+)$/ do |portion, method|
|
33
|
-
method = method.intern
|
34
|
-
case portion
|
35
|
-
when /(second|last) half/
|
36
|
-
@results = @indy.last(:half, method)
|
37
|
-
when 'first half'
|
38
|
-
@results = @indy.first(:half, method)
|
39
|
-
else
|
40
|
-
pending
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
32
|
Then /^I expect the (first|last|\d+(?:st|nd|rd|th)) entry to be:$/ do |position,expected|
|
45
33
|
@results[position].line.should == expected
|
46
34
|
end
|
@@ -18,10 +18,6 @@ Transform /^last$/ do |order|
|
|
18
18
|
-1
|
19
19
|
end
|
20
20
|
|
21
|
-
Transform /^(\d+)(?:st|nd|rd|th)$/ do |order|
|
22
|
-
order.to_i - 1
|
23
|
-
end
|
24
|
-
|
25
21
|
# When searching the log for all entries after and including the time 2000-09-07 14:07:44
|
26
22
|
Transform /^ and including$/ do |inclusive|
|
27
23
|
true
|
data/indy.gemspec
CHANGED
@@ -41,12 +41,13 @@ Gem::Specification.new do |s|
|
|
41
41
|
s.add_dependency('activesupport', '>= 2.3.5')
|
42
42
|
|
43
43
|
s.add_development_dependency('cucumber', '>= 0.10.0')
|
44
|
-
s.add_development_dependency('yard', '>= 0.
|
44
|
+
s.add_development_dependency('yard', '>= 0.7.2')
|
45
45
|
s.add_development_dependency('yard-cucumber', '>= 2.0.0')
|
46
46
|
s.add_development_dependency('rspec', '>= 2.4.0')
|
47
47
|
s.add_development_dependency('rspec-mocks', '>= 2.4.0')
|
48
48
|
s.add_development_dependency('rspec-prof', '>= 0.0.3')
|
49
|
-
s.add_development_dependency('
|
49
|
+
s.add_development_dependency('simplecov', '>= 0.4.0')
|
50
|
+
s.add_development_dependency('ruby-debug19', '>= 0.11.0')
|
50
51
|
s.add_development_dependency('flog', '>= 2.5.0')
|
51
52
|
|
52
53
|
changes = Indy.show_version_changes(::Indy::VERSION)
|
data/lib/indy.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require 'indy/
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start if ENV["COVERAGE"]
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
5
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
6
|
+
|
7
|
+
require 'indy/source'
|
8
|
+
require 'indy/indy'
|
9
|
+
require 'indy/result_set'
|
10
|
+
require 'indy/log_formats'
|
data/lib/indy/formats.rb
ADDED
data/lib/indy/indy.rb
CHANGED
@@ -2,58 +2,59 @@ require 'active_support/core_ext'
|
|
2
2
|
|
3
3
|
class Indy
|
4
4
|
|
5
|
-
|
5
|
+
def self.suppress_warnings(&block)
|
6
|
+
verbose = $VERBOSE
|
7
|
+
$VERBOSE = nil
|
8
|
+
yield block
|
9
|
+
$VERBOSE = verbose
|
10
|
+
end
|
6
11
|
|
7
|
-
VERSION = "0.
|
12
|
+
VERSION = "0.3.0"
|
8
13
|
|
9
|
-
#
|
10
14
|
# hash with one key (:string, :file, or :cmd) set to the string that defines the log
|
11
|
-
#
|
12
15
|
attr_accessor :source
|
13
16
|
|
14
|
-
#
|
15
17
|
# array with regexp string and capture groups followed by log field
|
16
18
|
# name symbols. :time field is required to use time scoping
|
17
|
-
|
18
|
-
attr_accessor :pattern
|
19
|
+
attr_accessor :log_format
|
19
20
|
|
20
|
-
#
|
21
21
|
# format string for explicit date/time format (optional)
|
22
|
-
#
|
23
22
|
attr_accessor :time_format
|
24
23
|
|
25
|
-
#
|
26
|
-
# initialization flag required if multiline log entries are allowed
|
27
|
-
#
|
28
|
-
# @example
|
29
|
-
#
|
30
|
-
# Indy.new(:source => MY_LOG, :pattern => [MY_REGEXP, FIELD1, FIELD2, FIELD3], :multiline => true)
|
31
|
-
#
|
24
|
+
# initialization flag (true || nil) to enable multiline log entries. See README
|
32
25
|
attr_accessor :multiline
|
33
26
|
|
34
27
|
#
|
35
|
-
# Initialize Indy.
|
28
|
+
# Initialize Indy. Also see class method Indy.search()
|
36
29
|
#
|
37
30
|
# @example
|
38
31
|
#
|
39
|
-
# Indy.new(:source =>
|
32
|
+
# Indy.new(:source => LOG_FILE)
|
40
33
|
# Indy.new(:source => LOG_CONTENTS_STRING)
|
41
34
|
# Indy.new(:source => {:cmd => LOG_COMMAND_STRING})
|
42
|
-
# Indy.new(:
|
43
|
-
# Indy.new(:time_format => '%m-%d-%Y',:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source =>
|
35
|
+
# Indy.new(:log_format => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILE)
|
36
|
+
# Indy.new(:time_format => '%m-%d-%Y',:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILE)
|
44
37
|
#
|
45
38
|
def initialize(args)
|
46
|
-
@source = @
|
47
|
-
@source = Hash.new
|
39
|
+
@source = @log_format = @time_format = @log_regexp = @log_fields = @multiline = nil
|
48
40
|
|
49
41
|
while (arg = args.shift) do
|
50
42
|
send("#{arg.first}=",arg.last)
|
51
43
|
end
|
52
44
|
|
53
|
-
|
45
|
+
update_log_format( @log_format )
|
54
46
|
|
55
47
|
end
|
56
48
|
|
49
|
+
#
|
50
|
+
# Create an Indy::Source object to manage the log source
|
51
|
+
#
|
52
|
+
# @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
|
53
|
+
#
|
54
|
+
def source=(param)
|
55
|
+
@source = Source.new(param)
|
56
|
+
end
|
57
|
+
|
57
58
|
class << self
|
58
59
|
|
59
60
|
#
|
@@ -66,38 +67,45 @@ class Indy
|
|
66
67
|
# Alternately, a Hash with a :source key (amoung others) can be used to
|
67
68
|
# provide multiple initialization parameters.
|
68
69
|
#
|
69
|
-
# @example
|
70
|
+
# @example filename source
|
70
71
|
# Indy.search("apache.log").for(:severity => "INFO")
|
71
72
|
#
|
72
|
-
# @example
|
73
|
+
# @example string source
|
73
74
|
# Indy.search("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.").for(:all)
|
74
75
|
#
|
75
|
-
# @example
|
76
|
+
# @example command source
|
76
77
|
# Indy.search(:cmd => "cat apache.log").for(:severity => "INFO")
|
77
78
|
#
|
78
|
-
# @example
|
79
|
-
# Indy.search(:source => {:cmd => "cat apache.log"}, :
|
79
|
+
# @example source as well as other paramters
|
80
|
+
# Indy.search(:source => {:cmd => "cat apache.log"}, :log_format => LOG_FORMAT, :time_format => MY_TIME_FORMAT).for(:all)
|
80
81
|
#
|
81
82
|
def search(params=nil)
|
82
83
|
|
83
|
-
raise Indy::InvalidSource if params.nil? || params.is_a?(Fixnum)
|
84
|
-
|
85
84
|
if params.respond_to?(:keys) && params[:source]
|
86
85
|
Indy.new(params)
|
87
86
|
else
|
88
|
-
Indy.new(:source => params, :
|
87
|
+
Indy.new(:source => params, :log_format => DEFAULT_LOG_FORMAT)
|
89
88
|
end
|
90
89
|
end
|
91
90
|
|
92
|
-
|
91
|
+
#
|
92
|
+
# Return a Struct::Line object from a hash of values from a log entry
|
93
|
+
#
|
94
|
+
# @param [Hash] line_hash a hash of :field_name => value pairs for one log line
|
95
|
+
#
|
96
|
+
def create_struct( line_hash )
|
97
|
+
params = line_hash.keys.sort_by{|e|e.to_s}.collect {|k| line_hash[k]}
|
98
|
+
Struct::Line.new( *params )
|
99
|
+
end
|
93
100
|
|
101
|
+
end
|
94
102
|
|
95
103
|
|
96
104
|
#
|
97
|
-
# Specify the log
|
105
|
+
# Specify the log format to use as the comparison against each line within
|
98
106
|
# the log file that has been specified.
|
99
107
|
#
|
100
|
-
# @param [Array]
|
108
|
+
# @param [Array] log_format an Array with the regular expression as the first element
|
101
109
|
# followed by list of fields (Symbols) in the log entry
|
102
110
|
# to use for comparison against each log line.
|
103
111
|
#
|
@@ -105,8 +113,8 @@ class Indy
|
|
105
113
|
#
|
106
114
|
# Indy.search(LOG_FILE).with(/^(\d{2}.\d{2}.\d{2})\s*(.+)$/,:time,:message)
|
107
115
|
#
|
108
|
-
def with(
|
109
|
-
|
116
|
+
def with(log_format = :default)
|
117
|
+
update_log_format( log_format )
|
110
118
|
self
|
111
119
|
end
|
112
120
|
|
@@ -119,18 +127,23 @@ class Indy
|
|
119
127
|
#
|
120
128
|
def for(search_criteria)
|
121
129
|
results = ResultSet.new
|
122
|
-
|
123
130
|
case search_criteria
|
124
131
|
when Enumerable
|
125
132
|
results += _search do |result|
|
126
|
-
create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
|
133
|
+
result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
|
134
|
+
yield result_struct if block_given? and result_struct
|
135
|
+
result_struct
|
127
136
|
end
|
128
137
|
|
129
138
|
when :all
|
130
|
-
results += _search
|
139
|
+
results += _search do |result|
|
140
|
+
result_struct = Indy.create_struct(result)
|
141
|
+
yield result_struct if block_given?
|
142
|
+
result_struct
|
143
|
+
end
|
131
144
|
end
|
132
145
|
|
133
|
-
results
|
146
|
+
results.compact
|
134
147
|
end
|
135
148
|
|
136
149
|
|
@@ -142,23 +155,25 @@ class Indy
|
|
142
155
|
#
|
143
156
|
# @example For all applications that end with Service
|
144
157
|
#
|
145
|
-
# Indy.search(LOG_FILE).like(:application => '
|
158
|
+
# Indy.search(LOG_FILE).like(:application => '.+service')
|
146
159
|
#
|
147
160
|
def like(search_criteria)
|
148
161
|
results = ResultSet.new
|
149
162
|
|
150
163
|
results += _search do |result|
|
151
|
-
create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/ }.empty?
|
164
|
+
result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/i }.empty?
|
165
|
+
yield result_struct if block_given? and result_struct
|
166
|
+
result_struct
|
152
167
|
end
|
153
168
|
|
154
|
-
results
|
169
|
+
results.compact
|
155
170
|
end
|
156
171
|
|
157
172
|
alias_method :matching, :like
|
158
173
|
|
159
174
|
|
160
175
|
#
|
161
|
-
#
|
176
|
+
# Scopes the eventual search to the last N entries, or last N minutes of entries.
|
162
177
|
#
|
163
178
|
# @param [Hash] scope_criteria hash describing the amount of time at
|
164
179
|
# the last portion of the source
|
@@ -185,47 +200,9 @@ class Indy
|
|
185
200
|
self
|
186
201
|
end
|
187
202
|
|
188
|
-
#
|
189
|
-
# Return a Struct::Line for the last valid entry from the source
|
190
|
-
#
|
191
|
-
def last_entry
|
192
|
-
last_entries(1)
|
193
|
-
end
|
194
203
|
|
195
204
|
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
# @param [Fixnum] num the number of rows to retrieve
|
199
|
-
#
|
200
|
-
def last_entries(num)
|
201
|
-
|
202
|
-
num_entries = 0
|
203
|
-
result = []
|
204
|
-
|
205
|
-
source_io = open_source
|
206
|
-
source_io.reverse_each do |line|
|
207
|
-
|
208
|
-
hash = parse_line(line)
|
209
|
-
|
210
|
-
set_time(hash) if @time_field
|
211
|
-
|
212
|
-
if hash
|
213
|
-
num_entries += 1
|
214
|
-
result << hash
|
215
|
-
break if num_entries >= num
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
warn "No matching lines found in source: #{source_io.class}" if result.empty?
|
220
|
-
|
221
|
-
source_io.close if @source[:file] || @source[:cmd]
|
222
|
-
|
223
|
-
num == 1 ? create_struct(result.first) : result.collect{|e| create_struct(e)}
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
|
-
#
|
228
|
-
# After() scopes the eventual search to all entries after to this point.
|
205
|
+
# Scopes the eventual search to all entries after to this point.
|
229
206
|
#
|
230
207
|
# @param [Hash] scope_criteria the field to scope for as the key and the
|
231
208
|
# value to compare against the other log messages
|
@@ -251,15 +228,15 @@ class Indy
|
|
251
228
|
end
|
252
229
|
|
253
230
|
#
|
254
|
-
#
|
255
|
-
# Otherwise consecutive calls retain state
|
231
|
+
# Removes any existing start and end times from the instance
|
232
|
+
# Otherwise consecutive search calls retain time scope state
|
256
233
|
#
|
257
234
|
def reset_scope
|
258
235
|
@inclusive = @start_time = @end_time = nil
|
259
236
|
end
|
260
237
|
|
261
238
|
#
|
262
|
-
#
|
239
|
+
# Scopes the eventual search to all entries prior to this point.
|
263
240
|
#
|
264
241
|
# @param [Hash] scope_criteria the field to scope for as the key and the
|
265
242
|
# value to compare against the other log messages
|
@@ -301,7 +278,7 @@ class Indy
|
|
301
278
|
|
302
279
|
|
303
280
|
#
|
304
|
-
#
|
281
|
+
# Scopes the eventual search to all entries between two times.
|
305
282
|
#
|
306
283
|
# @param [Hash] scope_criteria the field to scope for as the key and the
|
307
284
|
# value to compare against the other log messages
|
@@ -323,32 +300,6 @@ class Indy
|
|
323
300
|
|
324
301
|
private
|
325
302
|
|
326
|
-
#
|
327
|
-
# Sets the source for the Indy instance.
|
328
|
-
#
|
329
|
-
# @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
|
330
|
-
#
|
331
|
-
# @example
|
332
|
-
#
|
333
|
-
# source("apache.log")
|
334
|
-
# source(:cmd => "cat apache.log")
|
335
|
-
# source("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.")
|
336
|
-
#
|
337
|
-
def source=(param)
|
338
|
-
|
339
|
-
raise Indy::InvalidSource if param.nil?
|
340
|
-
|
341
|
-
cmd = param[:cmd] rescue nil
|
342
|
-
@source[:cmd] = param[:cmd] if cmd
|
343
|
-
|
344
|
-
unless cmd
|
345
|
-
File.exist?(param) ? @source[:file] = param : @source[:string] = param
|
346
|
-
end
|
347
|
-
|
348
|
-
raise Indy::InvalidSource unless @source.values.reject {|value| value.kind_of? String }.empty?
|
349
|
-
|
350
|
-
end
|
351
|
-
|
352
303
|
#
|
353
304
|
# Set @pattern as well as @log_regexp, @log_fields, and @time_field
|
354
305
|
#
|
@@ -356,16 +307,16 @@ class Indy
|
|
356
307
|
# followed by list of fields (Symbols) in the log entry
|
357
308
|
# to use for comparison against each log line.
|
358
309
|
#
|
359
|
-
def
|
310
|
+
def update_log_format( log_format )
|
360
311
|
|
361
|
-
case
|
312
|
+
case log_format
|
362
313
|
when :default, nil
|
363
|
-
@
|
314
|
+
@log_format = DEFAULT_LOG_FORMAT
|
364
315
|
else
|
365
|
-
@
|
316
|
+
@log_format = log_format
|
366
317
|
end
|
367
318
|
|
368
|
-
@log_regexp, *@log_fields = @
|
319
|
+
@log_regexp, *@log_fields = @log_format
|
369
320
|
|
370
321
|
@time_field = ( @log_fields.include?(:time) ? :time : nil )
|
371
322
|
|
@@ -385,7 +336,7 @@ class Indy
|
|
385
336
|
line_matched = nil
|
386
337
|
time_search = use_time_criteria?
|
387
338
|
|
388
|
-
source_io =
|
339
|
+
source_io = @source.open(time_search)
|
389
340
|
|
390
341
|
if @multiline
|
391
342
|
results = source_io.read.scan(Regexp.new(@log_regexp, Regexp::MULTILINE)).collect do |entry|
|
@@ -420,42 +371,11 @@ class Indy
|
|
420
371
|
|
421
372
|
end
|
422
373
|
|
423
|
-
warn "No matching lines found in source: #{source_io.class}" unless line_matched
|
374
|
+
# warn "No matching lines found in source: #{source_io.class}" unless line_matched
|
424
375
|
|
425
|
-
source_io.close if @source[:file] || @source[:cmd]
|
426
376
|
results.compact
|
427
377
|
end
|
428
378
|
|
429
|
-
|
430
|
-
#
|
431
|
-
# Return a log io object
|
432
|
-
#
|
433
|
-
def open_source
|
434
|
-
begin
|
435
|
-
|
436
|
-
case @source.keys.first # and only
|
437
|
-
when :cmd
|
438
|
-
source_io = exec_command(@source[:cmd])
|
439
|
-
raise "Failed to execute command (#{@source[:cmd]})" if source_io.nil?
|
440
|
-
|
441
|
-
when :file
|
442
|
-
source_io = File.open(@source[:file], 'r')
|
443
|
-
raise "Filed to open file: #{@source[:file]}" if source_io.nil?
|
444
|
-
|
445
|
-
when :string
|
446
|
-
source_io = StringIO.new( @source[:string] )
|
447
|
-
|
448
|
-
else
|
449
|
-
raise "Unsupported log source: #{@source.inspect}"
|
450
|
-
end
|
451
|
-
|
452
|
-
rescue Exception => e
|
453
|
-
raise "Unable to open log source. (#{e.message})"
|
454
|
-
end
|
455
|
-
|
456
|
-
source_io
|
457
|
-
end
|
458
|
-
|
459
379
|
#
|
460
380
|
# Return a hash of field=>value pairs for the log line
|
461
381
|
#
|
@@ -466,7 +386,8 @@ class Indy
|
|
466
386
|
def parse_line( line )
|
467
387
|
|
468
388
|
if line.kind_of? String
|
469
|
-
match_data =
|
389
|
+
match_data = nil
|
390
|
+
Indy.suppress_warnings { match_data = /#{@log_regexp}/.match(line) }
|
470
391
|
return nil unless match_data
|
471
392
|
|
472
393
|
values = match_data.captures
|
@@ -483,7 +404,6 @@ class Indy
|
|
483
404
|
hash = Hash[ *@log_fields.zip( values ).flatten ]
|
484
405
|
hash[:line] = entire_line.strip
|
485
406
|
|
486
|
-
|
487
407
|
hash
|
488
408
|
end
|
489
409
|
|
@@ -557,19 +477,22 @@ class Indy
|
|
557
477
|
end
|
558
478
|
|
559
479
|
#
|
560
|
-
#
|
561
|
-
#
|
562
|
-
# @param [String] command_string string of command that will return log contents
|
480
|
+
# Return a time or datetime object way in the future
|
563
481
|
#
|
564
|
-
def
|
482
|
+
def forever
|
483
|
+
@time_format ? DateTime.new(4712) : Time.at(0x7FFFFFFF)
|
484
|
+
end
|
565
485
|
|
486
|
+
#
|
487
|
+
# Return a time or datetime object way in the past
|
488
|
+
#
|
489
|
+
def forever_ago
|
566
490
|
begin
|
567
|
-
|
568
|
-
return nil if io.eof?
|
491
|
+
@time_format ? DateTime.new(-4712) : Time.at(-0x7FFFFFFF)
|
569
492
|
rescue
|
570
|
-
|
493
|
+
# Windows Ruby Time can't handle dates prior to 1969
|
494
|
+
@time_format ? DateTime.new(-4712) : Time.at(0)
|
571
495
|
end
|
572
|
-
io
|
573
496
|
end
|
574
497
|
|
575
498
|
#
|
@@ -577,44 +500,44 @@ class Indy
|
|
577
500
|
#
|
578
501
|
def define_struct
|
579
502
|
fields = (@log_fields + [:_time, :line]).sort_by{|e|e.to_s}
|
580
|
-
|
581
|
-
# suppress Struct 'redefining constant' warning
|
582
|
-
verbose = $VERBOSE
|
583
|
-
$VERBOSE = nil
|
584
|
-
|
585
|
-
Struct.new( "Line", *fields )
|
586
|
-
|
587
|
-
$VERBOSE = verbose
|
503
|
+
Indy.suppress_warnings { Struct.new( "Line", *fields ) }
|
588
504
|
end
|
589
505
|
|
590
506
|
#
|
591
|
-
# Return a Struct::Line
|
592
|
-
#
|
593
|
-
# @param [Hash] line_hash a hash of :field_name => value pairs for one log line
|
507
|
+
# Return a Struct::Line for the last valid entry from the source
|
594
508
|
#
|
595
|
-
def
|
596
|
-
|
597
|
-
Struct::Line.new( *params )
|
509
|
+
def last_entry
|
510
|
+
last_entries(1)
|
598
511
|
end
|
599
512
|
|
600
513
|
#
|
601
|
-
# Return
|
602
|
-
#
|
603
|
-
def forever
|
604
|
-
@time_format ? DateTime.new(4712) : Time.at(0x7FFFFFFF)
|
605
|
-
end
|
606
|
-
|
514
|
+
# Return an array of Struct::Line entries for the last N valid entries from the source
|
607
515
|
#
|
608
|
-
#
|
516
|
+
# @param [Fixnum] num the number of rows to retrieve
|
609
517
|
#
|
610
|
-
def
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
518
|
+
def last_entries(num)
|
519
|
+
|
520
|
+
num_entries = 0
|
521
|
+
result = []
|
522
|
+
|
523
|
+
source_io = @source.open
|
524
|
+
|
525
|
+
source_io.reverse_each do |line|
|
526
|
+
|
527
|
+
hash = parse_line(line)
|
528
|
+
|
529
|
+
set_time(hash) if @time_field
|
530
|
+
|
531
|
+
if hash
|
532
|
+
num_entries += 1
|
533
|
+
result << hash
|
534
|
+
break if num_entries >= num
|
535
|
+
end
|
616
536
|
end
|
617
|
-
end
|
618
537
|
|
538
|
+
warn "#last_entries found no matching lines in source." if result.empty?
|
539
|
+
|
540
|
+
num == 1 ? Indy.create_struct(result.first) : result.collect{|e| Indy.create_struct(e)}
|
541
|
+
end
|
619
542
|
|
620
543
|
end
|