-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add importer for log files #75
Changes from 9 commits
c731a3a
cb95d52
14f459c
16b0221
ef50d50
91ce356
049e262
162946c
c6c6047
ecc7f41
b1d77bc
b557e4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
require 'daru/io/importers/base' | ||
|
||
module Daru | ||
module IO | ||
module Importers | ||
# RailsLog Importer Class, that extends `read_rails_log` methods | ||
# to `Daru::DataFrame` | ||
class RailsLog < Base | ||
Daru::DataFrame.register_io_module :read_rails_log, self | ||
|
||
# Checks for required gem dependencies of RailsLog importer | ||
# and requires the patch for request-log-analyzer gem | ||
def initialize | ||
optional_gem 'request-log-analyzer', '~> 1.13.4', requires: 'request_log_analyzer' | ||
require 'daru/io/importers/shared/request_log_analyzer_patch' | ||
end | ||
|
||
# Reads data from a rails log file | ||
# | ||
# @!method self.read(path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please document |
||
# | ||
# @param path [String] Path to rails log file, where the dataframe is to be | ||
# imported from. | ||
# | ||
# @return [Daru::IO::Importers::RailsLog] | ||
# | ||
# @example Reading from plaintext file | ||
# instance = Daru::IO::Importers::RailsLog.read("rails_test.log") | ||
def read(path, format: :rails3) | ||
parser = RequestLogAnalyzer::Source::LogParser.new(RequestLogAnalyzer::FileFormat.load(format)) | ||
parser.extend(RequestLogAnalyzerPatch) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This means, that module RequestLogAnalyzerPatch
# look, no other nested modules!
def parse_hash(file)
end
end This way, when you do this extend, |
||
@file_data = parser.parse_hash(path) | ||
self | ||
end | ||
|
||
# header of the parsed information | ||
ORDER = %i[method path ip timestamp line_type lineno source | ||
controller action format params rendered_file | ||
partial_duration status duration view db].freeze | ||
|
||
# Imports a `Daru::DataFrame` from a RailsLog Importer instance and rails log file | ||
# | ||
# @return [Daru::DataFrame] | ||
# | ||
# @example Reading from a rails log file | ||
# df = instance.call | ||
# | ||
# => #<Daru::DataFrame(150x17)> | ||
# # method path ip timestamp line_type lineno source contr... | ||
# # 0 GET / 127.0.0.1 2018022607 completed 5 /home/roh Rails... | ||
# # 1 GET / 127.0.0.1 2018022716 completed 12 /home/roh Rails... | ||
# # ... ... ... ... ... ... ... ... ... | ||
def call | ||
Daru::DataFrame.rows(@file_data, order: ORDER) | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
module RequestLogAnalyzerPatch | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I proposed this module name, I've meant the following: You define ONLY parser = RequestLogAnalyzer::Source::LogParser.new(RequestLogAnalyzer::FileFormat.load(format))
parser.extend(RequestLogAnalyzerPatch) # only this instance has method replaced
parser.parse_hash(path) This way it is easy to see what you "inserting" into the third-party gem, and keep it local. |
||
module RequestLogAnalyzer::Source # rubocop:disable Style/ClassAndModuleChildren | ||
# LogParser class, that reads log data from a given source and uses a file format | ||
# definition to parse all relevent information about requests from the file | ||
class LogParser | ||
# Patch for the gem request-log-analyzer version 1.13.4 to combine the methods parse_file, | ||
# parse_io and parse_line of the LogParser class. Assigns all the necessary instance | ||
# variables defined in the above specified methods. Creates a request for each line of | ||
# the file stream and stores the hash of parsed information in raw_list. Each element of | ||
# parsed_list is an array of one parsed entry in log file. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh, now, I see a problem here (I thought that
Why???? What's the point of combining three methods in one 40-lines mess?.. Why can't you use original methods as they were, just calling them? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh. You should have spend a bit more time on understanding
...and, as it is aliased as RequestLogAnalyzer::Source::LogParser
.new(RequestLogAnalyzer::FileFormat.load(format))
.map { converting Request into line for dataframe } ...and that would be completely it. |
||
def parse_hash(file) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength | ||
log_file = File.open(file, 'rb') | ||
@max_line_length = max_line_length | ||
@line_divider = line_divider | ||
@current_lineno = 0 | ||
@file_format = file_format | ||
@current_source = File.expand_path(file) | ||
raw_list = [] | ||
while (line = log_file.gets(@line_divider, @max_line_length)) | ||
@current_lineno += 1 | ||
unless (request_data = @file_format.parse_line(line) { |wt, message| warn(wt, message) }) | ||
next | ||
end | ||
request_data = request_data.merge(source: @current_source, lineno: @current_lineno) | ||
@parsed_lines += 1 | ||
update_current_request(request_data) | ||
raw_hash = @file_format.request(request_data).attributes | ||
raw_list << raw_hash unless raw_hash.nil? | ||
end | ||
parsed_list = [] | ||
raw_list.each do |hash| | ||
parsed_list << hash if hash.key? :method | ||
end | ||
(0...parsed_list.size).each do |i| | ||
j = raw_list.index(parsed_list[i]) | ||
k = raw_list.index(parsed_list[i+1]) | ||
k = k.nil? ? raw_list.size : k | ||
(j...k).each do |l| | ||
parsed_list[i].merge!(raw_list[l]) | ||
end | ||
parsed_list[i] = Daru::IO::Importers::RailsLog::ORDER | ||
.map { |attr| parsed_list[i].include?(attr) ? parsed_list[i][attr] : nil } | ||
end | ||
parsed_list | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
RSpec.describe Daru::IO::Importers::RailsLog do | ||
subject { described_class.read(path).call } | ||
|
||
context 'parsing rails log' do | ||
let(:path) { 'spec/fixtures/rails_log/rails.log' } | ||
|
||
it_behaves_like 'exact daru dataframe', | ||
ncols: 17, | ||
nrows: 1, | ||
order: %i[method path ip timestamp line_type lineno source | ||
controller action format params rendered_file | ||
partial_duration status duration view db], | ||
:'timestamp.to_a' => [20_180_312_174_118], | ||
:'duration.to_a' => [0.097] | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Started GET "/articles/9" for 127.0.0.1 at 2018-03-12 17:41:18 +0530 | ||
Processing by ArticlesController#show as HTML | ||
Parameters: {"id"=>"9"} | ||
[1m[36mArticle Load (1.4ms)[0m [1m[34mSELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?[0m [["id", 9], ["LIMIT", 1]] | ||
Rendering articles/show.html.erb within layouts/application | ||
Rendered articles/show.html.erb within layouts/application (2.9ms) | ||
Completed 200 OK in 97ms (Views: 50.6ms | ActiveRecord: 1.4ms) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just remove the comment. Please don't repeat what method code says clearly (even if other importers do that :)).