log_line_parser 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +50 -1
- data/lib/log_line_parser/bots.rb +56 -0
- data/lib/log_line_parser/command_line_interface.rb +52 -22
- data/lib/log_line_parser/query.rb +24 -23
- data/lib/log_line_parser/utils.rb +14 -5
- data/lib/log_line_parser/version.rb +1 -1
- data/lib/log_line_parser.rb +1 -1
- data/samples/output/index-page-accessed-by-bot.log +1 -0
- data/samples/sample_combined_log.log +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ce0b6e321f311f85bc375942205567ab4d60743
|
4
|
+
data.tar.gz: ee2d0b41508c833d39cade577581426dbb06cb23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2b363c6be57677fda905270e36f0ee23c39f9cb0915199eaad63a5fabc566a814437e92ad1706040e874e171107c3c901e90d195c1d530ea4e0282edac0d2b6
|
7
|
+
data.tar.gz: ea2ae4f5b9c0eb962c221e605927f0cdf977c194d612fff620a0cc2bb113825620762b67c4e377abab4654dcc1877dc3134f73625d07119d7cfa70610eb9d2a7
|
data/README.md
CHANGED
@@ -130,7 +130,7 @@ Second, run the following command if you want to pick up from [samples/sample_co
|
|
130
130
|
|
131
131
|
Then the results are in [samples/output](https://github.com/nico-hn/LogLineParser/tree/master/samples/output/) directory.
|
132
132
|
|
133
|
-
##### Format of configuration
|
133
|
+
##### Format of configuration file
|
134
134
|
|
135
135
|
An example of configurations is below:
|
136
136
|
|
@@ -182,6 +182,7 @@ It contains three configurations, and each of them consists of parameters in the
|
|
182
182
|
|Available criteria |Note |
|
183
183
|
|----------------------------------------|------------------------------------------------------------------------------------------|
|
184
184
|
|:access_by_bots? |Access by major web crawlers such as Googlebot or Bingbot. |
|
185
|
+
|:access_to_image? |The value of "%U%q" matches /\.(?:jpe?g\|png\|gif\|ico\|tiff?\|bmp\|svgz?\|webp)$/in |
|
185
186
|
|:referred_from_resources? |The path part of the value of "%{Referer}i" matches any of the values of "resources". |
|
186
187
|
|:referred_from_under_resources? |The path part of the value of "%{Referer}i" begins with any of the values of "resources". |
|
187
188
|
|:access_to_resources? |The value of "%U%q" matches any of the values of "resources". |
|
@@ -201,6 +202,54 @@ It contains three configurations, and each of them consists of parameters in the
|
|
201
202
|
|:patch_method? |The value of "%m" is PATCH. |
|
202
203
|
|
203
204
|
|
205
|
+
##### Default web crawlers
|
206
|
+
|
207
|
+
The following web crawlers are set by default and used by Query#access_by_bots?:
|
208
|
+
|
209
|
+
* Googlebot
|
210
|
+
* Googlebot-Mobile
|
211
|
+
* Mediapartners-Google
|
212
|
+
* Bingbot
|
213
|
+
* Slurp
|
214
|
+
* Baiduspider
|
215
|
+
* BaiduImagespider
|
216
|
+
* BaiduMobaider
|
217
|
+
* YetiBot
|
218
|
+
* Applebot
|
219
|
+
|
220
|
+
|
221
|
+
##### Format of bots configuration file
|
222
|
+
|
223
|
+
You can specify web crawlers by giving a configuration file to `--bots-config` option. The following is an example of configuration file:
|
224
|
+
|
225
|
+
```yaml
|
226
|
+
inherit_default_bots: false
|
227
|
+
bots:
|
228
|
+
- Googlebot
|
229
|
+
- Googlebot-Mobile
|
230
|
+
- Mediapartners-Google
|
231
|
+
- Bingbot
|
232
|
+
- Slurp
|
233
|
+
- Baiduspider
|
234
|
+
- BaiduImagespider
|
235
|
+
- BaiduMobaider
|
236
|
+
- YetiBot
|
237
|
+
- Applebot
|
238
|
+
bots_re:
|
239
|
+
- " bot$"
|
240
|
+
```
|
241
|
+
|
242
|
+
|Parameters |Note |
|
243
|
+
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
244
|
+
|bots (optional) |Names of web crawlers that make Query#access_by_bots? return true when they are included in the value of "%{Referer}i". |
|
245
|
+
|inherit_default_bots (optional) |If this option is set to true, the default names of major web crawlers are added to the names specified by `bots` parameter. |
|
246
|
+
|bots_re (optional) |Use this parameter if you want to identify bots by regular expressions. |
|
247
|
+
|
248
|
+
The values given by the configuration file are compiled into a regular expression, and you can check the expression by invoking the tool as follows:
|
249
|
+
|
250
|
+
$ log_line_parser --show-current-settings --bots-config=bots_config.yml
|
251
|
+
|
252
|
+
|
204
253
|
## Development
|
205
254
|
|
206
255
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec log_line_parser` to use the code located in this directory, ignoring other installed copies of this gem.
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module LogLineParser
|
4
|
+
module Bots
|
5
|
+
module ConfigLabels
|
6
|
+
INHERIT_DEFAULT_BOTS = "inherit_default_bots"
|
7
|
+
BOTS = "bots"
|
8
|
+
BOTS_RE = "bots_re"
|
9
|
+
end
|
10
|
+
|
11
|
+
DEFAULT_BOTS = %w(
|
12
|
+
Googlebot
|
13
|
+
Googlebot-Mobile
|
14
|
+
Mediapartners-Google
|
15
|
+
Bingbot
|
16
|
+
Slurp
|
17
|
+
Baiduspider
|
18
|
+
BaiduImagespider
|
19
|
+
BaiduMobaider
|
20
|
+
YetiBot
|
21
|
+
Applebot
|
22
|
+
)
|
23
|
+
|
24
|
+
DEFAULT_CONFIG = {
|
25
|
+
ConfigLabels::INHERIT_DEFAULT_BOTS => true,
|
26
|
+
ConfigLabels::BOTS => [],
|
27
|
+
ConfigLabels::BOTS_RE => nil
|
28
|
+
}
|
29
|
+
|
30
|
+
def self.compile_bots_re(bots_config=DEFAULT_CONFIG)
|
31
|
+
escaped_re = compile_escaped_re(bots_config)
|
32
|
+
re = compile_re(bots_config)
|
33
|
+
return Regexp.union(escaped_re, re) if escaped_re && re
|
34
|
+
escaped_re || re
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.compile_escaped_re(bots_config)
|
38
|
+
bot_names = bots_config[ConfigLabels::BOTS] || []
|
39
|
+
if bots_config[ConfigLabels::INHERIT_DEFAULT_BOTS]
|
40
|
+
bot_names = (DEFAULT_BOTS + bot_names).uniq
|
41
|
+
end
|
42
|
+
return if bot_names.empty?
|
43
|
+
escaped_bots_str = bot_names.map {|name| Regexp.escape(name) }.join("|")
|
44
|
+
Regexp.compile(escaped_bots_str, Regexp::IGNORECASE, "n")
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.compile_re(bots_config)
|
48
|
+
bots_pats = bots_config[ConfigLabels::BOTS_RE]
|
49
|
+
Regexp.compile(bots_pats.join("|"), nil, "n") if bots_pats
|
50
|
+
end
|
51
|
+
|
52
|
+
private_class_method :compile_escaped_re, :compile_re
|
53
|
+
|
54
|
+
DEFAULT_RE = compile_bots_re
|
55
|
+
end
|
56
|
+
end
|
@@ -12,10 +12,6 @@ module LogLineParser
|
|
12
12
|
|
13
13
|
DEFAULT_FORMAT = "csv"
|
14
14
|
|
15
|
-
def self.read_configs(config)
|
16
|
-
YAML.load_stream(config).to_a
|
17
|
-
end
|
18
|
-
|
19
15
|
def self.parse_options
|
20
16
|
options = {}
|
21
17
|
|
@@ -25,6 +21,17 @@ module LogLineParser
|
|
25
21
|
options[:config_file] = config_file
|
26
22
|
end
|
27
23
|
|
24
|
+
opt.on("-b [bots_config_file]", "--bots-config [=bots_config_file]",
|
25
|
+
"Give a configuration file in yaml format. \
|
26
|
+
Default bots: #{Bots::DEFAULT_BOTS.join(', ')}") do |config_file|
|
27
|
+
options[:bots_config_file] = config_file
|
28
|
+
end
|
29
|
+
|
30
|
+
opt.on("-s", "--show-current-settings",
|
31
|
+
"Show the detail of the current settings") do
|
32
|
+
options[:show_settings] = true
|
33
|
+
end
|
34
|
+
|
28
35
|
opt.on("-f", "--filter-mode",
|
29
36
|
"Mode for choosing log records that satisfy certain criteria") do
|
30
37
|
options[:filter_mode] = true
|
@@ -52,12 +59,6 @@ formats predefined as #{predefined_options_for_log_format}") do |log_format|
|
|
52
59
|
options
|
53
60
|
end
|
54
61
|
|
55
|
-
def self.load_config_file(config_file)
|
56
|
-
open(File.expand_path(config_file)) do |f|
|
57
|
-
read_configs(f.read)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
62
|
def self.choose_log_parser(log_format)
|
62
63
|
return LogLineParser::CombinedLogParser unless log_format
|
63
64
|
parser = LogLineParser::PREDEFINED_FORMATS[log_format]
|
@@ -66,24 +67,28 @@ formats predefined as #{predefined_options_for_log_format}") do |log_format|
|
|
66
67
|
|
67
68
|
def self.execute
|
68
69
|
options = parse_options
|
69
|
-
if options[:
|
70
|
+
if options[:show_settings]
|
71
|
+
show_settings(options)
|
72
|
+
elsif options[:filter_mode]
|
70
73
|
execute_as_filter(options)
|
71
74
|
else
|
72
75
|
execute_as_converter(options)
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
79
|
+
def self.show_settings(options)
|
80
|
+
bots_re = compile_bots_re_from_config_file(options[:bots_config_file])
|
81
|
+
parser = choose_log_parser(options[:log_format])
|
82
|
+
puts "The regular expression for bots: #{bots_re}"
|
83
|
+
puts "LogFormat: #{parser.format_strings}"
|
84
|
+
end
|
85
|
+
|
76
86
|
def self.execute_as_filter(options)
|
77
|
-
configs = load_config_file(options[:config_file])
|
87
|
+
configs = Utils.load_config_file(options[:config_file])
|
78
88
|
parser = choose_log_parser(options[:log_format])
|
79
89
|
output_dir = options[:output_dir]
|
80
|
-
|
81
|
-
|
82
|
-
queries = setup_queries_from_configs(configs, logs)
|
83
|
-
LogLineParser.each_record(parser: parser) do |line, record|
|
84
|
-
queries.each {|query| query.call(line, record) }
|
85
|
-
end
|
86
|
-
end
|
90
|
+
bots_re = compile_bots_re_from_config_file(options[:bots_config_file])
|
91
|
+
execute_queries(configs, parser, output_dir, bots_re)
|
87
92
|
end
|
88
93
|
|
89
94
|
def self.execute_as_converter(options, output=STDOUT, input=ARGF)
|
@@ -101,7 +106,7 @@ formats predefined as #{predefined_options_for_log_format}") do |log_format|
|
|
101
106
|
end
|
102
107
|
end
|
103
108
|
|
104
|
-
private
|
109
|
+
# private class methods
|
105
110
|
|
106
111
|
def self.predefined_options_for_log_format
|
107
112
|
PREDEFINED_FORMATS.keys.
|
@@ -109,15 +114,31 @@ formats predefined as #{predefined_options_for_log_format}") do |log_format|
|
|
109
114
|
join(", ")
|
110
115
|
end
|
111
116
|
|
117
|
+
def self.compile_bots_re_from_config_file(bots_config_file)
|
118
|
+
return Bots::DEFAULT_RE unless bots_config_file
|
119
|
+
configs = Utils.load_config_file(bots_config_file)[0]
|
120
|
+
Bots.compile_bots_re(configs)
|
121
|
+
end
|
122
|
+
|
112
123
|
def self.collect_output_log_names(configs)
|
113
124
|
configs.map do |config|
|
114
125
|
config[Query::ConfigFields::OUTPUT_LOG_NAME]
|
115
126
|
end
|
116
127
|
end
|
117
128
|
|
118
|
-
def self.
|
129
|
+
def self.execute_queries(configs, parser, output_dir, bots_re)
|
130
|
+
output_log_names = collect_output_log_names(configs)
|
131
|
+
Utils.open_multiple_output_files(output_log_names, output_dir) do |logs|
|
132
|
+
queries = setup_queries_from_configs(configs, logs, bots_re)
|
133
|
+
LogLineParser.each_record(parser: parser) do |line, record|
|
134
|
+
queries.each {|query| query.call(line, record) }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.setup_queries_from_configs(configs, logs, bots_re)
|
119
140
|
configs.map do |config|
|
120
|
-
Query.register_query_to_log(config, logs)
|
141
|
+
Query.register_query_to_log(config, logs, bots_re)
|
121
142
|
end
|
122
143
|
end
|
123
144
|
|
@@ -138,5 +159,14 @@ formats predefined as #{predefined_options_for_log_format}") do |log_format|
|
|
138
159
|
output.puts parser.to_ltsv(line.chomp)
|
139
160
|
end
|
140
161
|
end
|
162
|
+
|
163
|
+
private_class_method(:predefined_options_for_log_format,
|
164
|
+
:compile_bots_re_from_config_file,
|
165
|
+
:collect_output_log_names,
|
166
|
+
:execute_queries,
|
167
|
+
:setup_queries_from_configs,
|
168
|
+
:convert_to_csv,
|
169
|
+
:convert_to_tsv,
|
170
|
+
:convert_to_ltsv)
|
141
171
|
end
|
142
172
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'log_line_parser/bots'
|
4
|
+
|
3
5
|
module LogLineParser
|
4
6
|
class Query
|
5
7
|
class NotAllowableMethodError < StandardError; end
|
@@ -18,20 +20,12 @@ module LogLineParser
|
|
18
20
|
|
19
21
|
TAIL_SLASH_RE = /\/$/
|
20
22
|
SLASH = '/'
|
21
|
-
|
22
|
-
|
23
|
-
Googlebot-Mobile
|
24
|
-
Mediapartners-Google
|
25
|
-
Bingbot
|
26
|
-
Slurp
|
27
|
-
Baiduspider
|
28
|
-
BaiduImagespider
|
29
|
-
BaiduMobaider
|
30
|
-
YetiBot
|
31
|
-
)
|
23
|
+
|
24
|
+
IMAGE_FILE_RE = /\.(?:jpe?g|png|gif|ico|tiff?|bmp|svgz?|webp)$/in
|
32
25
|
|
33
26
|
ALLOWABLE_METHODS = [
|
34
27
|
:access_by_bots?,
|
28
|
+
:access_to_image?,
|
35
29
|
:referred_from_resources?,
|
36
30
|
:referred_from_under_resources?,
|
37
31
|
:access_to_resources?,
|
@@ -64,15 +58,12 @@ YetiBot
|
|
64
58
|
MATCH_TYPE = "match_type" # The value should be "all" or "any".
|
65
59
|
end
|
66
60
|
|
67
|
-
def self.
|
68
|
-
|
69
|
-
Regexp.compile(bots_str, Regexp::IGNORECASE)
|
61
|
+
def self.access_by_bots?(record, bots_re=Bots::DEFAULT_RE)
|
62
|
+
bots_re =~ record.user_agent
|
70
63
|
end
|
71
64
|
|
72
|
-
|
73
|
-
|
74
|
-
def self.access_by_bots?(record, bots_re=DEFAULT_BOTS_RE)
|
75
|
-
bots_re =~ record.user_agent
|
65
|
+
def self.access_to_image?(record)
|
66
|
+
IMAGE_FILE_RE =~ record.resource
|
76
67
|
end
|
77
68
|
|
78
69
|
##
|
@@ -95,10 +86,15 @@ YetiBot
|
|
95
86
|
record.resource.start_with?(path)
|
96
87
|
end
|
97
88
|
|
89
|
+
def self.referred_from_host?(record, host_name)
|
90
|
+
record.referer_host == host_name
|
91
|
+
end
|
92
|
+
|
98
93
|
class << self
|
99
|
-
def register_query_to_log(option, logs)
|
94
|
+
def register_query_to_log(option, logs, bots_re=Bots::DEFAULT_RE)
|
100
95
|
query = Query.new(domain: option[ConfigFields::HOST_NAME],
|
101
|
-
resources: option[ConfigFields::RESOURCES]
|
96
|
+
resources: option[ConfigFields::RESOURCES],
|
97
|
+
bots_re: bots_re)
|
102
98
|
queries = option[ConfigFields::MATCH]
|
103
99
|
reject_unacceptable_queries(queries)
|
104
100
|
log = logs[option[ConfigFields::OUTPUT_LOG_NAME]]
|
@@ -176,15 +172,20 @@ YetiBot
|
|
176
172
|
end
|
177
173
|
end
|
178
174
|
|
179
|
-
def initialize(domain: nil, resources: [])
|
175
|
+
def initialize(domain: nil, resources: [], bots_re: Bots::DEFAULT_RE)
|
180
176
|
@domain = domain
|
181
177
|
@resources = normalize_resources(resources)
|
178
|
+
@bots_re = bots_re
|
182
179
|
@normalized_resources = normalize_resources(resources)
|
183
180
|
@normalized_dirs = @normalized_resources - @resources
|
184
181
|
end
|
185
182
|
|
186
|
-
def access_by_bots?(record
|
187
|
-
bots_re =~ record.user_agent
|
183
|
+
def access_by_bots?(record)
|
184
|
+
@bots_re =~ record.user_agent
|
185
|
+
end
|
186
|
+
|
187
|
+
def access_to_image?(record)
|
188
|
+
IMAGE_FILE_RE =~ record.resource
|
188
189
|
end
|
189
190
|
|
190
191
|
##
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'log_line_parser'
|
4
4
|
require 'log_line_parser/query'
|
5
5
|
require 'csv'
|
6
|
+
require 'yaml'
|
6
7
|
|
7
8
|
module LogLineParser
|
8
9
|
module Utils
|
@@ -15,10 +16,6 @@ module LogLineParser
|
|
15
16
|
}
|
16
17
|
SPECIAL_CHARS_RE = Regexp.compile(SPECIAL_CHARS.keys.join("|"))
|
17
18
|
|
18
|
-
def self.access_by_bots?(record, bots_re=Query::DEFAULT_BOTS_RE)
|
19
|
-
Query.access_by_bots?(record, bots_re)
|
20
|
-
end
|
21
|
-
|
22
19
|
def self.open_multiple_output_files(base_names, dir=nil, ext="log")
|
23
20
|
logs = {}
|
24
21
|
filepath = dir ? File.join(dir, "%s.#{ext}") : "%s.#{ext}"
|
@@ -32,6 +29,16 @@ module LogLineParser
|
|
32
29
|
end
|
33
30
|
end
|
34
31
|
|
32
|
+
def self.read_configs(config)
|
33
|
+
YAML.load_stream(config).to_a
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.load_config_file(config_file)
|
37
|
+
open(File.expand_path(config_file)) do |f|
|
38
|
+
read_configs(f.read)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
35
42
|
def self.to_tsv(line, escape=true)
|
36
43
|
LogLineParser.parse(line).to_a.map do |field|
|
37
44
|
escape ? escape_special_chars(field) : field
|
@@ -42,12 +49,14 @@ module LogLineParser
|
|
42
49
|
LogLineParser.parse(line).to_a.to_csv
|
43
50
|
end
|
44
51
|
|
45
|
-
private
|
52
|
+
# private class methods
|
46
53
|
|
47
54
|
def self.escape_special_chars(field)
|
48
55
|
field.gsub(SPECIAL_CHARS_RE) do |char|
|
49
56
|
SPECIAL_CHARS[char]
|
50
57
|
end
|
51
58
|
end
|
59
|
+
|
60
|
+
private_class_method :escape_special_chars
|
52
61
|
end
|
53
62
|
end
|
data/lib/log_line_parser.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
1
|
192.168.3.4 - - [07/Feb/2016:07:39:42 +0900] "GET /index.html HTTP/1.1" 200 432 "http://www.example.org" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
2
|
+
192.168.3.4 - - [07/Feb/2016:07:39:42 +0900] "GET /index.html HTTP/1.1" 200 432 "http://www.example.org" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like Gecko) Version/8.0.2 Safari/600.2.5 (Applebot/0.1)"
|
@@ -10,3 +10,4 @@
|
|
10
10
|
192.168.3.4 - quidam [07/Feb/2016:07:39:42 +0900] "GET /index.html HTTP/1.1" 200 432 "http://www.example.net/external.html" "Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.7.5) Gecko/20041108 Firefox/1.0"
|
11
11
|
192.168.3.4 - quidam [07/Feb/2016:07:39:42 +0900] "GET /index.html HTTP/1.1" 200 432 "http://www.example.net/external2.html" "Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.7.5) Gecko/20041108 Firefox/1.0"
|
12
12
|
192.168.3.4 - - [07/Feb/2016:07:39:42 +0900] "GET /index.html HTTP/1.1" 200 432 "http://www.example.org" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
13
|
+
192.168.3.4 - - [07/Feb/2016:07:39:42 +0900] "GET /index.html HTTP/1.1" 200 432 "http://www.example.org" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like Gecko) Version/8.0.2 Safari/600.2.5 (Applebot/0.1)"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: log_line_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HASHIMOTO, Naoki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -60,6 +60,7 @@ files:
|
|
60
60
|
- exe/log_line_parser
|
61
61
|
- lib/log_line_parser.rb
|
62
62
|
- lib/log_line_parser/apache.rb
|
63
|
+
- lib/log_line_parser/bots.rb
|
63
64
|
- lib/log_line_parser/command_line_interface.rb
|
64
65
|
- lib/log_line_parser/line_parser.rb
|
65
66
|
- lib/log_line_parser/ltsv.rb
|