apachecrunch 0.4 → 0.5
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/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
|