indy 0.1.5 → 0.2.0
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.
- data/Gemfile +3 -0
- data/History.txt +11 -0
- data/README.md +76 -18
- data/Rakefile +8 -7
- data/features/README.md +4 -0
- data/features/around_time.feature +2 -2
- data/indy.gemspec +9 -8
- data/lib/indy/indy.rb +144 -35
- data/spec/indy_spec.rb +64 -2
- data/spec/last_spec.rb +42 -0
- data/spec/time_spec.rb +20 -13
- metadata +20 -18
- data/performance/large.log +0 -10000
data/Gemfile
ADDED
data/History.txt
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
=== 0.2.0 / 2011-03-08
|
2
|
+
|
3
|
+
* Support for Multiline log entries. See README.md
|
4
|
+
* Time scopes now respect inclusive flag
|
5
|
+
* #last() no longer supports number of rows as a parameter. Use :span => minutes.
|
6
|
+
* Fixes for Ruby 1.9.2
|
7
|
+
|
8
|
+
=== 0.1.6 / 2011-03-07
|
9
|
+
|
10
|
+
* Unsupported. Gem version 0.1.6 == 0.2.0
|
11
|
+
|
1
12
|
=== 0.1.5 / 2011-01-21
|
2
13
|
|
3
14
|
* Searching with time scopes (#after, #within, #before) are much faster
|
data/README.md
CHANGED
@@ -40,16 +40,22 @@ Usage
|
|
40
40
|
|
41
41
|
Indy.search(log_string).for(:message => 'Entering application')
|
42
42
|
|
43
|
-
##
|
44
|
-
|
45
|
-
The default search pattern resembles something you might find:
|
46
|
-
|
47
|
-
YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
|
43
|
+
## Log Pattern
|
48
44
|
|
49
45
|
### Default Log Pattern
|
50
46
|
|
51
|
-
|
52
|
-
|
47
|
+
The default search pattern follows this form:
|
48
|
+
YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
|
49
|
+
|
50
|
+
Which uses this regexp:
|
51
|
+
/^(\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
|
+
and specifies these fields:
|
54
|
+
[:time, :severity, :application, :message]
|
55
|
+
|
56
|
+
For example:
|
57
|
+
Indy.search(source).for(:severity => 'INFO')
|
58
|
+
Indy.search(source).for(:application => 'MyApp', :severity => 'DEBUG')
|
53
59
|
|
54
60
|
### Custom Log Pattern
|
55
61
|
|
@@ -73,6 +79,36 @@ Several log formats have been predefined for ease of configuration. See indy/pat
|
|
73
79
|
# INFO mylog: This is a message with level INFO
|
74
80
|
Indy.new(:source => 'logfile.txt', :pattern => Indy::LOG4R_DEFAULT_PATTERN).for(:application => 'mylog')
|
75
81
|
|
82
|
+
### Multiline log entries
|
83
|
+
|
84
|
+
By default, Indy assumes that log lines are separated by new lines. Any lines that don't match the active pattern are ignored. To enable multiline log entries you must do two things:
|
85
|
+
|
86
|
+
1. Use `Indy.new()` and include the `:multiline => true` parameter
|
87
|
+
2. Use a log entry regexp that does not use `$` and/or `\n` to define the end of the entry.
|
88
|
+
|
89
|
+
#### Multiline Regexp tips
|
90
|
+
|
91
|
+
* Use non-greedy matching when needed: `.*?` instead of `.*`
|
92
|
+
* Assuming your log entries do not include a unique line ending, you can use a zero-width positive lookahead assertion to verify that each line is followed by the start of a valid log entry, or the end of the string. e.g.: `(?=^foo|\z)`
|
93
|
+
|
94
|
+
Check out [Regexp Extensions](http://www.ruby-doc.org/docs/ProgrammingRuby/html/language.html#UN)
|
95
|
+
|
96
|
+
Example:
|
97
|
+
|
98
|
+
# Given this log containing two entries:
|
99
|
+
#
|
100
|
+
# INFO MyApp - Multiline message begins here
|
101
|
+
# and ends here
|
102
|
+
# DEBUG MyOtherApp - Single line message
|
103
|
+
|
104
|
+
severity_string = 'DEBUG|INFO|WARN|ERROR|FATAL'
|
105
|
+
|
106
|
+
# single line regexp would be:
|
107
|
+
# /^(#{severity_string}) (\w+) - (.*)$/
|
108
|
+
multiline_regexp = /^(#{severity_string}) (\w+) - (.*?)(?=^#{severity_string}|\z)/
|
109
|
+
|
110
|
+
Indy.new( :multiline => true, :pattern => [multiline_regexp, :severity, :application, :message], :source => MY_LOG)
|
111
|
+
|
76
112
|
### Explicit Time Format
|
77
113
|
|
78
114
|
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.
|
@@ -94,13 +130,6 @@ This is required when log data uses a non-standard date format, e.g.: U.S. forma
|
|
94
130
|
Indy.search(source).for(:message => 'Entering Application', :application => 'MyApp')
|
95
131
|
Indy.search(source).for(:severity => 'INFO', :application => 'MyApp')
|
96
132
|
|
97
|
-
### Time Scope
|
98
|
-
|
99
|
-
Indy.search(source).after(:time => '2011-01-13 13:40:00').for(:all)
|
100
|
-
Indy.search(source).before(:time => '2010-12-31 23:59:59').for(:all)
|
101
|
-
Indy.search(source).around(:time => '2011-01-01 00:00:00', :span => 2).for(:all) # 2 minutes around New Year's Eve
|
102
|
-
Indy.search(source).within(:time => ['2011-01-01 00:00:00','2011-02-01 00:00:00']).for(:severity => 'ERROR', :application => 'MyApp')
|
103
|
-
|
104
133
|
### Partial Match
|
105
134
|
|
106
135
|
Indy.search(source).like(:message => 'Memory')
|
@@ -109,17 +138,46 @@ This is required when log data uses a non-standard date format, e.g.: U.S. forma
|
|
109
138
|
|
110
139
|
Indy.search(source).like(:severity => '(?:INFO|DEBUG)', :message => 'Memory')
|
111
140
|
|
141
|
+
## Log Scopes
|
142
|
+
|
143
|
+
Multiple scope methods can be called on an instance. Use #reset_scope to remove scope constrints on the instance.
|
144
|
+
|
145
|
+
### Time Scope
|
146
|
+
|
147
|
+
# After Dec 1
|
148
|
+
Indy.search(source).after(:time => '2010-12-01 23:59:59').for(:all)
|
149
|
+
|
150
|
+
# 20 minutes Around New Year's eve
|
151
|
+
Indy.search(source).around(:time => '2011-01-01 00:00:00', :span => 20).for(:all)
|
152
|
+
|
153
|
+
# After Jan 1 but Before Feb 1
|
154
|
+
@log = Indy.search(source)
|
155
|
+
@log.after(:time => '2011-01-01 00:00:00').before(:time => '2011-02-01 00:00:00')
|
156
|
+
@log.for(:all)
|
157
|
+
|
158
|
+
# Within Jan 1 and Feb 1 (same time scope as above)
|
159
|
+
Indy.search(source).within(:time => ['2011-01-01 00:00:00','2011-02-01 00:00:00']).for(:all)
|
160
|
+
|
161
|
+
# After Jan 1
|
162
|
+
@log = Indy.search(source)
|
163
|
+
@log.after(:time => '2011-01-01 00:00:00')
|
164
|
+
@log.for(:all)
|
165
|
+
# Reset the time scope to include entries before Jan 1
|
166
|
+
@log.reset_scope
|
167
|
+
# Before Feb 1
|
168
|
+
@log.before(:time => '2011-02-01 00:00:00')
|
169
|
+
@log.for(:all)
|
170
|
+
|
112
171
|
## Process the Results
|
113
172
|
|
114
|
-
|
173
|
+
A ResultSet is returned by #for and #like, which is an Enumerable containing a hash for each log entry.
|
115
174
|
|
116
175
|
entries = Indy.search(source).for(:message => 'Entering Application')
|
176
|
+
entries.first.keys
|
177
|
+
# => [:line, :time, :severity, :application, :message]
|
117
178
|
|
118
179
|
Indy.search(source).for(:message => 'Entering Application').each do |entry|
|
119
|
-
|
120
|
-
# each log line entry returned is an OpenStruct object
|
121
180
|
puts "[#{entry.time}] #{entry.message}: #{entry.application}"
|
122
|
-
|
123
181
|
end
|
124
182
|
|
125
183
|
LICENSE
|
data/Rakefile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rspec/core'
|
3
3
|
require 'rspec/core/rake_task'
|
4
|
+
require 'rcov/rcovtask'
|
4
5
|
require "cucumber/rake/task"
|
5
6
|
require "yard"
|
6
|
-
require "city"
|
7
7
|
|
8
8
|
desc 'Default: run tests'
|
9
9
|
task :default => :test
|
@@ -55,15 +55,16 @@ task :flog_detail do
|
|
55
55
|
system('find lib -name \*.rb | xargs flog -d')
|
56
56
|
end
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
t.
|
61
|
-
t.
|
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
62
|
t.rcov_opts = ['--exclude', 'spec', '--exclude', 'gems', '-T']
|
63
|
+
t.verbose = true
|
63
64
|
end
|
64
65
|
|
65
|
-
|
66
|
-
YARD::Rake::
|
66
|
+
# Task :yard -- Generate yard + yard-cucumber docs
|
67
|
+
YARD::Rake::YardocTask.new do |t|
|
67
68
|
t.files = ['features/**/*', 'lib/**/*.rb']
|
68
69
|
t.options = ['--private']
|
69
70
|
end
|
data/features/README.md
ADDED
@@ -30,5 +30,5 @@ Scenario: Count of entries for a time span before and including a specified time
|
|
30
30
|
Then I expect to have found 2 log entries
|
31
31
|
|
32
32
|
Scenario: Count of entries for a time span after and including a specified time
|
33
|
-
When searching the log for all entries
|
34
|
-
Then I expect to have found
|
33
|
+
When searching the log for all entries 30 minutes after and including the time 2000-09-07 14:17:43
|
34
|
+
Then I expect to have found 4 log entries
|
data/indy.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + "/lib/indy"
|
2
2
|
|
3
|
-
|
3
|
+
class Indy
|
4
4
|
|
5
5
|
def self.show_version_changes(version)
|
6
6
|
date = ""
|
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
|
|
32
32
|
s.authors = ["Franklin Webber","Brandon Faloona"]
|
33
33
|
s.description = %{ Indy is a log archelogy library that treats logs like data structures. Search fixed format or custom logs by field and/or time. }
|
34
34
|
s.summary = "Log Search Library"
|
35
|
-
s.email = '
|
35
|
+
s.email = 'brandon@faloona.net'
|
36
36
|
s.homepage = "http://github.com/burtlo/Indy"
|
37
37
|
s.license = 'MIT'
|
38
38
|
|
@@ -40,9 +40,9 @@ Gem::Specification.new do |s|
|
|
40
40
|
s.required_ruby_version = '>= 1.8.5'
|
41
41
|
s.add_dependency('activesupport', '>= 2.3.5')
|
42
42
|
|
43
|
-
s.add_development_dependency('cucumber', '>= 0.
|
43
|
+
s.add_development_dependency('cucumber', '>= 0.10.0')
|
44
44
|
s.add_development_dependency('yard', '>= 0.6.4')
|
45
|
-
s.add_development_dependency('cucumber
|
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')
|
@@ -62,11 +62,12 @@ Gem::Specification.new do |s|
|
|
62
62
|
|
63
63
|
}
|
64
64
|
|
65
|
-
|
66
|
-
# s.files = `git ls-files`.split("\n") - exclusions
|
67
|
-
|
65
|
+
|
68
66
|
s.rubygems_version = "1.3.7"
|
69
|
-
|
67
|
+
|
68
|
+
exclusions = [File.join("performance", "large.log")]
|
69
|
+
s.files = `git ls-files`.split("\n") - exclusions
|
70
|
+
|
70
71
|
s.extra_rdoc_files = ["README.md", "History.txt"]
|
71
72
|
s.rdoc_options = ["--charset=UTF-8"]
|
72
73
|
s.require_path = "lib"
|
data/lib/indy/indy.rb
CHANGED
@@ -4,7 +4,7 @@ class Indy
|
|
4
4
|
|
5
5
|
class InvalidSource < Exception; end
|
6
6
|
|
7
|
-
VERSION = "0.
|
7
|
+
VERSION = "0.2.0"
|
8
8
|
|
9
9
|
#
|
10
10
|
# hash with one key (:string, :file, or :cmd) set to the string that defines the log
|
@@ -22,6 +22,15 @@ class Indy
|
|
22
22
|
#
|
23
23
|
attr_accessor :time_format
|
24
24
|
|
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
|
+
#
|
32
|
+
attr_accessor :multiline
|
33
|
+
|
25
34
|
#
|
26
35
|
# Initialize Indy.
|
27
36
|
#
|
@@ -34,14 +43,14 @@ class Indy
|
|
34
43
|
# Indy.new(:time_format => '%m-%d-%Y',:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILENAME)
|
35
44
|
#
|
36
45
|
def initialize(args)
|
37
|
-
@source = @pattern = @time_format = @log_regexp = @log_fields = nil
|
46
|
+
@source = @pattern = @time_format = @log_regexp = @log_fields = @multiline = nil
|
38
47
|
@source = Hash.new
|
39
48
|
|
40
49
|
while (arg = args.shift) do
|
41
50
|
send("#{arg.first}=",arg.last)
|
42
51
|
end
|
43
52
|
|
44
|
-
update_log_pattern(@pattern)
|
53
|
+
update_log_pattern( @pattern )
|
45
54
|
|
46
55
|
end
|
47
56
|
|
@@ -71,7 +80,7 @@ class Indy
|
|
71
80
|
#
|
72
81
|
def search(params=nil)
|
73
82
|
|
74
|
-
raise Indy::InvalidSource if params.nil?
|
83
|
+
raise Indy::InvalidSource if params.nil? || params.is_a?(Fixnum)
|
75
84
|
|
76
85
|
if params.respond_to?(:keys) && params[:source]
|
77
86
|
Indy.new(params)
|
@@ -111,8 +120,6 @@ class Indy
|
|
111
120
|
def for(search_criteria)
|
112
121
|
results = ResultSet.new
|
113
122
|
|
114
|
-
define_struct
|
115
|
-
|
116
123
|
case search_criteria
|
117
124
|
when Enumerable
|
118
125
|
results += _search do |result|
|
@@ -139,7 +146,6 @@ class Indy
|
|
139
146
|
#
|
140
147
|
def like(search_criteria)
|
141
148
|
results = ResultSet.new
|
142
|
-
define_struct
|
143
149
|
|
144
150
|
results += _search do |result|
|
145
151
|
create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/ }.empty?
|
@@ -152,7 +158,74 @@ class Indy
|
|
152
158
|
|
153
159
|
|
154
160
|
#
|
155
|
-
#
|
161
|
+
# Last() scopes the eventual search to the last N minutes worth of entries.
|
162
|
+
#
|
163
|
+
# @param [Hash] scope_criteria hash describing the amount of time at
|
164
|
+
# the last portion of the source
|
165
|
+
#
|
166
|
+
# @example For last 10 minutes worth of entries
|
167
|
+
#
|
168
|
+
# Indy.search(LOG_FILE).last(:span => 100).for(:all)
|
169
|
+
#
|
170
|
+
def last(scope_criteria)
|
171
|
+
case scope_criteria
|
172
|
+
when Enumerable
|
173
|
+
raise ArgumentError unless scope_criteria[:span] || scope_criteria[:rows]
|
174
|
+
|
175
|
+
if scope_criteria[:span]
|
176
|
+
span = (scope_criteria[:span].to_i * 60).seconds
|
177
|
+
starttime = parse_date(last_entry[:_time]) - span
|
178
|
+
|
179
|
+
within(:time => [starttime, forever])
|
180
|
+
end
|
181
|
+
else
|
182
|
+
raise ArgumentError, "Invalid parameter: #{scope_criteria.inspect}"
|
183
|
+
end
|
184
|
+
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
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
|
+
|
195
|
+
#
|
196
|
+
# Return an array of Struct::Line entries for the last N valid entries from the source
|
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.
|
156
229
|
#
|
157
230
|
# @param [Hash] scope_criteria the field to scope for as the key and the
|
158
231
|
# value to compare against the other log messages
|
@@ -164,7 +237,7 @@ class Indy
|
|
164
237
|
def after(scope_criteria)
|
165
238
|
if scope_criteria[:time]
|
166
239
|
time = parse_date(scope_criteria[:time])
|
167
|
-
@inclusive = scope_criteria[:inclusive] ||
|
240
|
+
@inclusive = @inclusive || scope_criteria[:inclusive] || nil
|
168
241
|
|
169
242
|
if scope_criteria[:span]
|
170
243
|
span = (scope_criteria[:span].to_i * 60).seconds
|
@@ -178,7 +251,15 @@ class Indy
|
|
178
251
|
end
|
179
252
|
|
180
253
|
#
|
181
|
-
#
|
254
|
+
# reset_time_scope removes any existing start and end times from the instance
|
255
|
+
# Otherwise consecutive calls retain state
|
256
|
+
#
|
257
|
+
def reset_scope
|
258
|
+
@inclusive = @start_time = @end_time = nil
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# Before() scopes the eventual search to all entries prior to this point.
|
182
263
|
#
|
183
264
|
# @param [Hash] scope_criteria the field to scope for as the key and the
|
184
265
|
# value to compare against the other log messages
|
@@ -191,7 +272,7 @@ class Indy
|
|
191
272
|
def before(scope_criteria)
|
192
273
|
if scope_criteria[:time]
|
193
274
|
time = parse_date(scope_criteria[:time])
|
194
|
-
@inclusive = scope_criteria[:inclusive] ||
|
275
|
+
@inclusive = @inclusive || scope_criteria[:inclusive] || nil
|
195
276
|
|
196
277
|
if scope_criteria[:span]
|
197
278
|
span = (scope_criteria[:span].to_i * 60).seconds
|
@@ -208,8 +289,8 @@ class Indy
|
|
208
289
|
if scope_criteria[:time]
|
209
290
|
time = parse_date(scope_criteria[:time])
|
210
291
|
|
211
|
-
|
212
|
-
|
292
|
+
@inclusive = nil
|
293
|
+
warn "Ignoring inclusive scope_criteria" if scope_criteria[:inclusive]
|
213
294
|
|
214
295
|
half_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
|
215
296
|
within(:time => [time - half_span, time + half_span])
|
@@ -220,7 +301,7 @@ class Indy
|
|
220
301
|
|
221
302
|
|
222
303
|
#
|
223
|
-
# Within scopes the eventual search to all entries between two points.
|
304
|
+
# Within() scopes the eventual search to all entries between two points.
|
224
305
|
#
|
225
306
|
# @param [Hash] scope_criteria the field to scope for as the key and the
|
226
307
|
# value to compare against the other log messages
|
@@ -233,7 +314,7 @@ class Indy
|
|
233
314
|
if scope_criteria[:time]
|
234
315
|
@start_time, @end_time = scope_criteria[:time].collect {|str| parse_date(str) }
|
235
316
|
|
236
|
-
@inclusive = scope_criteria[:inclusive] ||
|
317
|
+
@inclusive = @inclusive || scope_criteria[:inclusive] || nil
|
237
318
|
end
|
238
319
|
|
239
320
|
self
|
@@ -288,6 +369,9 @@ class Indy
|
|
288
369
|
|
289
370
|
@time_field = ( @log_fields.include?(:time) ? :time : nil )
|
290
371
|
|
372
|
+
# now that we know the fields
|
373
|
+
define_struct
|
374
|
+
|
291
375
|
end
|
292
376
|
|
293
377
|
#
|
@@ -302,27 +386,43 @@ class Indy
|
|
302
386
|
time_search = use_time_criteria?
|
303
387
|
|
304
388
|
source_io = open_source
|
305
|
-
results = source_io.collect do |line|
|
306
389
|
|
307
|
-
|
390
|
+
if @multiline
|
391
|
+
results = source_io.read.scan(Regexp.new(@log_regexp, Regexp::MULTILINE)).collect do |entry|
|
308
392
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
393
|
+
hash = parse_line(entry)
|
394
|
+
hash ? (line_matched = true) : next
|
395
|
+
|
396
|
+
if time_search
|
397
|
+
set_time(hash)
|
398
|
+
next unless inside_time_window?(hash)
|
399
|
+
else
|
400
|
+
hash[:_time] = nil if hash
|
401
|
+
end
|
402
|
+
|
403
|
+
block_given? ? block.call(hash) : nil
|
316
404
|
end
|
317
405
|
|
318
|
-
|
406
|
+
else
|
407
|
+
results = source_io.collect do |line|
|
408
|
+
hash = parse_line(line)
|
409
|
+
hash ? (line_matched = true) : next
|
410
|
+
|
411
|
+
if time_search
|
412
|
+
set_time(hash)
|
413
|
+
next unless inside_time_window?(hash)
|
414
|
+
else
|
415
|
+
hash[:_time] = nil if hash
|
416
|
+
end
|
417
|
+
|
418
|
+
block_given? ? block.call(hash) : nil
|
419
|
+
end
|
319
420
|
|
320
421
|
end
|
321
422
|
|
322
423
|
warn "No matching lines found in source: #{source_io.class}" unless line_matched
|
323
424
|
|
324
|
-
source_io.close if @source[:file] || @source[:cmd]
|
325
|
-
|
425
|
+
source_io.close if @source[:file] || @source[:cmd]
|
326
426
|
results.compact
|
327
427
|
end
|
328
428
|
|
@@ -365,17 +465,26 @@ class Indy
|
|
365
465
|
#
|
366
466
|
def parse_line( line )
|
367
467
|
|
368
|
-
|
468
|
+
if line.kind_of? String
|
469
|
+
match_data = /#{@log_regexp}/.match(line)
|
470
|
+
return nil unless match_data
|
369
471
|
|
370
|
-
if match_data
|
371
472
|
values = match_data.captures
|
372
|
-
|
473
|
+
entire_line = line.strip
|
373
474
|
|
374
|
-
|
375
|
-
hash[:line] = line.strip
|
475
|
+
elsif line.kind_of? Enumerable
|
376
476
|
|
377
|
-
|
477
|
+
entire_line = line.shift
|
478
|
+
values = line
|
378
479
|
end
|
480
|
+
|
481
|
+
raise "Field mismatch between log pattern and log data. The data is: '#{values.join(':::')}'" unless values.length == @log_fields.length
|
482
|
+
|
483
|
+
hash = Hash[ *@log_fields.zip( values ).flatten ]
|
484
|
+
hash[:line] = entire_line.strip
|
485
|
+
|
486
|
+
|
487
|
+
hash
|
379
488
|
end
|
380
489
|
|
381
490
|
#
|
@@ -426,8 +535,8 @@ class Indy
|
|
426
535
|
def parse_date(param)
|
427
536
|
return nil unless @time_field
|
428
537
|
return param if param.kind_of? Time or param.kind_of? DateTime
|
429
|
-
|
430
|
-
time_string = param
|
538
|
+
|
539
|
+
time_string = param.is_a?(Hash) ? param[@time_field] : param.to_s
|
431
540
|
|
432
541
|
if @time_format
|
433
542
|
begin
|