danielsdeleo-teeth 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|