danielsdeleo-teeth 0.0.2 → 0.1.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/LICENSE +11 -0
- data/README.rdoc +107 -10
- data/Rakefile +47 -31
- data/VERSION.yml +4 -0
- data/doc/classes/String.html +182 -0
- data/doc/classes/Teeth/DuplicateDefinitionError.html +113 -0
- data/doc/classes/Teeth/DuplicateRuleError.html +113 -0
- data/doc/classes/Teeth/InvalidDefaultDefinitionName.html +113 -0
- data/doc/classes/Teeth/InvalidExtensionDirectory.html +113 -0
- data/doc/classes/Teeth/RuleStatement.html +291 -0
- data/doc/classes/Teeth/RuleStatementGroup.html +195 -0
- data/doc/classes/Teeth/Scanner.html +535 -0
- data/doc/classes/Teeth/ScannerDefinition.html +253 -0
- data/doc/classes/Teeth/ScannerDefinitionArgumentError.html +113 -0
- data/doc/classes/Teeth/ScannerDefinitionGroup.html +269 -0
- data/doc/classes/Teeth/ScannerError.html +111 -0
- data/doc/classes/Teeth.html +129 -0
- data/doc/created.rid +1 -0
- data/doc/files/README_rdoc.html +314 -0
- data/doc/files/ext/scan_apache_logs/scan_apache_logs_yy_c.html +101 -0
- data/doc/files/ext/scan_rails_logs/scan_rails_logs_yy_c.html +101 -0
- data/doc/files/lib/rule_statement_rb.html +101 -0
- data/doc/files/lib/scanner_definition_rb.html +101 -0
- data/doc/files/lib/scanner_rb.html +108 -0
- data/doc/files/lib/teeth_rb.html +111 -0
- data/doc/fr_class_index.html +39 -0
- data/doc/fr_file_index.html +33 -0
- data/doc/fr_method_index.html +60 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/ext/scan_apache_logs/Makefile +158 -0
- data/ext/scan_apache_logs/extconf.rb +3 -0
- data/ext/scan_apache_logs/scan_apache_logs.yy +267 -0
- data/ext/scan_apache_logs/scan_apache_logs.yy.c +8355 -0
- data/ext/scan_rails_logs/Makefile +158 -0
- data/ext/scan_rails_logs/extconf.rb +3 -0
- data/ext/scan_rails_logs/scan_rails_logs.yy +376 -0
- data/ext/scan_rails_logs/scan_rails_logs.yy.c +11127 -0
- data/lib/rule_statement.rb +61 -0
- data/lib/scanner.rb +98 -0
- data/lib/scanner_definition.rb +116 -0
- data/lib/teeth.rb +5 -1
- data/scanners/scan_apache_logs.rb +27 -0
- data/scanners/scan_rails_logs.rb +70 -0
- data/spec/fixtures/rails_1x.log +59 -0
- data/spec/fixtures/rails_22.log +12 -0
- data/spec/fixtures/rails_22_cached.log +10 -0
- data/spec/fixtures/rails_unordered.log +24 -0
- data/spec/playground/show_apache_processing.rb +13 -0
- data/spec/spec_helper.rb +6 -1
- data/spec/unit/rule_statement_spec.rb +60 -0
- data/spec/unit/{tokenize_apache_spec.rb → scan_apache_spec.rb} +16 -11
- data/spec/unit/scan_rails_logs_spec.rb +90 -0
- data/spec/unit/scaner_definition_spec.rb +65 -0
- data/spec/unit/scanner_spec.rb +109 -0
- data/teeth.gemspec +31 -0
- data/templates/tokenizer.yy.erb +168 -0
- metadata +60 -15
- data/ext/extconf.rb +0 -4
- data/ext/tokenize_apache_logs.yy +0 -215
- data/ext/tokenize_apache_logs.yy.c +0 -12067
@@ -0,0 +1,61 @@
|
|
1
|
+
module Teeth
|
2
|
+
|
3
|
+
class DuplicateRuleError < ScannerError
|
4
|
+
end
|
5
|
+
|
6
|
+
class RuleStatement
|
7
|
+
attr_reader :name, :regex, :strip_ends, :skip_line, :begin
|
8
|
+
|
9
|
+
def initialize(name, regex, options={})
|
10
|
+
@name, @regex = name, regex
|
11
|
+
@strip_ends, @skip_line, @begin = options[:strip_ends], options[:skip_line], options[:begin]
|
12
|
+
@ignore = options[:ignore]
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
other.kind_of?(RuleStatement) && other.name == name && other.regex == regex
|
17
|
+
end
|
18
|
+
|
19
|
+
def scanner_code
|
20
|
+
if @ignore
|
21
|
+
regex
|
22
|
+
else
|
23
|
+
"#{regex} {\n" + function_body + "}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def function_body
|
28
|
+
code = ""
|
29
|
+
code += " BEGIN(#{@begin});\n" if @begin
|
30
|
+
if skip_line
|
31
|
+
code += " return EOF_KVPAIR;\n"
|
32
|
+
else
|
33
|
+
code += " KVPAIR #{name.to_s} = {\"#{name.to_s}\", #{yytext_statement}};\n" +
|
34
|
+
" return #{name.to_s};\n"
|
35
|
+
end
|
36
|
+
code
|
37
|
+
end
|
38
|
+
|
39
|
+
def yytext_statement
|
40
|
+
strip_ends ? "strip_ends(yytext)" : "yytext"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class RuleStatementGroup < Array
|
46
|
+
|
47
|
+
def add(name, regex, options={})
|
48
|
+
push RuleStatement.new(name, regex, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def rule_names
|
52
|
+
map { |rule_statement| rule_statement.name.to_s }
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_missing(called_method_name, *args, &block)
|
56
|
+
args[1] ||={}
|
57
|
+
add(called_method_name, args[0], args[1])
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/scanner.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require "erb"
|
2
|
+
module Teeth
|
3
|
+
class ScannerError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidExtensionDirectory < ScannerError
|
7
|
+
end
|
8
|
+
|
9
|
+
class Scanner
|
10
|
+
TEMPLATE = File.dirname(__FILE__) + "/../templates/tokenizer.yy.erb"
|
11
|
+
attr_reader :scanner_defns, :scanner_rules, :rdoc
|
12
|
+
|
13
|
+
def initialize(name, ext_dir=nil)
|
14
|
+
@scanner_base_name, @ext_dir = name, ext_dir
|
15
|
+
@scanner_defns, @scanner_rules = ScannerDefinitionGroup.new, RuleStatementGroup.new
|
16
|
+
ensure_ext_dir_exists if ext_dir
|
17
|
+
end
|
18
|
+
|
19
|
+
def scanner_name
|
20
|
+
"scan_" + @scanner_base_name.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def main_function_name
|
24
|
+
"t_" + scanner_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def init_function_name
|
28
|
+
"Init_" + scanner_name
|
29
|
+
end
|
30
|
+
|
31
|
+
def function_prefix
|
32
|
+
@scanner_base_name.to_s + "_yy"
|
33
|
+
end
|
34
|
+
|
35
|
+
def entry_point
|
36
|
+
"scan_" + @scanner_base_name.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def extconf
|
40
|
+
'require "mkmf"' + "\n" + '$CFLAGS += " -Wall"' + "\n" + "create_makefile " +
|
41
|
+
%Q|"teeth/#{scanner_name}", "./"\n|
|
42
|
+
end
|
43
|
+
|
44
|
+
def rdoc=(rdoc_text)
|
45
|
+
lines_of_rdoc_text = rdoc_text.split("\n").map { |line| " * " + line.strip}
|
46
|
+
lines_of_rdoc_text.first[0] = "/"
|
47
|
+
lines_of_rdoc_text[-1] = lines_of_rdoc_text.last + " */"
|
48
|
+
@rdoc = lines_of_rdoc_text.join("\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
def define(*args)
|
52
|
+
@scanner_defns.add(*args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def definitions
|
56
|
+
yield @scanner_defns
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_default_definitions_for(*defn_types)
|
60
|
+
@scanner_defns.defaults_for(*defn_types)
|
61
|
+
end
|
62
|
+
|
63
|
+
def rule(*args)
|
64
|
+
scanner_rules.add(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def rules
|
68
|
+
yield scanner_rules
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate
|
72
|
+
template = ERB.new(IO.read(TEMPLATE))
|
73
|
+
scanner = self
|
74
|
+
b = binding
|
75
|
+
template.result(b)
|
76
|
+
end
|
77
|
+
|
78
|
+
def write!
|
79
|
+
raise InvalidExtensionDirectory, "no extension directory specified" unless @ext_dir
|
80
|
+
File.open(@ext_dir + "/extconf.rb", "w") do |extconf_rb|
|
81
|
+
extconf_rb.write extconf
|
82
|
+
end
|
83
|
+
File.open(@ext_dir + "/" + scanner_name + ".yy", "w") do |scanner|
|
84
|
+
scanner.write generate
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def ensure_ext_dir_exists
|
91
|
+
unless File.exist?(@ext_dir)
|
92
|
+
Dir.mkdir @ext_dir
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Teeth
|
2
|
+
class DuplicateDefinitionError < ScannerError
|
3
|
+
end
|
4
|
+
|
5
|
+
class InvalidDefaultDefinitionName < ScannerError
|
6
|
+
end
|
7
|
+
|
8
|
+
class ScannerDefinitionArgumentError < ScannerError
|
9
|
+
end
|
10
|
+
|
11
|
+
class ScannerDefinition
|
12
|
+
attr_reader :name, :regex
|
13
|
+
|
14
|
+
def initialize(name, regex, opts={})
|
15
|
+
if regex.kind_of?(Hash)
|
16
|
+
regex, opts = nil, regex
|
17
|
+
end
|
18
|
+
@name, @regex, @start_condition = name, regex, opts[:start_condition]
|
19
|
+
assert_valid_argument_combination
|
20
|
+
end
|
21
|
+
|
22
|
+
def scanner_code
|
23
|
+
start_condition_string + @name.to_s + regex_to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def regex_to_s
|
27
|
+
unless @regex.to_s == ""
|
28
|
+
" " + @regex.to_s
|
29
|
+
else
|
30
|
+
""
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def start_condition_string
|
35
|
+
case @start_condition.to_s
|
36
|
+
when /^inc/
|
37
|
+
"%s "
|
38
|
+
when /^exc/
|
39
|
+
"%x "
|
40
|
+
else
|
41
|
+
""
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def assert_valid_argument_combination
|
48
|
+
if @start_condition
|
49
|
+
if @regex.to_s != "" # (nil or "").to_s == ""
|
50
|
+
raise ScannerDefinitionArgumentError, "a scanner definition cannot define both a regex and start condition"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
class ScannerDefinitionGroup < Array
|
58
|
+
|
59
|
+
DEFAULT_DEFINITIONS = {}
|
60
|
+
DEFAULT_DEFINITIONS[:whitespace] = [["WS", '[[:space:]]'],
|
61
|
+
["NON_WS", "([a-z]|[0-9]|[:punct:])"]]
|
62
|
+
DEFAULT_DEFINITIONS[:ip] = [ ["IP4_OCT", "[0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]"],
|
63
|
+
["HOST", '[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*.[a-z0-9][a-z0-9\-\.]*[a-z]+(\:[0-9]+)?']]
|
64
|
+
DEFAULT_DEFINITIONS[:time] = [ ["WDAY", "mon|tue|wed|thu|fri|sat|sun"],
|
65
|
+
["MON", "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec"],
|
66
|
+
["MONTH_NUM", "0[1-9]|1[0-2]"],
|
67
|
+
["MDAY", "3[0-1]|[1-2][0-9]|0[1-9]"],
|
68
|
+
["HOUR", "2[0-3]|[0-1][0-9]"],
|
69
|
+
["MINSEC", "[0-5][0-9]|60"],
|
70
|
+
["YEAR", "[0-9][0-9][0-9][0-9]"],
|
71
|
+
["PLUSMINUS", '(\+|\-)']]
|
72
|
+
DEFAULT_DEFINITIONS[:web] = [ ["REL_URL", %q{(\/|\\\\|\.)[a-z0-9\._\~\-\/\?&;#=\%\:\+\[\]\\\\]*}],
|
73
|
+
["PROTO", "(http:|https:)"],
|
74
|
+
["ERR_LVL", "(emerg|alert|crit|err|error|warn|warning|notice|info|debug)"],
|
75
|
+
["HTTP_VERS", 'HTTP\/(1.0|1.1)'],
|
76
|
+
["HTTP_VERB", "(get|head|put|post|delete|trace|connect)"],
|
77
|
+
["HTTPCODE", "(100|101|20[0-6]|30[0-5]|307|40[0-9]|41[0-7]|50[0-5])"],
|
78
|
+
["BROWSER_STR", '\"(moz|msie|lynx).+\"']]
|
79
|
+
|
80
|
+
def add(name, regex, options={})
|
81
|
+
assert_defn_has_unique_name(name)
|
82
|
+
push ScannerDefinition.new(name, regex, options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def assert_defn_has_unique_name(name)
|
86
|
+
if defn_names.include?(name.to_s)
|
87
|
+
raise DuplicateDefinitionError, "a definition for #{name.to_s} has already been defined"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def defn_names
|
92
|
+
map { |defn_statement| defn_statement.name.to_s }
|
93
|
+
end
|
94
|
+
|
95
|
+
def method_missing(called_method_name, *args, &block)
|
96
|
+
args[1] ||={}
|
97
|
+
add(called_method_name, args[0], args[1])
|
98
|
+
end
|
99
|
+
|
100
|
+
def defaults_for(*default_types)
|
101
|
+
default_types.each do |default_type|
|
102
|
+
unless default_definitions = DEFAULT_DEFINITIONS[default_type]
|
103
|
+
raise InvalidDefaultDefinitionName, "no default definitions found for #{default_type.to_s}"
|
104
|
+
end
|
105
|
+
default_definitions.each do |defn|
|
106
|
+
begin
|
107
|
+
add(defn.first, defn.last)
|
108
|
+
rescue DuplicateDefinitionError
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
data/lib/teeth.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../lib/teeth"
|
2
|
+
scanner = Teeth::Scanner.new(:apache_logs, File.dirname(__FILE__) + '/../ext/scan_apache_logs/')
|
3
|
+
scanner.load_default_definitions_for(:whitespace, :ip, :time, :web)
|
4
|
+
scanner.rdoc = <<-RDOC
|
5
|
+
Scans self, which is expected to be a single line from an Apache error or
|
6
|
+
access log, and returns a Hash of the components of the log message. The
|
7
|
+
following parts of the log message are returned if they are present:
|
8
|
+
IPv4 address, datetime, HTTP Version used, the browser string given by the
|
9
|
+
client, any absolute or relative URLs, the error level, HTTP response code,
|
10
|
+
HTTP Method (verb), and any other uncategorized strings present.
|
11
|
+
RDOC
|
12
|
+
scanner.rules do |r|
|
13
|
+
r.ipv4_addr '{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT}'
|
14
|
+
r.apache_err_datetime '{WDAY}{WS}{MON}{WS}{MDAY}{WS}{HOUR}":"{MINSEC}":"{MINSEC}{WS}{YEAR}'
|
15
|
+
r.apache_access_datetime '{MDAY}\/{MON}\/{YEAR}":"{HOUR}":"{MINSEC}":"{MINSEC}{WS}{PLUSMINUS}{YEAR}'
|
16
|
+
r.http_version '{HTTP_VERS}'
|
17
|
+
r.browser_string '{BROWSER_STR}', :strip_ends => true
|
18
|
+
r.absolute_url '{PROTO}"\/\/"({HOST}|{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT})({REL_URL}|"\/")?'
|
19
|
+
r.host '{HOST}'
|
20
|
+
r.relative_url '{REL_URL}'
|
21
|
+
r.error_level '{ERR_LVL}'
|
22
|
+
r.http_response '{HTTPCODE}'
|
23
|
+
r.http_method '{HTTP_VERB}'
|
24
|
+
r.strings '{NON_WS}{NON_WS}*'
|
25
|
+
end
|
26
|
+
|
27
|
+
scanner.write!
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../lib/teeth"
|
2
|
+
scanner = Teeth::Scanner.new(:rails_logs, File.dirname(__FILE__) + '/../ext/scan_rails_logs/')
|
3
|
+
scanner.load_default_definitions_for(:whitespace, :ip, :time, :web)
|
4
|
+
scanner.rdoc = <<-RDOC
|
5
|
+
Scans self, which is expected to be a line from a Rails production or dev log,
|
6
|
+
and returns a Hash of the significant features in the log message, including
|
7
|
+
the IP address of the client, the Controller and Action, any partials rendered,
|
8
|
+
and the time spent rendering them, the duration of the DB request(s), the HTTP
|
9
|
+
verb, etc.
|
10
|
+
RDOC
|
11
|
+
scanner.definitions do |define|
|
12
|
+
define.RAILS_TEASER '(processing|filter\ chain\ halted|rendered)'
|
13
|
+
define.CONTROLLER_ACTION '[a-z0-9]+#[a-z0-9]+'
|
14
|
+
define.RAILS_SKIP_LINES '(session\ id)'
|
15
|
+
define.CACHE_HIT 'actioncontroller"::"caching"::"actions"::"actioncachefilter":"0x[0-9a-f]+'
|
16
|
+
define.PARTIAL_SESSION_ID '^([a-z0-9]+"="*"-"+[a-z0-9]+)'
|
17
|
+
define.RAILS_ERROR_CLASS '([a-z]+\:\:)*[a-z]+error'
|
18
|
+
define.REQUEST_COMPLETED :start_condition => :exclusive
|
19
|
+
define.COMPLETED_REQ_VIEW_STATS :start_condition => :exclusive
|
20
|
+
define.COMPLETED_REQ_DB_STATS :start_condition => :exclusive
|
21
|
+
end
|
22
|
+
scanner.rules do |r|
|
23
|
+
# Processing DashboardController#index (for 1.1.1.1 at 2008-08-14 21:16:25) [GET]
|
24
|
+
r.teaser '{RAILS_TEASER}'
|
25
|
+
r.controller_action '{CONTROLLER_ACTION}'
|
26
|
+
r.ipv4_addr '{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT}'
|
27
|
+
r.datetime '{YEAR}"-"{MONTH_NUM}"-"{MDAY}{WS}{HOUR}":"{MINSEC}":"{MINSEC}'
|
28
|
+
r.http_method '{HTTP_VERB}'
|
29
|
+
# Session ID: BAh7CToMcmVmZXJlciIbL3ByaXNjaWxsYS9wZW9wbGUvMjM1MCIKZmxhc2hJ
|
30
|
+
# QzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVz ...
|
31
|
+
r.skip_lines '{RAILS_SKIP_LINES}', :skip_line => true
|
32
|
+
r.end_session_id '{PARTIAL_SESSION_ID}'
|
33
|
+
# RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
|
34
|
+
# ActionController::RoutingError (no route found to match "/favicon.ico" with {:method=>:get}):
|
35
|
+
# ActionView::TemplateError (No rhtml, rxml, rjs or delegate template found for /shared/_ids_modal_selection_panel in script/../config/../app/views) on line #2 of app/views/events/index.rhtml:
|
36
|
+
# ActionView::TemplateError (You have a nil object when you didn't expect it!
|
37
|
+
# NoMethodError (undefined method `find' for ActionController::Filters::Filter:Class):
|
38
|
+
r.error '{RAILS_ERROR_CLASS}'
|
39
|
+
r.error_message '\(({WS}|{NON_WS})+\)', :strip_ends => true
|
40
|
+
r.line_number '"#"[0-9]+{WS}', :strip_ends => true
|
41
|
+
r.file_and_line '{WS}{REL_URL}":"', :strip_ends => true
|
42
|
+
# Filter chain halted as [#<ActionController::Caching::Actions::ActionCacheFilter:0x2a999ad620 @check=nil, @options={:store_options=>{}, :layout=>nil, :cache_path=>#<Proc:0x0000002a999b8890@/app/controllers/cached_controller.rb:8>}>] rendered_or_redirected.
|
43
|
+
r.cache_hit '{CACHE_HIT}'
|
44
|
+
# Rendered shared/_analytics (0.2ms)
|
45
|
+
# Rendered layouts/_doc_type (0.00001)
|
46
|
+
r.partial '[a-z0-9]+{REL_URL}/\ \('
|
47
|
+
r.render_duration_ms '[0-9\.]+/ms\)'
|
48
|
+
r.render_duration_s '\([0-9\.]+\)', :strip_ends => true
|
49
|
+
# Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
|
50
|
+
# Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
|
51
|
+
r.teaser 'completed\ in', :begin => "REQUEST_COMPLETED"
|
52
|
+
r.duration_s '<REQUEST_COMPLETED>[0-9]+\.[0-9]+'
|
53
|
+
r.duration_ms '<REQUEST_COMPLETED>[0-9]+/ms'
|
54
|
+
r.start_view_stats '<REQUEST_COMPLETED>(View":"|Rendering":")', :begin => "COMPLETED_REQ_VIEW_STATS"
|
55
|
+
r.view_s '<COMPLETED_REQ_VIEW_STATS>([0-9]+\.[0-9]+)', :begin => "REQUEST_COMPLETED"
|
56
|
+
r.view_ms '<COMPLETED_REQ_VIEW_STATS>[0-9]+', :begin => "REQUEST_COMPLETED"
|
57
|
+
r.view_throwaway_tokens '<COMPLETED_REQ_VIEW_STATS>{CATCHALL}', :ignore => true
|
58
|
+
r.start_db_stats '<REQUEST_COMPLETED>DB":"', :begin => "COMPLETED_REQ_DB_STATS"
|
59
|
+
r.db_s '<COMPLETED_REQ_DB_STATS>[0-9]+\.[0-9]+', :begin => "REQUEST_COMPLETED"
|
60
|
+
r.db_ms '<COMPLETED_REQ_DB_STATS>[0-9]+', :begin => "REQUEST_COMPLETED"
|
61
|
+
r.db_throwaway_tokens '<COMPLETED_REQ_DB_STATS>{CATCHALL}', :ignore => true
|
62
|
+
r.url '<REQUEST_COMPLETED>\[{PROTO}"\/\/"({HOST}|{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT}"."{IP4_OCT})({REL_URL}|"\/")?\]', :strip_ends => true
|
63
|
+
r.http_response '<REQUEST_COMPLETED>{HTTPCODE}'
|
64
|
+
r.strings '<REQUEST_COMPLETED>{NON_WS}{NON_WS}*'
|
65
|
+
r.ignore_others '<REQUEST_COMPLETED>{CATCHALL}', :ignore => true
|
66
|
+
# fallback to collecting strings
|
67
|
+
r.strings '{NON_WS}{NON_WS}*'
|
68
|
+
end
|
69
|
+
|
70
|
+
scanner.write!
|
@@ -0,0 +1,59 @@
|
|
1
|
+
Processing DashboardController#index (for 1.1.1.1 at 2008-08-14 21:16:25) [GET]
|
2
|
+
Session ID: BAh7CToMcmVmZXJlciIbL3ByaXNjaWxsYS9wZW9wbGUvMjM1MCIKZmxhc2hJ
|
3
|
+
QzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVz
|
4
|
+
ZWR7ADoNbGFuZ3VhZ2VvOhNMb2NhbGU6Ok9iamVjdBI6CUB3aW4wOg1AY291
|
5
|
+
bnRyeSIHTkw6CkBoYXNoaf3L2Js6DkBvcmlnX3N0ciIKbmwtTkw6DUBpc28z
|
6
|
+
MDY2MDoNQGNoYXJzZXQiClVURi04Og5AbGFuZ3VhZ2UiB25sOg5AbW9kaWZp
|
7
|
+
ZXIwOgtAcG9zaXgiCm5sX05MOg1AZ2VuZXJhbCIKbmxfTkw6DUB2YXJpYW50
|
8
|
+
MDoOQGZhbGxiYWNrMDoMQHNjcmlwdDA6DnBlcnNvbl9pZGkCMgc=--7918aed37151c13360cd370c37b541f136146fbd
|
9
|
+
Parameters: {"action"=>"index", "controller"=>"dashboard"}
|
10
|
+
Set language to: nl_NL
|
11
|
+
Rendering template within layouts/priscilla
|
12
|
+
Rendering dashboard/index
|
13
|
+
Completed in 0.22699 (4 reqs/sec) | Rendering: 0.02667 (11%) | DB: 0.03057 (13%) | 200 OK [https://www.example.com/]
|
14
|
+
|
15
|
+
|
16
|
+
Processing PeopleController#index (for 1.1.1.1 at 2008-08-14 21:16:30) [GET]
|
17
|
+
Session ID: BAh7CSIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo
|
18
|
+
SGFzaHsABjoKQHVzZWR7ADoMcmVmZXJlciIQL3ByaXNjaWxsYS86DnBlcnNv
|
19
|
+
bl9pZGkCMgc6DWxhbmd1YWdlbzoTTG9jYWxlOjpPYmplY3QSOg1AY291bnRy
|
20
|
+
eSIHTkw6CUB3aW4wOg5Ab3JpZ19zdHIiCm5sLU5MOgpAaGFzaGn9y9ibOg5A
|
21
|
+
bGFuZ3VhZ2UiB25sOg1AY2hhcnNldCIKVVRGLTg6DUBpc28zMDY2MDoOQG1v
|
22
|
+
ZGlmaWVyMDoLQHBvc2l4IgpubF9OTDoNQHZhcmlhbnQwOg1AZ2VuZXJhbCIK
|
23
|
+
bmxfTkw6DEBzY3JpcHQwOg5AZmFsbGJhY2sw--48cbe3788ef27f6005f8e999610a42af6e90ffb3
|
24
|
+
Parameters: {"commit"=>"Zoek", "action"=>"index", "q"=>"gaby", "controller"=>"people"}
|
25
|
+
Set language to: nl_NL
|
26
|
+
Redirected to https://www.example.com/people/2545
|
27
|
+
Completed in 0.04759 (21 reqs/sec) | DB: 0.03719 (78%) | 302 Found [https://www.example.com/people?q=gaby&commit=Zoek]
|
28
|
+
|
29
|
+
|
30
|
+
Processing PeopleController#show (for 1.1.1.1 at 2008-08-14 21:16:30) [GET]
|
31
|
+
Session ID: BAh7CToMcmVmZXJlciIpL3ByaXNjaWxsYS9wZW9wbGU/cT1nYWJ5JmNvbW1p
|
32
|
+
dD1ab2VrIgpmbGFzaElDOidBY3Rpb25Db250cm9sbGVyOjpGbGFzaDo6Rmxh
|
33
|
+
c2hIYXNoewAGOgpAdXNlZHsAOg1sYW5ndWFnZW86E0xvY2FsZTo6T2JqZWN0
|
34
|
+
EjoJQHdpbjA6DUBjb3VudHJ5IgdOTDoKQGhhc2hp/cvYmzoOQG9yaWdfc3Ry
|
35
|
+
IgpubC1OTDoNQGlzbzMwNjYwOg1AY2hhcnNldCIKVVRGLTg6DkBsYW5ndWFn
|
36
|
+
ZSIHbmw6DkBtb2RpZmllcjA6C0Bwb3NpeCIKbmxfTkw6DUBnZW5lcmFsIgpu
|
37
|
+
bF9OTDoNQHZhcmlhbnQwOg5AZmFsbGJhY2swOgxAc2NyaXB0MDoOcGVyc29u
|
38
|
+
X2lkaQIyBw==--3ad1948559448522a49d289a2a89dc7ccbe8847a
|
39
|
+
Parameters: {"action"=>"show", "id"=>"2545", "controller"=>"people"}
|
40
|
+
Set language to: nl_NL
|
41
|
+
Rendering template within layouts/priscilla
|
42
|
+
Rendering people/show
|
43
|
+
person: John Doe, study_year: 2008/2009
|
44
|
+
Completed in 0.29077 (3 reqs/sec) | Rendering: 0.24187 (83%) | DB: 0.04030 (13%) | 200 OK [https://www.example.com/people/2545]
|
45
|
+
|
46
|
+
|
47
|
+
Processing PeopleController#picture (for 1.1.1.1 at 2008-08-14 21:16:35) [GET]
|
48
|
+
Session ID: BAh7CSIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo
|
49
|
+
SGFzaHsABjoKQHVzZWR7ADoMcmVmZXJlciIbL3ByaXNjaWxsYS9wZW9wbGUv
|
50
|
+
MjU0NToOcGVyc29uX2lkaQIyBzoNbGFuZ3VhZ2VvOhNMb2NhbGU6Ok9iamVj
|
51
|
+
dBI6DUBjb3VudHJ5IgdOTDoJQHdpbjA6DkBvcmlnX3N0ciIKbmwtTkw6CkBo
|
52
|
+
YXNoaf3L2Js6DkBsYW5ndWFnZSIHbmw6DUBjaGFyc2V0IgpVVEYtODoNQGlz
|
53
|
+
bzMwNjYwOg5AbW9kaWZpZXIwOgtAcG9zaXgiCm5sX05MOg1AdmFyaWFudDA6
|
54
|
+
DUBnZW5lcmFsIgpubF9OTDoMQHNjcmlwdDA6DkBmYWxsYmFjazA=--797a33f280a482647111397d138d0918f2658167
|
55
|
+
Parameters: {"action"=>"picture", "id"=>"2545", "controller"=>"people"}
|
56
|
+
Set language to: nl_NL
|
57
|
+
Rendering template within layouts/priscilla
|
58
|
+
Rendering people/picture
|
59
|
+
Completed in 0.05383 (18 reqs/sec) | Rendering: 0.04622 (85%) | DB: 0.00206 (3%) | 200 OK [https://www.example.com/people/2545/picture]
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Processing PageController#demo (for 127.0.0.1 at 2008-12-10 16:28:09) [GET]
|
2
|
+
Parameters: {"action"=>"demo", "controller"=>"page"}
|
3
|
+
Logging in from session data...
|
4
|
+
Logged in as test@example.com
|
5
|
+
Using locale: en-US, http-accept: ["en-US"], session: , det browser: en-US, det domain:
|
6
|
+
Rendering template within layouts/demo
|
7
|
+
Rendering page/demo
|
8
|
+
Rendered shared/_analytics (0.2ms)
|
9
|
+
Rendered layouts/_actions (0.6ms)
|
10
|
+
Rendered layouts/_menu (2.2ms)
|
11
|
+
Rendered layouts/_tabbar (0.5ms)
|
12
|
+
Completed in 614ms (View: 120, DB: 31) | 200 OK [http://www.example.coml/demo]
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Processing CachedController#cached (for 1.1.1.1 at 2008-12-24 07:36:53) [GET]
|
2
|
+
Parameters: {"action"=>"cached", "controller"=>"cached"}
|
3
|
+
Logging in from session data...
|
4
|
+
Logging in using cookie...
|
5
|
+
Using locale: zh-Hans, http-accept: ["zh-CN", "zh-HK", "zh-TW", "en-US"], session: , det browser: zh-Hans, det domain: , user pref locale:
|
6
|
+
Referer: http://www.example.com/referer
|
7
|
+
Cached fragment hit: views/zh-Hans-www-cached-cached-all-CN--- (0.0ms)
|
8
|
+
Filter chain halted as [#<ActionController::Caching::Actions::ActionCacheFilter:0x2a999ad620 @check=nil, @options={:store_options=>{}, :layout=>nil, :cache_path=>#<Proc:0x0000002a999b8890@/app/controllers/cached_controller.rb:8>}>] rendered_or_redirected.
|
9
|
+
Filter chain halted as [#<ActionController::Filters::AroundFilter:0x2a999ad120 @identifier=nil, @kind=:filter, @options={:only=>#<Set: {"cached"}>, :if=>:not_logged_in?, :unless=>nil}, @method=#<ActionController::Caching::Actions::ActionCacheFilter:0x2a999ad620 @check=nil, @options={:store_options=>{}, :layout=>nil, :cache_path=>#<Proc:0x0000002a999b8890@/app/controllers/cached_controller.rb:8>}>>] did_not_yield.
|
10
|
+
Completed in 3ms (View: 0, DB: 0) | 200 OK [http://www.example.com/cached/cached/]
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Processing AccountController#dashboard (for 1.1.1.1 at 2008-12-24 07:36:49) [GET]
|
2
|
+
Parameters: {"action"=>"dashboard", "controller"=>"account", "first_use"=>"true"}
|
3
|
+
Logging in from session data...
|
4
|
+
|
5
|
+
|
6
|
+
Processing ProjectsController#new (for 1.1.1.1 at 2008-12-24 07:36:49) [GET]
|
7
|
+
Parameters: {"action"=>"new", "controller"=>"projects"}
|
8
|
+
Rendering template within layouts/default
|
9
|
+
Rendering account/dashboard
|
10
|
+
Logging in from session data...
|
11
|
+
Logging in using cookie...
|
12
|
+
Using locale: en-US, http-accept: [], session: , det browser: , det domain: , user pref locale:
|
13
|
+
Rendered shared/_maintenance (0.6ms)
|
14
|
+
Rendering template within layouts/templates/general_default/index.html.erb
|
15
|
+
Rendered projects/_recent_designs (4.3ms)
|
16
|
+
Rendered projects/_project (13.6ms)
|
17
|
+
Rendered projects/_projects (18.7ms)
|
18
|
+
Rendered layouts/_menu (1.4ms)
|
19
|
+
Completed in 36ms (View: 30, DB: 3) | 200 OK [http://www.example.com/projects/new]
|
20
|
+
Rendered layouts/_actions (0.3ms)
|
21
|
+
Rendered layouts/_menu (1.6ms)
|
22
|
+
Rendered layouts/_tabbar (1.9ms)
|
23
|
+
Rendered layouts/_footer (3.2ms)
|
24
|
+
Completed in 50ms (View: 41, DB: 4) | 200 OK [http://www.example.com/dashboard?first_use=true]
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper" # loads libs
|
2
|
+
|
3
|
+
error_line = %q{[Sun Nov 30 14:23:45 2008] [error] [client 10.0.1.197] Invalid URI in request GET .\\.\\.\\.\\.\\.\\.\\.\\.\\.\\/winnt/win.ini HTTP/1.1}
|
4
|
+
access_line = %q{127.81.248.53 - - [14/Jan/2009:11:49:43 -0500] "GET /reports/REPORT7_1ART02.pdf HTTP/1.1" 206 255404}
|
5
|
+
|
6
|
+
mangled_error_line = error_line + "more words"
|
7
|
+
|
8
|
+
puts "Processed Error Message:"
|
9
|
+
puts error_line.tokenize_apache_logs.inspect
|
10
|
+
puts "Error Message with extras:"
|
11
|
+
puts mangled_error_line.tokenize_apache_logs.inspect
|
12
|
+
puts "Processed Access Message"
|
13
|
+
puts access_line.tokenize_apache_logs.inspect
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
require 'teeth/
|
1
|
+
require 'teeth/scan_apache_logs'
|
2
|
+
#require 'teeth/scan_rails_logs'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + "/../lib/teeth"
|
2
5
|
|
3
6
|
def be_greater_than(expected)
|
4
7
|
simple_matcher("be greater than #{expected.to_s}") do |given, matcher|
|
@@ -8,3 +11,5 @@ def be_greater_than(expected)
|
|
8
11
|
end
|
9
12
|
|
10
13
|
end
|
14
|
+
|
15
|
+
include Teeth
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe RuleStatement do
|
4
|
+
|
5
|
+
it "should generate a rule to short circuit scanner processing when given option :skip_line => true" do
|
6
|
+
rs = RuleStatement.new :rails_session_id_start, '{WS}*Session ID":"', :skip_line => true
|
7
|
+
expected =
|
8
|
+
%q|{WS}*Session ID":" {
|
9
|
+
return EOF_KVPAIR;
|
10
|
+
}|
|
11
|
+
rs.scanner_code.should == expected
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should use strip_ends(yytext) in the rule when given option :strip_ends => true" do
|
15
|
+
rs = RuleStatement.new :browser_string, '{BROWSER_STR}', :strip_ends => true
|
16
|
+
expected =
|
17
|
+
%q|{BROWSER_STR} {
|
18
|
+
KVPAIR browser_string = {"browser_string", strip_ends(yytext)};
|
19
|
+
return browser_string;
|
20
|
+
}|
|
21
|
+
rs.scanner_code.should == expected
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should include a call to the BEGIN() macro if given the :begin option" do
|
25
|
+
rs = RuleStatement.new :start_special_state, '{SPECIAL_STATE_REGEX}', :begin => "SPECIAL_STATE"
|
26
|
+
expected =
|
27
|
+
%q|{SPECIAL_STATE_REGEX} {
|
28
|
+
BEGIN(SPECIAL_STATE);
|
29
|
+
KVPAIR start_special_state = {"start_special_state", yytext};
|
30
|
+
return start_special_state;
|
31
|
+
}|
|
32
|
+
rs.scanner_code.should == expected
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should not include any C code if given :ignore => true" do
|
36
|
+
rs = RuleStatement.new :catchall_rule_for_special_state, '<SPECIAL_STATE>{CATCHALL}', :ignore => true
|
37
|
+
expected = %q|<SPECIAL_STATE>{CATCHALL}|
|
38
|
+
rs.scanner_code.should == expected
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
describe RuleStatementGroup do
|
44
|
+
|
45
|
+
before(:each) do
|
46
|
+
@statement_group = RuleStatementGroup.new
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should not reject duplicate rule definitions" do
|
50
|
+
@statement_group.add :explode_on_2nd_try, '{WS}'
|
51
|
+
lambda {@statement_group.add :explode_on_2nd_try, '{WS}'}.should_not raise_error DuplicateRuleError
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should use method missing magic to define rules with sugary syntax" do
|
55
|
+
@statement_group.http_version "{HTTP_VERSION}"
|
56
|
+
@statement_group.first.should == RuleStatement.new(:http_version, "{HTTP_VERSION}")
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require "teeth/scan_apache_logs"
|
2
3
|
$INCLUDE_SLOW_TESTS = true
|
3
4
|
|
4
5
|
describe "Apache Lexer Extension", "when lexing apache errors" do
|
5
6
|
|
6
7
|
before(:each) do
|
7
8
|
str = "[Sun Nov 30 14:23:45 2008] [error] [client 10.0.1.197] Invalid URI in request GET .\\.\\.\\.\\.\\.\\.\\.\\.\\.\\/winnt/win.ini HTTP/1.1"
|
8
|
-
@tokens = str.
|
9
|
+
@tokens = str.scan_apache_logs
|
9
10
|
end
|
10
11
|
|
11
12
|
it "should return an uuid and empty message for an empty string" do
|
12
|
-
tokens = "".
|
13
|
+
tokens = "".scan_apache_logs
|
13
14
|
tokens[:message].should == ""
|
14
15
|
tokens[:id].should match(/[0-9A-F]{32}/)
|
15
16
|
end
|
@@ -30,9 +31,13 @@ describe "Apache Lexer Extension", "when lexing apache errors" do
|
|
30
31
|
@tokens[:relative_url].first.should == ".\\.\\.\\.\\.\\.\\.\\.\\.\\.\\/winnt/win.ini"
|
31
32
|
end
|
32
33
|
|
34
|
+
it "should group unknown tokens into strings" do
|
35
|
+
@tokens[:strings].should == ["client", "Invalid URI in request"]
|
36
|
+
end
|
37
|
+
|
33
38
|
it "should error out if the string is longer than 1M chars" do
|
34
39
|
str = ((("abcDE" * 2) * 1000) * 100) + "X"
|
35
|
-
lambda {str.
|
40
|
+
lambda {str.scan_apache_logs}.should raise_error(ArgumentError, "string too long for scan_apache_logs! max length is 1,000,000 chars")
|
36
41
|
end
|
37
42
|
|
38
43
|
end
|
@@ -40,19 +45,19 @@ end
|
|
40
45
|
describe "Apache Lexer Extension", "when lexing apache access logs" do
|
41
46
|
before(:each) do
|
42
47
|
str = %q{couchdb.localdomain:80 172.16.115.1 - - [13/Dec/2008:19:26:11 -0500] "GET /favicon.ico HTTP/1.1" 404 241 "http://172.16.115.130/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_4_11; en) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/3.2.1 Safari/525.27.1"}
|
43
|
-
@tokens = str.
|
48
|
+
@tokens = str.scan_apache_logs
|
44
49
|
str2 = %q{127.162.219.29 - - [14/Jan/2009:15:32:32 -0500] "GET /reports//ee_commerce/paypalcart.php?toroot=http://www.shenlishi.com//skin/fxid1.txt?? HTTP/1.1" 404 5636}
|
45
|
-
@tokens2 = str2.
|
50
|
+
@tokens2 = str2.scan_apache_logs
|
46
51
|
str3 = %q{127.81.248.53 - - [14/Jan/2009:11:49:43 -0500] "GET /reports/REPORT7_1ART02.pdf HTTP/1.1" 206 255404}
|
47
|
-
@tokens3 = str3.
|
52
|
+
@tokens3 = str3.scan_apache_logs
|
48
53
|
str4 = %q{127.140.136.56 - - [23/Jan/2009:12:59:24 -0500] "GET /scripts/..%255c%255c../winnt/system32/cmd.exe?/c+dir" 404 5607}
|
49
|
-
@tokens4 = str4.
|
54
|
+
@tokens4 = str4.scan_apache_logs
|
50
55
|
str5 = %q{127.254.43.205 - - [26/Jan/2009:08:32:08 -0500] "GET /reports/REPORT9_3.pdf//admin/includes/footer.php?admin_template_default=../../../../../../../../../../../../../etc/passwd%00 HTTP/1.1" 404 5673}
|
51
|
-
@tokens5 = str5.
|
56
|
+
@tokens5 = str5.scan_apache_logs
|
52
57
|
str6 = %q{127.218.234.82 - - [26/Jan/2009:08:32:19 -0500] "GET /reports/REPORT9_3.pdf//admin/includes/header.php?bypass_installed=1&bypass_restrict=1&row_secure[account_theme]=../../../../../../../../../../../../../etc/passwd%00 HTTP/1.1" 404 5721}
|
53
|
-
@tokens6 = str6.
|
58
|
+
@tokens6 = str6.scan_apache_logs
|
54
59
|
str_naked_url = %q{127.218.234.82 - - [26/Jan/2009:08:32:19 -0500] "GET / HTTP/1.1" 404 5721}
|
55
|
-
@tokens_naked_url = str_naked_url.
|
60
|
+
@tokens_naked_url = str_naked_url.scan_apache_logs
|
56
61
|
end
|
57
62
|
|
58
63
|
it "provides hints for testing" do
|
@@ -74,7 +79,7 @@ describe "Apache Lexer Extension", "when lexing apache access logs" do
|
|
74
79
|
(300 .. 305).map { |n| n.to_s } + ['307'] + (400 .. 417).map { |n| n.to_s } +
|
75
80
|
(500 .. 505).map { |n| n.to_s }
|
76
81
|
codes.each do |code|
|
77
|
-
code.
|
82
|
+
code.scan_apache_logs[:http_response].first.should == code
|
78
83
|
end
|
79
84
|
end
|
80
85
|
|