indy 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -0
- data/README.md +20 -3
- data/Rakefile +1 -0
- data/features/step_definitions/find_by.steps.rb +2 -10
- data/indy.gemspec +8 -1
- data/lib/indy.rb +3 -1
- data/lib/indy/indy.rb +237 -280
- data/lib/indy/log_formats.rb +39 -0
- data/lib/indy/patterns.rb +25 -0
- data/performance/profile_spec.rb +8 -8
- data/spec/indy_spec.rb +20 -20
- data/spec/log_format_spec.rb +58 -0
- data/spec/search_spec.rb +26 -6
- data/spec/time_spec.rb +16 -0
- metadata +105 -7
- data/features/log_levels.feature +0 -40
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== 0.1.2 / 2011-01-18
|
2
|
+
|
3
|
+
* Predefined log formats for NCSA Common, NCSA Combined, and Log4r (default)
|
4
|
+
* Source IO is explicitly closed after each #_search
|
5
|
+
* Removed instance method #search; use #for.
|
6
|
+
* Removed instance method #severity
|
7
|
+
|
1
8
|
=== 0.1.1 / 2011-01-13
|
2
9
|
|
3
10
|
* Scope search by time (after, before, around, or within)
|
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 it does not often treat the logs themselves as data structures. Indy attempts to deliver logs with more powerful features by allowing the ability
|
7
|
+
Log files are often searched for particular strings but it does not often treat the logs themselves as data structures. Indy attempts to deliver logs with 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
|
------------
|
@@ -57,10 +57,22 @@ If the default pattern is obviously not strong enough for you, brew your own.
|
|
57
57
|
To do so, specify a pattern and each of the match with their symbolic name.
|
58
58
|
|
59
59
|
# HH:MM:SS SEVERITY APPLICATION#METHOD - MESSAGE
|
60
|
-
custom_pattern =
|
60
|
+
custom_pattern = /^(\d{2}:\d{2}:\d{2})\s*(INFO|DEBUG|WARN|ERROR)\s*([^#]+)#([^\s]+)\s*-\s*(.+)$/
|
61
61
|
|
62
62
|
Indy.search(source).with(custom_pattern,:time,:severity,:application,:method,:message).for(:severity => 'INFO', :method => 'allocate')
|
63
63
|
|
64
|
+
### Predefined Log Patterns
|
65
|
+
|
66
|
+
Several log formats have been predefined for ease of configuration. See indy/patterns.rb
|
67
|
+
|
68
|
+
# Indy::COMMON_LOG_PATTERN
|
69
|
+
# Indy::COMBINED_LOG_PATTERN
|
70
|
+
# Indy::LOG4R_DEFAULT_PATTERN
|
71
|
+
#
|
72
|
+
# Example (Log4r)
|
73
|
+
# INFO mylog: This is a message with level INFO
|
74
|
+
Indy.new(:source => 'logfile.txt', :pattern => Indy::LOG4R_DEFAULT_PATTERN).for(:application => 'mylog')
|
75
|
+
|
64
76
|
### Explicit Time Format
|
65
77
|
|
66
78
|
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.
|
@@ -99,10 +111,15 @@ This is required when log data uses a non-standard date format, e.g.: U.S. forma
|
|
99
111
|
|
100
112
|
## Process the Results
|
101
113
|
|
114
|
+
A ResultSet (Array) is returned by #for, #like, #after, etc.
|
115
|
+
|
102
116
|
entries = Indy.search(source).for(:message => 'Entering Application')
|
103
117
|
|
104
118
|
Indy.search(source).for(:message => 'Entering Application').each do |entry|
|
105
|
-
|
119
|
+
|
120
|
+
# each log line entry returned is an OpenStruct object
|
121
|
+
puts "[#{entry.time}] #{entry.message}: #{entry.application}"
|
122
|
+
|
106
123
|
end
|
107
124
|
|
108
125
|
LICENSE
|
data/Rakefile
CHANGED
@@ -7,14 +7,6 @@ When /^searching the log for the log severity (\w+)$/ do |severity|
|
|
7
7
|
@results = @indy.for(:severity => severity)
|
8
8
|
end
|
9
9
|
|
10
|
-
When /^searching the log for the log severity (\w+) and lower$/ do |severity|
|
11
|
-
@results = @indy.severity(severity,:equal_and_below)
|
12
|
-
end
|
13
|
-
|
14
|
-
When /^searching the log for the log severity (\w+) and higher$/ do |severity|
|
15
|
-
@results = @indy.severity(severity,:equal_and_above)
|
16
|
-
end
|
17
|
-
|
18
10
|
When /^searching the log for the exact match of the message "([^"]+)"$/ do |message|
|
19
11
|
@results = @indy.for(:message => message)
|
20
12
|
end
|
@@ -25,7 +17,7 @@ end
|
|
25
17
|
|
26
18
|
When /^searching the log for:$/ do |fields|
|
27
19
|
fields.map_headers! {|header| header.is_a?(Symbol) ? header : header.downcase.gsub(/\s/,'_').to_sym }
|
28
|
-
@results = @indy.
|
20
|
+
@results = @indy.for(fields.hashes.first)
|
29
21
|
end
|
30
22
|
|
31
23
|
When /^searching the log for entries like:$/ do |fields|
|
@@ -34,7 +26,7 @@ When /^searching the log for entries like:$/ do |fields|
|
|
34
26
|
end
|
35
27
|
|
36
28
|
When /^searching the log for the exact match of custom field ([^"]+)\s*"([^"]+)"$/ do |field,value|
|
37
|
-
@results = @indy.
|
29
|
+
@results = @indy.for(field.strip.gsub(/\s/,'_').to_sym => value)
|
38
30
|
end
|
39
31
|
|
40
32
|
When /^searching the log for entries in the (\w+ \w+), by (.+)$/ do |portion, method|
|
data/indy.gemspec
CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
s.name = 'indy'
|
31
31
|
s.version = ::Indy::VERSION
|
32
32
|
s.authors = ["Franklin Webber","Brandon Faloona"]
|
33
|
-
s.description = %{ Indy is a log archelogy tool that allows you to
|
33
|
+
s.description = %{ Indy is a log archelogy tool that allows you to interact with log data like an object while you search by fields and/or time.}
|
34
34
|
s.summary = "Log Search Tool"
|
35
35
|
s.email = 'franklin.webber@gmail.com'
|
36
36
|
s.homepage = "http://github.com/burtlo/Indy"
|
@@ -40,6 +40,13 @@ Gem::Specification.new do |s|
|
|
40
40
|
s.required_ruby_version = '>= 1.8.7'
|
41
41
|
s.add_dependency('activesupport', '>= 2.3.5')
|
42
42
|
|
43
|
+
s.add_development_dependency('cucumber', '>= 0.9.2')
|
44
|
+
s.add_development_dependency('rspec', '>= 2.4.0')
|
45
|
+
s.add_development_dependency('rspec-mocks', '>= 2.4.0')
|
46
|
+
s.add_development_dependency('rspec-prof', '>= 0.0.3')
|
47
|
+
s.add_development_dependency('rcov', '>= 0.9.9')
|
48
|
+
s.add_development_dependency('flog', '>= 2.5.0')
|
49
|
+
|
43
50
|
changes = Indy.show_version_changes(::Indy::VERSION)
|
44
51
|
|
45
52
|
s.post_install_message = %{
|
data/lib/indy.rb
CHANGED
@@ -2,4 +2,6 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
2
2
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
3
|
|
4
4
|
require 'indy/indy'
|
5
|
-
require 'indy/result_set'
|
5
|
+
require 'indy/result_set'
|
6
|
+
require 'indy/log_formats'
|
7
|
+
require 'indy/patterns'
|
data/lib/indy/indy.rb
CHANGED
@@ -3,10 +3,10 @@ require 'active_support/core_ext'
|
|
3
3
|
|
4
4
|
class Indy
|
5
5
|
|
6
|
-
VERSION = "0.1.
|
6
|
+
VERSION = "0.1.2"
|
7
7
|
|
8
8
|
#
|
9
|
-
# string, file, or
|
9
|
+
# hash with one key (:string, :file, or :cmd) set to the string that defines the log
|
10
10
|
#
|
11
11
|
attr_accessor :source
|
12
12
|
|
@@ -21,15 +21,6 @@ class Indy
|
|
21
21
|
#
|
22
22
|
attr_accessor :time_format
|
23
23
|
|
24
|
-
DATE_TIME = "\\d{4}.\\d{2}.\\d{2}\s+\\d{2}.\\d{2}.\\d{2}" #"%Y-%m-%d %H:%M:%S"
|
25
|
-
SEVERITY = [:trace,:debug,:info,:warn,:error,:fatal]
|
26
|
-
SEVERITY_PATTERN = "(?:#{SEVERITY.map{|s| s.to_s.upcase}.join("|")})"
|
27
|
-
APPLICATION = "\\w+"
|
28
|
-
MESSAGE = ".+"
|
29
|
-
|
30
|
-
DEFAULT_LOG_PATTERN = "^(#{DATE_TIME})\\s+(#{SEVERITY_PATTERN})\\s+(#{APPLICATION})\\s+-\\s+(#{MESSAGE})$"
|
31
|
-
DEFAULT_LOG_FIELDS = [:time,:severity,:application,:message]
|
32
|
-
|
33
24
|
FOREVER_AGO = DateTime.now - 200_000
|
34
25
|
FOREVER = DateTime.now + 200_000
|
35
26
|
|
@@ -47,13 +38,13 @@ class Indy
|
|
47
38
|
#
|
48
39
|
def initialize(args)
|
49
40
|
@source = @pattern = nil
|
50
|
-
@
|
41
|
+
@source = Hash.new
|
51
42
|
|
52
43
|
while (arg = args.shift) do
|
53
44
|
send("#{arg.first}=",arg.last)
|
54
45
|
end
|
55
46
|
|
56
|
-
@pattern = @pattern ||
|
47
|
+
@pattern = @pattern || DEFAULT_LOG_PATTERN
|
57
48
|
@time_field = ( @pattern[1..-1].include?(:time) ? :time : nil )
|
58
49
|
|
59
50
|
end
|
@@ -86,7 +77,7 @@ class Indy
|
|
86
77
|
if params.respond_to?(:keys) && params[:source]
|
87
78
|
Indy.new(params)
|
88
79
|
else
|
89
|
-
Indy.new(:source => params, :pattern =>
|
80
|
+
Indy.new(:source => params, :pattern => DEFAULT_LOG_PATTERN)
|
90
81
|
end
|
91
82
|
end
|
92
83
|
|
@@ -104,10 +95,10 @@ class Indy
|
|
104
95
|
#
|
105
96
|
# @example Log formatted as - HH:MM:SS Message
|
106
97
|
#
|
107
|
-
# Indy.search(LOG_FILE).with(
|
98
|
+
# Indy.search(LOG_FILE).with(/^(\d{2}.\d{2}.\d{2})\s*(.+)$/,:time,:message)
|
108
99
|
#
|
109
100
|
def with(pattern_array = :default)
|
110
|
-
@pattern = pattern_array == :default ?
|
101
|
+
@pattern = pattern_array == :default ? DEFAULT_LOG_PATTERN : pattern_array
|
111
102
|
@time_field = @pattern[1..-1].include?(:time) ? :time : nil
|
112
103
|
self
|
113
104
|
end
|
@@ -119,345 +110,311 @@ class Indy
|
|
119
110
|
# value to compare against the other log messages. This function also
|
120
111
|
# supports symbol :all to return all messages
|
121
112
|
#
|
122
|
-
def
|
113
|
+
def for(search_criteria)
|
123
114
|
results = ResultSet.new
|
124
115
|
|
125
|
-
case
|
126
|
-
when
|
116
|
+
case search_criteria
|
117
|
+
when Enumerable
|
127
118
|
results += _search do |result|
|
128
119
|
OpenStruct.new(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
|
129
120
|
end
|
130
|
-
when
|
121
|
+
when :all
|
131
122
|
results += _search {|result| OpenStruct.new(result) }
|
132
123
|
end
|
133
124
|
|
134
125
|
results
|
135
126
|
end
|
136
127
|
|
137
|
-
alias_method :for, :search
|
138
128
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
129
|
+
#
|
130
|
+
# Search the source and make a regular expression comparison
|
131
|
+
#
|
132
|
+
# @param [Hash] search_criteria the field to search for as the key and the
|
133
|
+
# value to compare against the other log messages
|
134
|
+
#
|
135
|
+
# @example For all applications that end with Service
|
136
|
+
#
|
137
|
+
# Indy.search(LOG_FILE).like(:application => '(.+)Service')
|
138
|
+
#
|
139
|
+
def like(search_criteria)
|
140
|
+
results = ResultSet.new
|
151
141
|
|
152
|
-
|
153
|
-
|
154
|
-
|
142
|
+
results += _search do |result|
|
143
|
+
OpenStruct.new(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/ }.empty?
|
144
|
+
end
|
155
145
|
|
156
|
-
|
157
|
-
|
146
|
+
results
|
147
|
+
end
|
158
148
|
|
159
|
-
|
149
|
+
alias_method :matching, :like
|
160
150
|
|
161
151
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
152
|
+
#
|
153
|
+
# After scopes the eventual search to all entries after to this point.
|
154
|
+
#
|
155
|
+
# @param [Hash] scope_criteria the field to scope for as the key and the
|
156
|
+
# value to compare against the other log messages
|
157
|
+
#
|
158
|
+
# @example For all messages after specified date
|
159
|
+
#
|
160
|
+
# Indy.search(LOG_FILE).after(:time => time).for(:all)
|
161
|
+
#
|
162
|
+
def after(scope_criteria)
|
163
|
+
if scope_criteria[:time]
|
164
|
+
time = parse_date(scope_criteria[:time])
|
165
|
+
@inclusive = scope_criteria[:inclusive] || false
|
166
|
+
|
167
|
+
if scope_criteria[:span]
|
168
|
+
span = (scope_criteria[:span].to_i * 60).seconds
|
169
|
+
within(:time => [time, time + span])
|
170
|
+
else
|
171
|
+
@start_time = time
|
172
|
+
end
|
182
173
|
end
|
183
|
-
end
|
184
174
|
|
185
|
-
|
186
|
-
|
175
|
+
self
|
176
|
+
end
|
187
177
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
178
|
+
#
|
179
|
+
# Before scopes the eventual search to all entries prior to this point.
|
180
|
+
#
|
181
|
+
# @param [Hash] scope_criteria the field to scope for as the key and the
|
182
|
+
# value to compare against the other log messages
|
183
|
+
#
|
184
|
+
# @example For all messages before specified date
|
185
|
+
#
|
186
|
+
# Indy.search(LOG_FILE).before(:time => time).for(:all)
|
187
|
+
# Indy.search(LOG_FILE).before(:time => time, :span => 10).for(:all)
|
188
|
+
#
|
189
|
+
def before(scope_criteria)
|
190
|
+
if scope_criteria[:time]
|
191
|
+
time = parse_date(scope_criteria[:time])
|
192
|
+
@inclusive = scope_criteria[:inclusive] || false
|
193
|
+
|
194
|
+
if scope_criteria[:span]
|
195
|
+
span = (scope_criteria[:span].to_i * 60).seconds
|
196
|
+
within(:time => [time - span, time], :inclusive => scope_criteria[:inclusive])
|
197
|
+
else
|
198
|
+
@end_time = time
|
199
|
+
end
|
209
200
|
end
|
201
|
+
|
202
|
+
self
|
210
203
|
end
|
211
204
|
|
212
|
-
|
213
|
-
|
205
|
+
def around(scope_criteria)
|
206
|
+
if scope_criteria[:time]
|
207
|
+
time = parse_date(scope_criteria[:time])
|
214
208
|
|
215
|
-
|
216
|
-
|
217
|
-
time = parse_date(scope_criteria[:time])
|
209
|
+
# does @inclusive add any real value to the #around method?
|
210
|
+
@inclusive = scope_criteria[:inclusive] || false
|
218
211
|
|
219
|
-
|
220
|
-
|
212
|
+
half_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
|
213
|
+
within(:time => [time - half_span, time + half_span])
|
214
|
+
end
|
221
215
|
|
222
|
-
|
223
|
-
within(:time => [time - half_span, time + half_span])
|
216
|
+
self
|
224
217
|
end
|
225
218
|
|
226
|
-
self
|
227
|
-
end
|
228
219
|
|
220
|
+
#
|
221
|
+
# Within scopes the eventual search to all entries between two points.
|
222
|
+
#
|
223
|
+
# @param [Hash] scope_criteria the field to scope for as the key and the
|
224
|
+
# value to compare against the other log messages
|
225
|
+
#
|
226
|
+
# @example For all messages within the specified dates
|
227
|
+
#
|
228
|
+
# Indy.search(LOG_FILE).within(:time => [start_time,stop_time]).for(:all)
|
229
|
+
#
|
230
|
+
def within(scope_criteria)
|
231
|
+
if scope_criteria[:time]
|
232
|
+
@start_time, @end_time = scope_criteria[:time]
|
233
|
+
@inclusive = scope_criteria[:inclusive] || false
|
234
|
+
end
|
229
235
|
|
230
|
-
|
231
|
-
# Within scopes the eventual search to all entries between two points.
|
232
|
-
#
|
233
|
-
# @param [Hash] scope_criteria the field to scope for as the key and the
|
234
|
-
# value to compare against the other log messages
|
235
|
-
#
|
236
|
-
# @example For all messages within the specified dates
|
237
|
-
#
|
238
|
-
# Indy.search(LOG_FILE).within(:time => [start_time,stop_time]).for(:all)
|
239
|
-
#
|
240
|
-
def within(scope_criteria)
|
241
|
-
if scope_criteria[:time]
|
242
|
-
@start_time, @end_time = scope_criteria[:time]
|
243
|
-
@inclusive = scope_criteria[:inclusive] || false
|
236
|
+
self
|
244
237
|
end
|
245
238
|
|
246
|
-
self
|
247
|
-
end
|
248
239
|
|
240
|
+
private
|
249
241
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
# @example Custom Level and Below
|
263
|
-
#
|
264
|
-
# Indy.search(LOG_FILE).with([CUSTOM_PATTERN,time,severity,message]).severity(:yellow,:equal_and_below,[:green,:yellow,:orange,:red])
|
265
|
-
# Indy.search(LOG_FILE).with([CUSTOM_PATTERN,time,severity,message]).matching(:severity => '(GREEN|YELLOW)')
|
266
|
-
#
|
267
|
-
def severity(severity,direction = :equal,scale = SEVERITY)
|
268
|
-
severity = severity.to_s.downcase.to_sym
|
269
|
-
|
270
|
-
case direction
|
271
|
-
when :equal
|
272
|
-
severity = [severity]
|
273
|
-
when :equal_and_above
|
274
|
-
severity = scale[scale.index(severity)..-1]
|
275
|
-
when :equal_and_below
|
276
|
-
severity = scale[0..scale.index(severity)]
|
277
|
-
end
|
242
|
+
#
|
243
|
+
# Sets the source for the Indy instance.
|
244
|
+
#
|
245
|
+
# @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
|
246
|
+
#
|
247
|
+
# @example
|
248
|
+
#
|
249
|
+
# source("apache.log")
|
250
|
+
# source(:cmd => "cat apache.log")
|
251
|
+
# source("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.")
|
252
|
+
#
|
253
|
+
def source=(param)
|
278
254
|
|
279
|
-
|
255
|
+
cmd = param[:cmd] rescue nil
|
256
|
+
@source[:cmd] = param[:cmd] if cmd
|
280
257
|
|
281
|
-
|
258
|
+
unless cmd
|
259
|
+
File.exist?(param) ? @source[:file] = param : @source[:string] = param
|
260
|
+
end
|
282
261
|
|
283
|
-
|
262
|
+
end
|
284
263
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
# source("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.")
|
295
|
-
#
|
296
|
-
def source=(specified_source)
|
264
|
+
#
|
265
|
+
# Search the @source and yield to the block the line that was found
|
266
|
+
# with @pattern
|
267
|
+
#
|
268
|
+
# This method is supposed to be used internally.
|
269
|
+
#
|
270
|
+
def _search(&block)
|
271
|
+
time_search = use_time_criteria?
|
272
|
+
source_io = open_source
|
297
273
|
|
298
|
-
|
274
|
+
results = source_io.each.collect do |line|
|
299
275
|
|
300
|
-
|
301
|
-
possible_source = try_as_command(cmd)
|
302
|
-
@source_info[:cmd] = specified_source[:cmd]
|
303
|
-
else
|
276
|
+
hash = parse_line(line, @pattern)
|
304
277
|
|
305
|
-
|
278
|
+
if time_search
|
279
|
+
set_time(hash)
|
280
|
+
next unless inside_time_window?(hash)
|
281
|
+
end
|
282
|
+
next unless hash
|
283
|
+
|
284
|
+
block_given? ? block.call(hash) : nil
|
306
285
|
|
307
|
-
if possible_source
|
308
|
-
@source_info[:file] = specified_source
|
309
|
-
else
|
310
|
-
possible_source = StringIO.new(specified_source.to_s)
|
311
|
-
@source_info[:string] = specified_source
|
312
286
|
end
|
287
|
+
|
288
|
+
source_io.close if @source[:file] || @source[:cmd]
|
289
|
+
|
290
|
+
results.compact
|
313
291
|
end
|
314
292
|
|
315
|
-
@source = possible_source
|
316
|
-
end
|
317
293
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
# @param [IO] source is a Ruby IO object
|
324
|
-
#
|
325
|
-
def _search(source = @source,pattern_array = @pattern,&block)
|
294
|
+
#
|
295
|
+
# Return a log io object
|
296
|
+
#
|
297
|
+
def open_source
|
298
|
+
begin
|
326
299
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
300
|
+
case @source.keys.first # and only
|
301
|
+
when :cmd
|
302
|
+
source_io = exec_command(@source[:cmd])
|
303
|
+
raise "Failed to execute command (#{@source[:cmd]})" if source_io.nil?
|
331
304
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
source.rewind
|
336
|
-
actual_source = source.dup
|
337
|
-
end
|
305
|
+
when :file
|
306
|
+
source_io = File.open(@source[:file], 'r')
|
307
|
+
raise "Filed to open file: #{@source[:file]}" if source_io.nil?
|
338
308
|
|
339
|
-
|
309
|
+
when :string
|
310
|
+
source_io = StringIO.new( @source[:string] )
|
340
311
|
|
341
|
-
|
312
|
+
else
|
313
|
+
raise "Unsupported log source: #{@source.inspect}"
|
314
|
+
end
|
342
315
|
|
343
|
-
|
344
|
-
|
345
|
-
next unless inside_time_window?(hash)
|
316
|
+
rescue Exception => e
|
317
|
+
raise "Unable to open log source. (#{e.message})"
|
346
318
|
end
|
347
319
|
|
348
|
-
|
349
|
-
|
350
|
-
block_given? ? block.call(hash) : nil
|
320
|
+
source_io
|
351
321
|
end
|
352
322
|
|
323
|
+
#
|
324
|
+
# Return a hash of field=>value pairs for the log line
|
325
|
+
#
|
326
|
+
# @param [String] line The log line
|
327
|
+
# @param [Array] pattern_array The match regexp string, followed by log fields
|
328
|
+
# see Class method search
|
329
|
+
#
|
330
|
+
def parse_line( line, pattern_array = @pattern)
|
331
|
+
regexp, *fields = pattern_array
|
353
332
|
|
354
|
-
|
355
|
-
|
333
|
+
if /#{regexp}/.match(line)
|
334
|
+
values = /#{regexp}/.match(line).captures
|
335
|
+
raise "Field mismatch between log pattern and log data. The data is: '#{values.join(':::')}'" unless values.length == fields.length
|
356
336
|
|
357
|
-
|
358
|
-
|
359
|
-
#
|
360
|
-
# @param [String] line The log line
|
361
|
-
# @param [Array] pattern_array The match regexp string, followed by log fields
|
362
|
-
# see Class method search
|
363
|
-
#
|
364
|
-
def parse_line( line, pattern_array = @pattern)
|
365
|
-
regexp, *fields = pattern_array
|
337
|
+
hash = Hash[ *fields.zip( values ).flatten ]
|
338
|
+
hash[:line] = line.strip
|
366
339
|
|
367
|
-
|
368
|
-
|
369
|
-
|
340
|
+
hash
|
341
|
+
end
|
342
|
+
end
|
370
343
|
|
371
|
-
|
372
|
-
|
344
|
+
#
|
345
|
+
# Return true if start or end time has been set, and a :time field exists
|
346
|
+
#
|
347
|
+
def use_time_criteria?
|
348
|
+
if @start_time || @end_time
|
349
|
+
# ensure both boundaries are set
|
350
|
+
@start_time = @start_time || FOREVER_AGO
|
351
|
+
@end_time = @end_time || FOREVER
|
352
|
+
end
|
373
353
|
|
374
|
-
|
354
|
+
return (@time_field && @start_time && @end_time)
|
375
355
|
end
|
376
|
-
end
|
377
|
-
|
378
|
-
#
|
379
|
-
# Set the :_time value in the hash
|
380
|
-
#
|
381
|
-
# @param [Hash] hash The log line hash to modify
|
382
|
-
#
|
383
|
-
def set_time(hash)
|
384
|
-
hash[:_time] = parse_date( hash ) if hash
|
385
|
-
end
|
386
356
|
|
387
|
-
#
|
388
|
-
# Evaluate if a log line satisfies the configured time conditions
|
389
|
-
#
|
390
|
-
# @param [Hash] line_hash The log line hash to be evaluated
|
391
|
-
#
|
392
|
-
def inside_time_window?( line_hash )
|
393
357
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
358
|
+
#
|
359
|
+
# Set the :_time value in the hash
|
360
|
+
#
|
361
|
+
# @param [Hash] hash The log line hash to modify
|
362
|
+
#
|
363
|
+
def set_time(hash)
|
364
|
+
hash[:_time] = parse_date( hash ) if hash
|
400
365
|
end
|
401
366
|
|
402
|
-
|
367
|
+
#
|
368
|
+
# Evaluate if a log line satisfies the configured time conditions
|
369
|
+
#
|
370
|
+
# @param [Hash] line_hash The log line hash to be evaluated
|
371
|
+
#
|
372
|
+
def inside_time_window?( line_hash )
|
403
373
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
# @param [String, Hash] param The log line hash, or string to be evaluated
|
408
|
-
#
|
409
|
-
def parse_date(param)
|
410
|
-
return nil unless @time_field
|
411
|
-
|
412
|
-
time_string = param[@time_field] ? param[@time_field] : param
|
413
|
-
|
414
|
-
begin
|
415
|
-
# Attempt the appropriate parse method
|
416
|
-
date = @time_format ? DateTime.strptime(time_string, @time_format) : DateTime.parse(time_string)
|
417
|
-
rescue
|
418
|
-
begin
|
419
|
-
# If appropriate, fall back to simple parse method
|
420
|
-
if @time_format
|
421
|
-
date = DateTime.parse(time_string)
|
374
|
+
if line_hash && line_hash[:_time]
|
375
|
+
if @inclusive
|
376
|
+
true unless line_hash[:_time] > @end_time or line_hash[:_time] < @start_time
|
422
377
|
else
|
423
|
-
|
378
|
+
true unless line_hash[:_time] >= @end_time or line_hash[:_time] <= @start_time
|
424
379
|
end
|
425
|
-
rescue ArgumentError
|
426
|
-
date = @time_field = nil
|
427
380
|
end
|
381
|
+
|
428
382
|
end
|
429
|
-
date
|
430
|
-
end
|
431
383
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
384
|
+
#
|
385
|
+
# Return a valid DateTime object for the log line or string
|
386
|
+
#
|
387
|
+
# @param [String, Hash] param The log line hash, or string to be evaluated
|
388
|
+
#
|
389
|
+
def parse_date(param)
|
390
|
+
return nil unless @time_field
|
438
391
|
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
392
|
+
time_string = param[@time_field] ? param[@time_field] : param
|
393
|
+
|
394
|
+
begin
|
395
|
+
# Attempt the appropriate parse method
|
396
|
+
@time_format ? DateTime.strptime(time_string, @time_format) : DateTime.parse(time_string)
|
397
|
+
rescue
|
398
|
+
# If appropriate, fall back to simple parse method
|
399
|
+
DateTime.parse(time_string) if @time_format rescue nil
|
400
|
+
end
|
444
401
|
end
|
445
|
-
io
|
446
|
-
end
|
447
402
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
403
|
+
#
|
404
|
+
# Try opening the string as a command string, returning an IO object
|
405
|
+
#
|
406
|
+
# @param [String] command_string string of command that will return log contents
|
407
|
+
#
|
408
|
+
def exec_command(command_string)
|
454
409
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
410
|
+
begin
|
411
|
+
io = IO.popen(command_string)
|
412
|
+
return nil if io.eof?
|
413
|
+
rescue
|
414
|
+
nil
|
415
|
+
end
|
416
|
+
io
|
459
417
|
end
|
460
418
|
|
461
|
-
end
|
462
419
|
|
463
|
-
end
|
420
|
+
end
|