ngmoco-request-log-analyzer 1.4.2
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/.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
|