ngmoco-request-log-analyzer 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/DESIGN.rdoc +41 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +8 -0
- data/bin/request-log-analyzer +114 -0
- data/lib/cli/command_line_arguments.rb +301 -0
- data/lib/cli/database_console.rb +26 -0
- data/lib/cli/database_console_init.rb +43 -0
- data/lib/cli/progressbar.rb +213 -0
- data/lib/cli/tools.rb +46 -0
- data/lib/request_log_analyzer.rb +44 -0
- data/lib/request_log_analyzer/aggregator.rb +49 -0
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
- data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
- data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
- data/lib/request_log_analyzer/controller.rb +332 -0
- data/lib/request_log_analyzer/database.rb +102 -0
- data/lib/request_log_analyzer/database/base.rb +115 -0
- data/lib/request_log_analyzer/database/connection.rb +38 -0
- data/lib/request_log_analyzer/database/request.rb +22 -0
- data/lib/request_log_analyzer/database/source.rb +13 -0
- data/lib/request_log_analyzer/database/warning.rb +14 -0
- data/lib/request_log_analyzer/file_format.rb +160 -0
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
- data/lib/request_log_analyzer/file_format/apache.rb +141 -0
- data/lib/request_log_analyzer/file_format/merb.rb +67 -0
- data/lib/request_log_analyzer/file_format/rack.rb +11 -0
- data/lib/request_log_analyzer/file_format/rails.rb +176 -0
- data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
- data/lib/request_log_analyzer/filter.rb +30 -0
- data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
- data/lib/request_log_analyzer/filter/field.rb +42 -0
- data/lib/request_log_analyzer/filter/timespan.rb +45 -0
- data/lib/request_log_analyzer/line_definition.rb +111 -0
- data/lib/request_log_analyzer/log_processor.rb +99 -0
- data/lib/request_log_analyzer/mailer.rb +62 -0
- data/lib/request_log_analyzer/output.rb +113 -0
- data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
- data/lib/request_log_analyzer/output/html.rb +184 -0
- data/lib/request_log_analyzer/request.rb +175 -0
- data/lib/request_log_analyzer/source.rb +72 -0
- data/lib/request_log_analyzer/source/database_loader.rb +87 -0
- data/lib/request_log_analyzer/source/log_parser.rb +274 -0
- data/lib/request_log_analyzer/tracker.rb +206 -0
- data/lib/request_log_analyzer/tracker/duration.rb +104 -0
- data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
- data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
- data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
- data/request-log-analyzer.gemspec +40 -0
- data/spec/database.yml +23 -0
- data/spec/fixtures/apache_combined.log +5 -0
- data/spec/fixtures/apache_common.log +10 -0
- data/spec/fixtures/decompression.log +12 -0
- data/spec/fixtures/decompression.log.bz2 +0 -0
- data/spec/fixtures/decompression.log.gz +0 -0
- data/spec/fixtures/decompression.log.zip +0 -0
- data/spec/fixtures/decompression.tar.gz +0 -0
- data/spec/fixtures/decompression.tgz +0 -0
- data/spec/fixtures/header_and_footer.log +6 -0
- data/spec/fixtures/merb.log +84 -0
- data/spec/fixtures/merb_prefixed.log +9 -0
- data/spec/fixtures/multiple_files_1.log +5 -0
- data/spec/fixtures/multiple_files_2.log +2 -0
- data/spec/fixtures/rails.db +0 -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/fixtures/syslog_1x.log +5 -0
- data/spec/fixtures/test_file_format.log +13 -0
- data/spec/fixtures/test_language_combined.log +14 -0
- data/spec/fixtures/test_order.log +16 -0
- data/spec/integration/command_line_usage_spec.rb +84 -0
- data/spec/integration/munin_plugins_rails_spec.rb +58 -0
- data/spec/integration/scout_spec.rb +151 -0
- data/spec/lib/helpers.rb +52 -0
- data/spec/lib/macros.rb +18 -0
- data/spec/lib/matchers.rb +77 -0
- data/spec/lib/mocks.rb +76 -0
- data/spec/lib/testing_format.rb +46 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
- data/spec/unit/aggregator/summarizer_spec.rb +26 -0
- data/spec/unit/controller/controller_spec.rb +41 -0
- data/spec/unit/controller/log_processor_spec.rb +18 -0
- data/spec/unit/database/base_class_spec.rb +183 -0
- data/spec/unit/database/connection_spec.rb +34 -0
- data/spec/unit/database/database_spec.rb +133 -0
- data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
- data/spec/unit/file_format/apache_format_spec.rb +203 -0
- data/spec/unit/file_format/file_format_api_spec.rb +69 -0
- data/spec/unit/file_format/line_definition_spec.rb +75 -0
- data/spec/unit/file_format/merb_format_spec.rb +52 -0
- data/spec/unit/file_format/rails_format_spec.rb +164 -0
- data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
- data/spec/unit/filter/field_filter_spec.rb +66 -0
- data/spec/unit/filter/filter_spec.rb +17 -0
- data/spec/unit/filter/timespan_filter_spec.rb +58 -0
- data/spec/unit/mailer_spec.rb +30 -0
- data/spec/unit/request_spec.rb +111 -0
- data/spec/unit/source/log_parser_spec.rb +119 -0
- data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
- data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
- data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
- data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
- data/spec/unit/tracker/tracker_api_spec.rb +124 -0
- data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
- data/tasks/github-gem.rake +323 -0
- data/tasks/request_log_analyzer.rake +26 -0
- metadata +220 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activerecord'
|
3
|
+
|
4
|
+
class RequestLogAnalyzer::Database
|
5
|
+
|
6
|
+
def self.const_missing(const) # :nodoc:
|
7
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
8
|
+
end
|
9
|
+
|
10
|
+
include RequestLogAnalyzer::Database::Connection
|
11
|
+
|
12
|
+
attr_accessor :file_format
|
13
|
+
attr_reader :line_classes
|
14
|
+
|
15
|
+
def initialize(connection_identifier = nil)
|
16
|
+
@line_classes = []
|
17
|
+
RequestLogAnalyzer::Database::Base.database = self
|
18
|
+
connect(connection_identifier)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the ORM class for the provided line type
|
22
|
+
def get_class(line_type)
|
23
|
+
line_type = line_type.name if line_type.respond_to?(:name)
|
24
|
+
Object.const_get("#{line_type}_line".camelize)
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_classes
|
28
|
+
[RequestLogAnalyzer::Database::Request, RequestLogAnalyzer::Database::Source, RequestLogAnalyzer::Database::Warning]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Loads the ORM classes by inspecting the tables in the current database
|
32
|
+
def load_database_schema!
|
33
|
+
connection.tables.map do |table|
|
34
|
+
case table.to_sym
|
35
|
+
when :warnings then RequestLogAnalyzer::Database::Warning
|
36
|
+
when :sources then RequestLogAnalyzer::Database::Source
|
37
|
+
when :requests then RequestLogAnalyzer::Database::Request
|
38
|
+
else load_activerecord_class(table)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns an array of all the ActiveRecord-bases ORM classes for this database
|
44
|
+
def orm_classes
|
45
|
+
default_classes + line_classes
|
46
|
+
end
|
47
|
+
|
48
|
+
# Loads an ActiveRecord-based class that correspond to the given parameter, which can either be
|
49
|
+
# a table name or a LineDefinition instance.
|
50
|
+
def load_activerecord_class(linedefinition_or_table)
|
51
|
+
|
52
|
+
case linedefinition_or_table
|
53
|
+
when String, Symbol
|
54
|
+
klass_name = linedefinition_or_table.to_s.singularize.camelize
|
55
|
+
klass = RequestLogAnalyzer::Database::Base.subclass_from_table(linedefinition_or_table)
|
56
|
+
when RequestLogAnalyzer::LineDefinition
|
57
|
+
klass_name = "#{linedefinition_or_table.name}_line".camelize
|
58
|
+
klass = RequestLogAnalyzer::Database::Base.subclass_from_line_definition(linedefinition_or_table)
|
59
|
+
end
|
60
|
+
|
61
|
+
Object.const_set(klass_name, klass)
|
62
|
+
klass = Object.const_get(klass_name)
|
63
|
+
@line_classes << klass
|
64
|
+
return klass
|
65
|
+
end
|
66
|
+
|
67
|
+
def fileformat_classes
|
68
|
+
raise "No file_format provided!" unless file_format
|
69
|
+
line_classes = file_format.line_definitions.map { |(name, definition)| load_activerecord_class(definition) }
|
70
|
+
return default_classes + line_classes
|
71
|
+
end
|
72
|
+
|
73
|
+
# Creates the database schema and related ActiveRecord::Base subclasses that correspond to the
|
74
|
+
# file format definition. These ORM classes will later be used to create records in the database.
|
75
|
+
def create_database_schema!
|
76
|
+
fileformat_classes.each { |klass| klass.create_table! }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Drops the table of all the ORM classes, and unregisters the classes
|
80
|
+
def drop_database_schema!
|
81
|
+
file_format ? fileformat_classes.map(&:drop_table!) : orm_classes.map(&:drop_table!)
|
82
|
+
remove_orm_classes!
|
83
|
+
end
|
84
|
+
|
85
|
+
# Registers the default ORM classes in the default namespace
|
86
|
+
def register_default_orm_classes!
|
87
|
+
Object.const_set('Request', RequestLogAnalyzer::Database::Request)
|
88
|
+
Object.const_set('Source', RequestLogAnalyzer::Database::Source)
|
89
|
+
Object.const_set('Warning', RequestLogAnalyzer::Database::Warning)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Unregisters every ORM class constant
|
93
|
+
def remove_orm_classes!
|
94
|
+
orm_classes.each do |klass|
|
95
|
+
if klass.respond_to?(:name) && !klass.name.blank?
|
96
|
+
klass_name = klass.name.split('::').last
|
97
|
+
Object.send(:remove_const, klass_name) if Object.const_defined?(klass_name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
|
2
|
+
|
3
|
+
self.abstract_class = true
|
4
|
+
|
5
|
+
def <=>(other)
|
6
|
+
if (source_id.nil? && other.source_id.nil?) || (source_comparison = source_id <=> other.source_id) == 0
|
7
|
+
lineno <=> other.lineno
|
8
|
+
else
|
9
|
+
source_comparison
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def line_type
|
14
|
+
self.class.name.underscore.gsub(/_line$/, '').to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
class_inheritable_accessor :line_definition
|
18
|
+
cattr_accessor :database
|
19
|
+
|
20
|
+
def self.subclass_from_line_definition(definition)
|
21
|
+
klass = Class.new(RequestLogAnalyzer::Database::Base)
|
22
|
+
klass.set_table_name("#{definition.name}_lines")
|
23
|
+
|
24
|
+
klass.line_definition = definition
|
25
|
+
|
26
|
+
# Set relations with requests and sources table
|
27
|
+
klass.belongs_to :request
|
28
|
+
klass.belongs_to :source
|
29
|
+
|
30
|
+
# Serialize complex fields into the database
|
31
|
+
definition.captures.select { |c| c.has_key?(:provides) }.each do |capture|
|
32
|
+
klass.send(:serialize, capture[:name], Hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
RequestLogAnalyzer::Database::Request.has_many "#{definition.name}_lines".to_sym
|
36
|
+
RequestLogAnalyzer::Database::Source.has_many "#{definition.name}_lines".to_sym
|
37
|
+
|
38
|
+
return klass
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.subclass_from_table(table)
|
42
|
+
raise "Table #{table} not found!" unless database.connection.table_exists?(table)
|
43
|
+
|
44
|
+
klass = Class.new(RequestLogAnalyzer::Database::Base)
|
45
|
+
klass.set_table_name(table)
|
46
|
+
|
47
|
+
if klass.column_names.include?('request_id')
|
48
|
+
klass.belongs_to :request
|
49
|
+
RequestLogAnalyzer::Database::Request.has_many table.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
if klass.column_names.include?('source_id')
|
53
|
+
klass.belongs_to :source
|
54
|
+
RequestLogAnalyzer::Database::Source.has_many table.to_sym
|
55
|
+
end
|
56
|
+
|
57
|
+
return klass
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.drop_table!
|
61
|
+
database.connection.remove_index(self.table_name, [:source_id]) rescue nil
|
62
|
+
database.connection.remove_index(self.table_name, [:request_id]) rescue nil
|
63
|
+
database.connection.drop_table(self.table_name) if database.connection.table_exists?(self.table_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.create_table!
|
67
|
+
raise "No line_definition available to base table schema on!" unless self.line_definition
|
68
|
+
|
69
|
+
unless table_exists?
|
70
|
+
database.connection.create_table(table_name.to_sym) do |t|
|
71
|
+
|
72
|
+
# Default fields
|
73
|
+
t.column :request_id, :integer
|
74
|
+
t.column :source_id, :integer
|
75
|
+
t.column :lineno, :integer
|
76
|
+
|
77
|
+
line_definition.captures.each do |capture|
|
78
|
+
# Add a field for every capture
|
79
|
+
t.column(capture[:name], column_type(capture[:type]))
|
80
|
+
|
81
|
+
# If the capture provides other field as well, create columns for them, too
|
82
|
+
capture[:provides].each { |field, field_type| t.column(field, column_type(field_type)) } if capture[:provides].kind_of?(Hash)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Add indices to table for more speedy querying
|
88
|
+
database.connection.add_index(self.table_name.to_sym, [:request_id]) # rescue
|
89
|
+
database.connection.add_index(self.table_name.to_sym, [:source_id]) # rescue
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Function to determine the column type for a field
|
94
|
+
# TODO: make more robust / include in file-format definition
|
95
|
+
def self.column_type(type_indicator)
|
96
|
+
case type_indicator
|
97
|
+
when :eval; :text
|
98
|
+
when :hash; :text
|
99
|
+
when :text; :text
|
100
|
+
when :string; :string
|
101
|
+
when :sec; :double
|
102
|
+
when :msec; :double
|
103
|
+
when :duration; :double
|
104
|
+
when :float; :double
|
105
|
+
when :double; :double
|
106
|
+
when :integer; :integer
|
107
|
+
when :int; :int
|
108
|
+
when :timestamp; :datetime
|
109
|
+
when :datetime; :datetime
|
110
|
+
when :date; :date
|
111
|
+
else :string
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RequestLogAnalyzer::Database::Connection
|
2
|
+
|
3
|
+
def self.from_string(string)
|
4
|
+
hash = {}
|
5
|
+
if string =~ /^(?:\w+=(?:[^;])*;)*\w+=(?:[^;])*$/
|
6
|
+
string.scan(/(\w+)=([^;]*);?/) { |variable, value| hash[variable.to_sym] = value }
|
7
|
+
elsif string =~ /^(\w+)\:\/\/(?:(?:([^:]+)(?:\:([^:]+))?\@)?([\w\.-]+)\/)?([\w\:\-\.\/]+)$/
|
8
|
+
hash[:adapter], hash[:username], hash[:password], hash[:host], hash[:database] = $1, $2, $3, $4, $5
|
9
|
+
hash.delete_if { |k, v| v.nil? }
|
10
|
+
end
|
11
|
+
return hash.empty? ? nil : hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def connect(connection_identifier)
|
15
|
+
if connection_identifier.kind_of?(Hash)
|
16
|
+
RequestLogAnalyzer::Database::Base.establish_connection(connection_identifier)
|
17
|
+
elsif connection_identifier == ':memory:'
|
18
|
+
RequestLogAnalyzer::Database::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
19
|
+
elsif connection_hash = RequestLogAnalyzer::Database::Connection.from_string(connection_identifier)
|
20
|
+
RequestLogAnalyzer::Database::Base.establish_connection(connection_hash)
|
21
|
+
elsif connection_identifier.kind_of?(String) # Normal SQLite 3 database file
|
22
|
+
RequestLogAnalyzer::Database::Base.establish_connection(:adapter => 'sqlite3', :database => connection_identifier)
|
23
|
+
elsif connection_identifier.nil?
|
24
|
+
nil
|
25
|
+
else
|
26
|
+
raise "Cannot connect with this connection_identifier: #{connection_identifier.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def disconnect
|
31
|
+
RequestLogAnalyzer::Database::Base.remove_connection
|
32
|
+
end
|
33
|
+
|
34
|
+
def connection
|
35
|
+
RequestLogAnalyzer::Database::Base.connection
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class RequestLogAnalyzer::Database::Request < RequestLogAnalyzer::Database::Base
|
2
|
+
|
3
|
+
# Returns an array of all the Line objects of this request in the correct order.
|
4
|
+
def lines
|
5
|
+
@lines ||= begin
|
6
|
+
lines = []
|
7
|
+
self.class.reflections.each { |r, d| lines += self.send(r).all }
|
8
|
+
lines.sort
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates the table to store requests in.
|
13
|
+
def self.create_table!
|
14
|
+
unless database.connection.table_exists?(:requests)
|
15
|
+
database.connection.create_table(:requests) do |t|
|
16
|
+
t.column :first_lineno, :integer
|
17
|
+
t.column :last_lineno, :integer
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class RequestLogAnalyzer::Database::Source < RequestLogAnalyzer::Database::Base
|
2
|
+
|
3
|
+
def self.create_table!
|
4
|
+
unless database.connection.table_exists?(:sources)
|
5
|
+
database.connection.create_table(:sources) do |t|
|
6
|
+
t.column :filename, :string
|
7
|
+
t.column :mtime, :datetime
|
8
|
+
t.column :filesize, :integer
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class RequestLogAnalyzer::Database::Warning < RequestLogAnalyzer::Database::Base
|
2
|
+
|
3
|
+
def self.create_table!
|
4
|
+
unless database.connection.table_exists?(:warnings)
|
5
|
+
database.connection.create_table(:warnings) do |t|
|
6
|
+
t.column :warning_type, :string, :limit => 30, :null => false
|
7
|
+
t.column :message, :string
|
8
|
+
t.column :source_id, :integer
|
9
|
+
t.column :lineno, :integer
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
2
|
+
|
3
|
+
def self.const_missing(const)
|
4
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
5
|
+
end
|
6
|
+
|
7
|
+
# Loads a FileFormat::Base subclass instance.
|
8
|
+
# You can provide:
|
9
|
+
# * A FileFormat instance (which will return itself)
|
10
|
+
# * A FileFormat class (of which an imstance will be returned)
|
11
|
+
# * A filename (from which the FileFormat class is loaded)
|
12
|
+
# * A symbol of a built-in file format (e.g. :rails)
|
13
|
+
def self.load(file_format, *args)
|
14
|
+
klass = nil
|
15
|
+
if file_format.kind_of?(RequestLogAnalyzer::FileFormat::Base)
|
16
|
+
# this already is a file format! return itself
|
17
|
+
return @current_file_format = file_format
|
18
|
+
|
19
|
+
elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
|
20
|
+
# a usable class is provided. Use this format class.
|
21
|
+
klass = file_format
|
22
|
+
|
23
|
+
elsif file_format.kind_of?(String) && File.exist?(file_format)
|
24
|
+
# load a format from a ruby file
|
25
|
+
require file_format
|
26
|
+
const = RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb'))
|
27
|
+
if RequestLogAnalyzer::FileFormat.const_defined?(const)
|
28
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(const)
|
29
|
+
elsif Object.const_defined?(const)
|
30
|
+
klass = Object.const_get(const)
|
31
|
+
else
|
32
|
+
raise "Cannot load class #{const} from #{file_format}!"
|
33
|
+
end
|
34
|
+
|
35
|
+
else
|
36
|
+
# load a provided file format
|
37
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
|
38
|
+
end
|
39
|
+
|
40
|
+
# check the returned klass to see if it can be used
|
41
|
+
raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
|
42
|
+
raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
|
43
|
+
|
44
|
+
@current_file_format = klass.create(*args) # return an instance of the class
|
45
|
+
end
|
46
|
+
|
47
|
+
# Base class for all log file format definitions. This class provides functions for subclasses to
|
48
|
+
# define their LineDefinitions and to define a summary report.
|
49
|
+
#
|
50
|
+
# A subclass of this class is instantiated when request-log-analyzer is started and this instance
|
51
|
+
# is shared with all components of the application so they can act on the specifics of the format
|
52
|
+
class Base
|
53
|
+
|
54
|
+
attr_reader :line_definitions, :report_trackers
|
55
|
+
|
56
|
+
####################################################################################
|
57
|
+
# CLASS METHODS for format definition
|
58
|
+
####################################################################################
|
59
|
+
|
60
|
+
# Registers the line definer instance for a subclass.
|
61
|
+
def self.inherited(subclass)
|
62
|
+
if subclass.superclass == RequestLogAnalyzer::FileFormat::Base
|
63
|
+
|
64
|
+
# Create aline and report definer for this class
|
65
|
+
subclass.class_eval do
|
66
|
+
instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
|
67
|
+
instance_variable_set(:@report_definer, RequestLogAnalyzer::Aggregator::Summarizer::Definer.new)
|
68
|
+
class << self; attr_accessor :line_definer, :report_definer; end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create a custom Request class for this file format
|
72
|
+
subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request)) unless subclass.const_defined?('Request')
|
73
|
+
else
|
74
|
+
|
75
|
+
# Copy the line and report definer from the parent class.
|
76
|
+
subclass.class_eval do
|
77
|
+
instance_variable_set(:@line_definer, superclass.line_definer.clone)
|
78
|
+
instance_variable_set(:@report_definer, superclass.report_definer.clone)
|
79
|
+
class << self; attr_accessor :line_definer, :report_definer; end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Create a custom Request class based on the superclass's Request class
|
83
|
+
subclass.const_set('Request', Class.new(subclass.superclass::Request)) unless subclass.const_defined?('Request')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Specifies a single line defintions.
|
88
|
+
def self.line_definition(name, &block)
|
89
|
+
@line_definer.send(name, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Specifies multiple line definitions at once using a block
|
93
|
+
def self.format_definition(&block)
|
94
|
+
if block_given?
|
95
|
+
yield self.line_definer
|
96
|
+
else
|
97
|
+
return self.line_definer
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Specifies the summary report using a block.
|
102
|
+
def self.report(mode = :append, &block)
|
103
|
+
self.report_definer.reset! if mode == :overwrite
|
104
|
+
yield(self.report_definer)
|
105
|
+
end
|
106
|
+
|
107
|
+
####################################################################################
|
108
|
+
# Instantiation
|
109
|
+
####################################################################################
|
110
|
+
|
111
|
+
def self.create(*args)
|
112
|
+
# Ignore arguments
|
113
|
+
return self.new(line_definer.line_definitions, report_definer.trackers)
|
114
|
+
end
|
115
|
+
|
116
|
+
def initialize(line_definitions = {}, report_trackers = [])
|
117
|
+
@line_definitions, @report_trackers = line_definitions, report_trackers
|
118
|
+
end
|
119
|
+
|
120
|
+
####################################################################################
|
121
|
+
# INSTANCE methods
|
122
|
+
####################################################################################
|
123
|
+
|
124
|
+
# Returns the Request class of this file format
|
125
|
+
def request_class
|
126
|
+
self.class::Request
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a Request instance with the given parsed lines that should be provided as hashes.
|
130
|
+
def request(*hashes)
|
131
|
+
request_class.create(self, *hashes)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Checks whether the line definitions form a valid language.
|
135
|
+
# A file format should have at least a header and a footer line type
|
136
|
+
def valid?
|
137
|
+
line_definitions.any? { |(name, ld)| ld.header } && line_definitions.any? { |(name, ld)| ld.footer }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns true if this language captures the given symbol in one of its line definitions
|
141
|
+
def captures?(name)
|
142
|
+
line_definitions.any? { |(name, ld)| ld.captures?(name) }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Function that a file format con implement to monkey patch the environment.
|
146
|
+
# * <tt>controller</tt> The environment is provided as a controller instance
|
147
|
+
def setup_environment(controller)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Parses a line by trying to parse it using every line definition in this file format
|
151
|
+
def parse_line(line, &warning_handler)
|
152
|
+
self.line_definitions.each do |lt, definition|
|
153
|
+
match = definition.matches(line, &warning_handler)
|
154
|
+
return match if match
|
155
|
+
end
|
156
|
+
|
157
|
+
return nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|