apachecrunch 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/apachecrunch +1 -1
- data/lib/apachecrunch.rb +5 -15
- data/lib/cast.rb +21 -0
- data/lib/derivation.rb +113 -0
- data/lib/element.rb +16 -0
- data/lib/element_value_fetcher.rb +72 -0
- data/lib/entry.rb +64 -54
- data/lib/format.rb +21 -63
- data/lib/format_token.rb +114 -0
- data/lib/format_token_definition.rb +183 -0
- data/lib/log_parser.rb +39 -31
- data/lib/procedure_dsl.rb +254 -244
- data/lib/progress.rb +1 -1
- data/test/mock.rb +37 -0
- data/test/runner.rb +13 -1
- data/test/stub.rb +66 -36
- data/test/test_derived_value_fetcher.rb +36 -0
- data/test/test_element.rb +18 -0
- data/test/test_element_value_fetcher.rb +45 -0
- data/test/test_entry_parser.rb +39 -0
- data/test/test_format.rb +13 -51
- data/test/test_format_parser.rb +22 -13
- data/test/test_log_parser.rb +88 -0
- data/test/test_raw_value_fetcher.rb +36 -0
- data/test/test_regex_token.rb +17 -0
- data/test/test_req_firstline_derivation_rule.rb +41 -0
- data/test/test_reqheader_token.rb +26 -0
- data/test/test_string_token.rb +27 -0
- data/test/test_time_derivation_rule.rb +29 -0
- metadata +23 -18
- data/lib/log_element.rb +0 -351
- data/test/test_entry.rb +0 -28
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'test/stub'
|
2
|
+
require 'test/mock'
|
3
|
+
|
4
|
+
class TestLogParser < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@entry_parser = StubEntryParser.new
|
7
|
+
@inst = ApacheCrunch::LogParser.new(@entry_parser)
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
@inst = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# Tests setting the log file to a given file
|
15
|
+
def test_set_file
|
16
|
+
first_log_file = MockFile.new
|
17
|
+
assert_nothing_thrown("#{@inst.class}#set_file! threw an exception") do
|
18
|
+
@inst.set_file!(first_log_file)
|
19
|
+
end
|
20
|
+
|
21
|
+
second_log_file = MockFile.new
|
22
|
+
assert_nothing_thrown("#{@inst.class}#set_file! threw an exception") do
|
23
|
+
@inst.set_file!(second_log_file)
|
24
|
+
end
|
25
|
+
assert_equal(1, first_log_file.close_count,
|
26
|
+
"#{@inst.class}#set_file! didn't close old file")
|
27
|
+
end
|
28
|
+
|
29
|
+
# Tests replacing the log file with another file
|
30
|
+
def test_replace_file
|
31
|
+
mock_file_class = MockFileClass.new
|
32
|
+
@inst.dep_inject!(mock_file_class)
|
33
|
+
|
34
|
+
first_log_file = MockFile.new
|
35
|
+
first_log_file.path = "/first/path"
|
36
|
+
@inst.set_file!(first_log_file)
|
37
|
+
|
38
|
+
second_log_file = MockFile.new
|
39
|
+
second_log_file.path = "/second/path"
|
40
|
+
assert_nothing_thrown("#{@inst.class}#replace_file! threw an exception") do
|
41
|
+
@inst.replace_file!(second_log_file)
|
42
|
+
end
|
43
|
+
assert_equal([["/second/path", "/first/path"]], mock_file_class.rename_calls,
|
44
|
+
"#{@inst.class}#replace_file! didn't move the file into place")
|
45
|
+
assert_equal(1, first_log_file.close_count,
|
46
|
+
"#{@inst.class}#replace_file! didn't close old file")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Tests resetting the log file to its beginning
|
50
|
+
def test_reset_file
|
51
|
+
mock_file_class = MockFileClass.new
|
52
|
+
@inst.dep_inject!(mock_file_class)
|
53
|
+
|
54
|
+
log_file = MockFile.new
|
55
|
+
@inst.set_file!(log_file)
|
56
|
+
|
57
|
+
assert_nothing_thrown("#{@inst.class}#reset_file! threw an exception") do
|
58
|
+
@inst.reset_file!
|
59
|
+
end
|
60
|
+
assert_equal(1, log_file.close_count,
|
61
|
+
"#{@inst.class}#reset_file! didn't close the log file")
|
62
|
+
assert_equal(1, mock_file_class.open_calls.length,
|
63
|
+
"#{@inst.class}#reset_file! didn't reopen the log file")
|
64
|
+
end
|
65
|
+
|
66
|
+
# Tests retrieving the next entry in the log
|
67
|
+
def test_next_entry
|
68
|
+
@entry_parser.parse_return_values = [
|
69
|
+
StubEntry.new,
|
70
|
+
StubEntry.new,
|
71
|
+
nil,
|
72
|
+
StubEntry.new
|
73
|
+
]
|
74
|
+
|
75
|
+
log_file = MockFile.new
|
76
|
+
log_file.lines = ["a\n", "b\n", "c\n", "d\n"]
|
77
|
+
|
78
|
+
@inst.set_file!(log_file)
|
79
|
+
@inst.set_format!(StubFormat.new)
|
80
|
+
assert_instance_of(StubEntry, @inst.next_entry,
|
81
|
+
"#{@inst.class}#next_entry returned wrong type of thing")
|
82
|
+
assert_instance_of(StubEntry, @inst.next_entry,
|
83
|
+
"#{@inst.class}#next_entry returned wrong second value")
|
84
|
+
assert_instance_of(StubEntry, @inst.next_entry,
|
85
|
+
"#{@inst.class}#next_entry returned wrong value when it should have skipped a malformatted entry")
|
86
|
+
assert_nil(@inst.next_entry, "#{@inst.class}#next_entry returned non-nil at EOF")
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test/stub'
|
2
|
+
|
3
|
+
class TestRawValueFetcher < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@inst = ApacheCrunch::RawValueFetcher.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@inst = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# Tests a successful fetch call
|
13
|
+
def test_fetch
|
14
|
+
entry = StubEntry.new
|
15
|
+
alnum_element = StubElement.new
|
16
|
+
alnum_element.populate!(StubAlphanumericFormatToken.new, "foo123")
|
17
|
+
num_element = StubElement.new
|
18
|
+
num_element.populate!(StubNumericFormatToken.new, 54321)
|
19
|
+
|
20
|
+
entry.captured_elements = {:alnum => alnum_element, :num => num_element}
|
21
|
+
assert_equal("foo123", @inst.fetch(entry, StubAlphanumericFormatToken.new.name))
|
22
|
+
assert_equal(54321, @inst.fetch(entry, StubNumericFormatToken.new.name))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Tests a fetch call for an element that's not there
|
26
|
+
def test_fetch_missing
|
27
|
+
entry = StubEntry.new
|
28
|
+
alnum_element = StubElement.new
|
29
|
+
alnum_element.populate!(StubAlphanumericFormatToken.new, "foo123")
|
30
|
+
num_element = StubElement.new
|
31
|
+
num_element.populate!(StubNumericFormatToken.new, 54321)
|
32
|
+
|
33
|
+
entry.captured_elements = {:alnum => alnum_element, :num => num_element}
|
34
|
+
assert_nil(@inst.fetch(entry, :missing_element))
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class TestReqheaderToken < Test::Unit::TestCase
|
2
|
+
def setup
|
3
|
+
@inst = ApacheCrunch::RegexToken.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
@inst = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
# Tests that the token figures out its name correctly from the header
|
11
|
+
def test_name
|
12
|
+
@inst.populate!("foobar", "[A-Za-z0-9]+")
|
13
|
+
|
14
|
+
assert_equal(:regex_foobar, @inst.name,
|
15
|
+
"#{@inst.class} got wrong name based on regex name 'foobar'")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test/stub'
|
2
|
+
|
3
|
+
class TestReqFirstlineDerivationRule < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@inst = ApacheCrunch::ReqFirstlineDerivationRule.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@inst = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_derive
|
13
|
+
expected_names = @inst.target_names
|
14
|
+
datasets = [
|
15
|
+
["GET / HTTP/1.1", {:req_method => "GET",
|
16
|
+
:url_path => "/",
|
17
|
+
:query_string => "",
|
18
|
+
:protocol => "HTTP/1.1"}],
|
19
|
+
["HEAD /?herp=derp&foo=bar HTTP/1.0", {:req_method => "HEAD",
|
20
|
+
:url_path => "/",
|
21
|
+
:query_string => "?herp=derp&foo=bar",
|
22
|
+
:protocol => "HTTP/1.0"}],
|
23
|
+
["POST /some/page?never=gonna HTTP/1.1", {:req_method => "POST",
|
24
|
+
:url_path => "/some/page",
|
25
|
+
:query_string => "?never=gonna",
|
26
|
+
:protocol => "HTTP/1.1"}]
|
27
|
+
]
|
28
|
+
|
29
|
+
expected_names = @inst.target_names
|
30
|
+
datasets.each do |ds|
|
31
|
+
firstline_value = ds[0]
|
32
|
+
expected_values = ds[1]
|
33
|
+
|
34
|
+
expected_names = [:req_method, :url_path, :query_string, :protocol]
|
35
|
+
expected_names.each do |name|
|
36
|
+
assert_equal(expected_values[name], @inst.derive(name, firstline_value),
|
37
|
+
"#{@inst.class}#derive returned wrong value for element '#{name}'")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class TestReqheaderToken < Test::Unit::TestCase
|
2
|
+
def setup
|
3
|
+
@inst = ApacheCrunch::ReqheaderToken.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
@inst = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
# Tests that the token figures out its name correctly from the header
|
11
|
+
def test_name
|
12
|
+
datasets = [
|
13
|
+
['Host', :reqheader_host],
|
14
|
+
['X-Two-Words', :reqheader_x_two_words]
|
15
|
+
]
|
16
|
+
|
17
|
+
datasets.each do |ds|
|
18
|
+
header_name = ds[0]
|
19
|
+
expected_token_name = ds[1]
|
20
|
+
|
21
|
+
@inst.populate!(header_name)
|
22
|
+
assert_equal(expected_token_name, @inst.name,
|
23
|
+
"#{@inst.class} got wrong name based on header name '#{header_name}'")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test/stub'
|
2
|
+
|
3
|
+
class TestStringToken < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@inst = ApacheCrunch::StringToken.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@inst = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_regex
|
13
|
+
datasets = [
|
14
|
+
[' ', '\ '],
|
15
|
+
[' {has (regex [chars', '\ \{has\ \(regex\ \[chars']
|
16
|
+
]
|
17
|
+
|
18
|
+
datasets.each do |ds|
|
19
|
+
string_value = ds[0]
|
20
|
+
expected_regex = ds[1]
|
21
|
+
|
22
|
+
@inst.populate!(string_value)
|
23
|
+
assert_equal(expected_regex, @inst.regex,
|
24
|
+
"#{@inst.class}#regex returned incorrect value")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test/stub'
|
2
|
+
|
3
|
+
class TestTimeDerivationRule < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@inst = ApacheCrunch::TimeDerivationRule.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@inst = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# Tests that the expected list of derived elements matches what actually gets derived
|
13
|
+
def test_derive
|
14
|
+
expected_names = [:year, :month, :day, :hour, :minute, :second]
|
15
|
+
expected_values = {
|
16
|
+
:year => 2011,
|
17
|
+
:month => 7,
|
18
|
+
:day => 15,
|
19
|
+
:hour => 9,
|
20
|
+
:minute => 55,
|
21
|
+
:second => 41
|
22
|
+
}
|
23
|
+
|
24
|
+
expected_names.each do |name|
|
25
|
+
assert_equal(expected_values[name], @inst.derive(name, "[15/Jul/2011:09:55:41 +0400]"),
|
26
|
+
"#{@inst.class}#derive_all returned incorrect value for element #{name}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apachecrunch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 4
|
9
|
-
version: "0.4"
|
4
|
+
version: "0.5"
|
10
5
|
platform: ruby
|
11
6
|
authors:
|
12
7
|
- Dan Slimmon
|
@@ -14,7 +9,7 @@ autorequire:
|
|
14
9
|
bindir: bin
|
15
10
|
cert_chain: []
|
16
11
|
|
17
|
-
date: 2011-
|
12
|
+
date: 2011-09-17 00:00:00 -04:00
|
18
13
|
default_executable:
|
19
14
|
dependencies: []
|
20
15
|
|
@@ -31,20 +26,36 @@ extra_rdoc_files: []
|
|
31
26
|
|
32
27
|
files:
|
33
28
|
- lib/apachecrunch.rb
|
29
|
+
- lib/cast.rb
|
34
30
|
- lib/config.rb
|
31
|
+
- lib/derivation.rb
|
32
|
+
- lib/element.rb
|
33
|
+
- lib/element_value_fetcher.rb
|
35
34
|
- lib/entry.rb
|
36
35
|
- lib/format.rb
|
37
|
-
- lib/
|
36
|
+
- lib/format_token.rb
|
37
|
+
- lib/format_token_definition.rb
|
38
38
|
- lib/log_parser.rb
|
39
39
|
- lib/procedure_dsl.rb
|
40
40
|
- lib/progress.rb
|
41
41
|
- bin/apachecrunch
|
42
42
|
- LICENSE
|
43
|
+
- test/mock.rb
|
43
44
|
- test/runner.rb
|
44
45
|
- test/stub.rb
|
45
|
-
- test/
|
46
|
+
- test/test_derived_value_fetcher.rb
|
47
|
+
- test/test_element.rb
|
48
|
+
- test/test_element_value_fetcher.rb
|
49
|
+
- test/test_entry_parser.rb
|
46
50
|
- test/test_format.rb
|
47
51
|
- test/test_format_parser.rb
|
52
|
+
- test/test_log_parser.rb
|
53
|
+
- test/test_raw_value_fetcher.rb
|
54
|
+
- test/test_regex_token.rb
|
55
|
+
- test/test_req_firstline_derivation_rule.rb
|
56
|
+
- test/test_reqheader_token.rb
|
57
|
+
- test/test_string_token.rb
|
58
|
+
- test/test_time_derivation_rule.rb
|
48
59
|
has_rdoc: true
|
49
60
|
homepage: https://github.com/danslimmon/apachecrunch/
|
50
61
|
licenses:
|
@@ -55,27 +66,21 @@ rdoc_options: []
|
|
55
66
|
require_paths:
|
56
67
|
- lib
|
57
68
|
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
69
|
requirements:
|
60
70
|
- - ">="
|
61
71
|
- !ruby/object:Gem::Version
|
62
|
-
hash: 3
|
63
|
-
segments:
|
64
|
-
- 0
|
65
72
|
version: "0"
|
73
|
+
version:
|
66
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
-
none: false
|
68
75
|
requirements:
|
69
76
|
- - ">="
|
70
77
|
- !ruby/object:Gem::Version
|
71
|
-
hash: 3
|
72
|
-
segments:
|
73
|
-
- 0
|
74
78
|
version: "0"
|
79
|
+
version:
|
75
80
|
requirements: []
|
76
81
|
|
77
82
|
rubyforge_project:
|
78
|
-
rubygems_version: 1.
|
83
|
+
rubygems_version: 1.3.5
|
79
84
|
signing_key:
|
80
85
|
specification_version: 3
|
81
86
|
summary: Apache log analysis tool designed for ease of use
|
data/lib/log_element.rb
DELETED
@@ -1,351 +0,0 @@
|
|
1
|
-
# Converts a string to an integer
|
2
|
-
class IntegerCast
|
3
|
-
def self.cast(string_value)
|
4
|
-
string_value.to_i
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
|
9
|
-
# Converts a CLF-formatted string to an integer
|
10
|
-
#
|
11
|
-
# "CLF-formatted" means that if the value is 0, the string will be a single hyphen instead of
|
12
|
-
# a number. Like %b, for instance.
|
13
|
-
class CLFIntegerCast
|
14
|
-
def self.cast(string_value)
|
15
|
-
if string_value == "-"
|
16
|
-
return 0
|
17
|
-
end
|
18
|
-
string_value.to_i
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
# An element in a log format. Abstract from which all elements inherit.
|
24
|
-
#
|
25
|
-
# Exposes:
|
26
|
-
# abbrev: The Apache abbreviation for the element (such as "%h" or "%u" or "%{Referer}i")
|
27
|
-
# name: A short name for the element (such as "remote_host", "remote_user", or "reqhead_referer")
|
28
|
-
# regex: A regex that should match such an element ("[A-Za-z0-9.-]+", "[^:]+", ".+")
|
29
|
-
#
|
30
|
-
# If '_caster' is not nil, it should be a class with a method called "cast" that
|
31
|
-
# transforms a string to the appropriate data type or format for consumption.
|
32
|
-
# For example, the IntegerCast class transforms "562" to 562. The correct cast
|
33
|
-
# of a string can then be performed by passing that string to this LogFormaElement
|
34
|
-
# instance's "cast" method.
|
35
|
-
#
|
36
|
-
# 'derive_elements' manages elements that can be derived from the instance's value. See
|
37
|
-
# ReqFirstlineElement for an example.
|
38
|
-
class LogFormatElement
|
39
|
-
@_caster = nil
|
40
|
-
|
41
|
-
attr_accessor :abbrev, :name, :regex
|
42
|
-
# Class variables that determine the _default_ for abbrev, name, and regex in an instance.
|
43
|
-
# That is, an instance will initialize with these values for the instance variables @abbrev,
|
44
|
-
# @name, and @regex.
|
45
|
-
class << self; attr_accessor :abbrev, :name, :regex end
|
46
|
-
# Additionally we need to access this from within the instance:
|
47
|
-
class << self; attr_accessor :_caster end
|
48
|
-
|
49
|
-
def initialize
|
50
|
-
@abbrev = self.class.abbrev
|
51
|
-
@name = self.class.name
|
52
|
-
@regex = self.class.regex
|
53
|
-
end
|
54
|
-
|
55
|
-
# Casts a string found in the log to the correct type, using the class's @_caster attribute.
|
56
|
-
def cast(string_value)
|
57
|
-
if _caster.nil?
|
58
|
-
return string_value
|
59
|
-
else
|
60
|
-
return _caster.cast(string_value)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Derives the named element (e.g. "url_path") from a given value for this one.
|
65
|
-
#
|
66
|
-
# See ReqFirstlineElement for an example.
|
67
|
-
def self.derive(name, our_own_value)
|
68
|
-
raise NotImplementedError
|
69
|
-
end
|
70
|
-
|
71
|
-
# Returns a list of the element classes that can be derived from this one.
|
72
|
-
#
|
73
|
-
# See ReqFirstlineElement for an example.
|
74
|
-
def derived_elements
|
75
|
-
[]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
|
80
|
-
class RemoteHostElement < LogFormatElement
|
81
|
-
@abbrev = "%h"
|
82
|
-
@name = :remote_host
|
83
|
-
@regex = %q![A-Za-z0-9.-]+!
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
class LogNameElement < LogFormatElement
|
88
|
-
@abbrev = "%l"
|
89
|
-
@name = :log_name
|
90
|
-
@regex = %q!\S+!
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
class RemoteUserElement < LogFormatElement
|
95
|
-
@abbrev = "%u"
|
96
|
-
@name = :remote_user
|
97
|
-
@regex = %q![^:]+!
|
98
|
-
end
|
99
|
-
|
100
|
-
|
101
|
-
class TimeElement < LogFormatElement
|
102
|
-
@abbrev = "%t"
|
103
|
-
@name = :time
|
104
|
-
@regex = %q!\[\d\d/[A-Za-z]{3}/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\]!
|
105
|
-
|
106
|
-
@_derivation_regex = nil
|
107
|
-
@_month_map = {"Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
|
108
|
-
"Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12}
|
109
|
-
|
110
|
-
def self.derive(name, our_own_value)
|
111
|
-
if @_derivation_regex.nil?
|
112
|
-
@_derivation_regex = Regexp.compile(%q!^\[(\d\d)/([A-Za-z]{3})/(\d\d\d\d):(\d\d):(\d\d):(\d\d)!)
|
113
|
-
end
|
114
|
-
|
115
|
-
hsh = {}
|
116
|
-
if our_own_value =~ @_derivation_regex
|
117
|
-
hsh[:year] = $3.to_i
|
118
|
-
hsh[:month] = @_month_map[$2]
|
119
|
-
hsh[:day] = $1.to_i
|
120
|
-
|
121
|
-
hsh[:hour] = $4.to_i
|
122
|
-
hsh[:minute] = $5.to_i
|
123
|
-
hsh[:second] = $6.to_i
|
124
|
-
end
|
125
|
-
|
126
|
-
hsh[name]
|
127
|
-
end
|
128
|
-
|
129
|
-
def derived_elements
|
130
|
-
[YearElement, MonthElement, DayElement, HourElement, MinuteElement, SecondElement]
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
|
135
|
-
# Elements derived from TimeElement
|
136
|
-
class YearElement < LogFormatElement
|
137
|
-
@name = :year
|
138
|
-
@regex = %q!\d{4}!
|
139
|
-
end
|
140
|
-
class MonthElement < LogFormatElement
|
141
|
-
@name = :month
|
142
|
-
@regex = %q![A-Za-z]{3}!
|
143
|
-
end
|
144
|
-
class DayElement < LogFormatElement
|
145
|
-
@name = :day
|
146
|
-
@regex = %q!\d{2}!
|
147
|
-
end
|
148
|
-
class HourElement < LogFormatElement
|
149
|
-
@name = :hour
|
150
|
-
@regex = %q!\d{2}!
|
151
|
-
end
|
152
|
-
class MinuteElement < LogFormatElement
|
153
|
-
@name = :minute
|
154
|
-
@regex = %q!\d{2}!
|
155
|
-
end
|
156
|
-
class SecondElement < LogFormatElement
|
157
|
-
@name = :second
|
158
|
-
@regex = %q!\d{2}!
|
159
|
-
end
|
160
|
-
|
161
|
-
|
162
|
-
class ReqFirstlineElement < LogFormatElement
|
163
|
-
@abbrev = "%r"
|
164
|
-
@name = :req_firstline
|
165
|
-
@regex = %q![^"]+!
|
166
|
-
|
167
|
-
@_derivation_regex = nil
|
168
|
-
|
169
|
-
def self.derive(name, our_own_value)
|
170
|
-
if @_derivation_regex.nil?
|
171
|
-
@_derivation_regex = Regexp.compile("^(#{ReqMethodElement.regex})\s+(#{UrlPathElement.regex})(#{QueryStringElement.regex})\s+(#{ProtocolElement.regex})$")
|
172
|
-
end
|
173
|
-
|
174
|
-
hsh = {}
|
175
|
-
if our_own_value =~ @_derivation_regex
|
176
|
-
hsh[ReqMethodElement.name] = $1
|
177
|
-
hsh[UrlPathElement.name] = $2
|
178
|
-
hsh[QueryStringElement.name] = $3
|
179
|
-
hsh[ProtocolElement.name] = $4
|
180
|
-
end
|
181
|
-
|
182
|
-
hsh[name]
|
183
|
-
end
|
184
|
-
|
185
|
-
def derived_elements
|
186
|
-
return [ReqMethodElement, UrlPathElement, QueryStringElement, ProtocolElement]
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
|
191
|
-
class StatusElement < LogFormatElement
|
192
|
-
@abbrev = "%s"
|
193
|
-
@name = :status
|
194
|
-
@regex = %q!\d+|-!
|
195
|
-
end
|
196
|
-
|
197
|
-
|
198
|
-
class BytesSentElement < LogFormatElement
|
199
|
-
@abbrev = "%b"
|
200
|
-
@name = :bytes_sent
|
201
|
-
@regex = %q!\d+!
|
202
|
-
|
203
|
-
@@_caster = IntegerCast
|
204
|
-
end
|
205
|
-
|
206
|
-
|
207
|
-
class BytesSentElement < LogFormatElement
|
208
|
-
@abbrev = "%b"
|
209
|
-
@name = :bytes_sent
|
210
|
-
@regex = %q![\d-]+!
|
211
|
-
|
212
|
-
@_caster = CLFIntegerCast
|
213
|
-
end
|
214
|
-
|
215
|
-
|
216
|
-
class BytesSentWithHeadersElement < LogFormatElement
|
217
|
-
@abbrev = "%O"
|
218
|
-
@name = :bytes_sent_with_headers
|
219
|
-
@regex = %q!\d+!
|
220
|
-
|
221
|
-
@_caster = IntegerCast
|
222
|
-
end
|
223
|
-
|
224
|
-
|
225
|
-
class ServeTimeMicroElement < LogFormatElement
|
226
|
-
@abbrev = "%D"
|
227
|
-
@name = :serve_time_micro
|
228
|
-
@regex = %q!\d+!
|
229
|
-
|
230
|
-
@_caster = IntegerCast
|
231
|
-
end
|
232
|
-
|
233
|
-
|
234
|
-
class UrlPathElement < LogFormatElement
|
235
|
-
@abbrev = "%U"
|
236
|
-
@name = :url_path
|
237
|
-
@regex = %q!/[^?]*!
|
238
|
-
end
|
239
|
-
|
240
|
-
|
241
|
-
class QueryStringElement < LogFormatElement
|
242
|
-
@abbrev = "%q"
|
243
|
-
@name = :query_string
|
244
|
-
@regex = %q!\??\S*!
|
245
|
-
end
|
246
|
-
|
247
|
-
|
248
|
-
class ReqMethodElement < LogFormatElement
|
249
|
-
@abbrev = "%m"
|
250
|
-
@name = :req_method
|
251
|
-
@regex = %q![A-Z]+!
|
252
|
-
end
|
253
|
-
|
254
|
-
|
255
|
-
class ProtocolElement < LogFormatElement
|
256
|
-
@abbrev = "%H"
|
257
|
-
@name = :protocol
|
258
|
-
@regex = %q!\S+!
|
259
|
-
end
|
260
|
-
|
261
|
-
|
262
|
-
class ReqheaderElement < LogFormatElement
|
263
|
-
end
|
264
|
-
|
265
|
-
|
266
|
-
class RegexElement < LogFormatElement
|
267
|
-
end
|
268
|
-
|
269
|
-
|
270
|
-
# Finds log format elements given information about them.
|
271
|
-
class ElementDictionary
|
272
|
-
@@_ELEMENTS = [
|
273
|
-
RemoteHostElement,
|
274
|
-
LogNameElement,
|
275
|
-
RemoteUserElement,
|
276
|
-
TimeElement,
|
277
|
-
ReqFirstlineElement,
|
278
|
-
StatusElement,
|
279
|
-
BytesSentElement,
|
280
|
-
BytesSentElement,
|
281
|
-
BytesSentWithHeadersElement,
|
282
|
-
ServeTimeMicroElement,
|
283
|
-
UrlPathElement,
|
284
|
-
QueryStringElement,
|
285
|
-
ReqMethodElement,
|
286
|
-
ProtocolElement
|
287
|
-
]
|
288
|
-
|
289
|
-
# Returns the LogFormatElement subclass with the given format-string abbreviation.
|
290
|
-
#
|
291
|
-
# If none exists, returns nil.
|
292
|
-
def self.find_by_abbrev(abbrev)
|
293
|
-
@@_ELEMENTS.each do |element|
|
294
|
-
if element.abbrev == abbrev
|
295
|
-
return element
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
nil
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
|
304
|
-
# Generates LogFormatElement instances.
|
305
|
-
#
|
306
|
-
# This class does the work of figuring out which LogFormatElement subclass to make and makes it.
|
307
|
-
class LogFormatElementFactory
|
308
|
-
# Takes an Apache log format abbreviation and returns a corresponding LogFormatElement
|
309
|
-
def from_abbrev(abbrev)
|
310
|
-
element_cls = ElementDictionary.find_by_abbrev(abbrev)
|
311
|
-
if element_cls
|
312
|
-
# We found it in the dictionary, so just return an instance
|
313
|
-
return element_cls.new
|
314
|
-
elsif abbrev =~ /^%\{([A-Za-z0-9-]+)\}i/
|
315
|
-
# HTTP request header
|
316
|
-
return _reqheader_element(abbrev, $1)
|
317
|
-
elsif abbrev =~ /^%\{(.*?):([^}]+)\}r/
|
318
|
-
# Arbitrary regex
|
319
|
-
return _regex_element(abbrev, $1, $2)
|
320
|
-
end
|
321
|
-
|
322
|
-
raise "Unknown element format '#{abbrev}'"
|
323
|
-
end
|
324
|
-
|
325
|
-
# Returns a format element based on an HTTP header
|
326
|
-
def _reqheader_element(abbrev, header_name)
|
327
|
-
element = ReqheaderElement.new
|
328
|
-
|
329
|
-
element.abbrev = abbrev
|
330
|
-
element.regex = %q![^"]*!
|
331
|
-
element.name = _header_name_to_element_name(header_name)
|
332
|
-
|
333
|
-
element
|
334
|
-
end
|
335
|
-
|
336
|
-
# Returns a format element based on an arbitrary regex
|
337
|
-
def _regex_element(abbrev, regex_name, regex)
|
338
|
-
element = RegexElement.new
|
339
|
-
|
340
|
-
element.abbrev = abbrev
|
341
|
-
element.regex = regex
|
342
|
-
element.name = "regex_#{regex_name}".to_sym
|
343
|
-
|
344
|
-
element
|
345
|
-
end
|
346
|
-
|
347
|
-
# Lowercases header name and turns hyphens into underscores
|
348
|
-
def _header_name_to_element_name(header_name)
|
349
|
-
("reqheader_" + header_name.downcase().gsub("-", "_")).to_sym
|
350
|
-
end
|
351
|
-
end
|