logtool 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/bin/logtool +5 -0
- data/lib/logtool.rb +70 -0
- data/lib/logtool/block.rb +44 -0
- data/lib/logtool/parser.rb +45 -0
- data/lib/logtool/query.rb +50 -0
- data/logtool.gemspec +18 -0
- metadata +77 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gemspec
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rake/testtask'
|
data/bin/logtool
ADDED
data/lib/logtool.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "logtool", "block.rb")
|
4
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "logtool", "parser.rb")
|
5
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "logtool", "query.rb")
|
6
|
+
|
7
|
+
module LogTool
|
8
|
+
def self.usage
|
9
|
+
<<USAGE
|
10
|
+
Usage: logtool file mode mode_options
|
11
|
+
|
12
|
+
File is the rails log file that should be parsed. Mode can be either gtk or query. For gtk, a window will open and let you explore the log file. For query, you can write a query that will be executed on the log entries.
|
13
|
+
|
14
|
+
== Query mode ==
|
15
|
+
When using the query mode, mode_options should be in the following format: filter information [options]. The first parameter filters the log entries, the second one is the information you want to retrieve. The last one is the only optional one, where you can specify further options.
|
16
|
+
==filter==
|
17
|
+
Boolean Operators: and, or, not
|
18
|
+
Value Operators ==, <=, >=, <, >
|
19
|
+
Values: ip, method, response, asset
|
20
|
+
|
21
|
+
==information to retrieve==
|
22
|
+
ip, head, tail, method, time
|
23
|
+
|
24
|
+
==options==
|
25
|
+
-l num: shows only the last num entries.
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
logtool log/production.log query "ip == 127.0.0.1" head
|
29
|
+
logtool log/production.log query "(ip == 127.0.0.1) and not asset" head
|
30
|
+
logtool log/production.log query "(ip == 127.0.0.1) or (ip == 0.0.0.0) and not asset" head
|
31
|
+
logtool log/production.log query "response > 200" ip
|
32
|
+
USAGE
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.fatal_error(err_msg=nil)
|
36
|
+
puts err_msg if err_msg
|
37
|
+
puts usage
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.supported_modes
|
42
|
+
%W(query)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.parse_args
|
46
|
+
fatal_error if ARGV.size < 2
|
47
|
+
|
48
|
+
options = {}
|
49
|
+
options[:input_file] = ARGV[0]
|
50
|
+
options[:mode] = ARGV[1]
|
51
|
+
options[:mode_args] = ARGV[2..-1]
|
52
|
+
|
53
|
+
fatal_error("Invalid mode") unless self.supported_modes.include?(options[:mode])
|
54
|
+
|
55
|
+
return options
|
56
|
+
end
|
57
|
+
|
58
|
+
public
|
59
|
+
def self.execute
|
60
|
+
options = parse_args
|
61
|
+
blocks = Parser.parse_blocks(options[:input_file])
|
62
|
+
|
63
|
+
case options[:mode]
|
64
|
+
when 'query' then
|
65
|
+
query = Query.new(blocks, options)
|
66
|
+
query.run
|
67
|
+
else puts "Mode not implemented"; exit 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module LogTool
|
2
|
+
class Block
|
3
|
+
attr_accessor :text, :ip, :method, :response, :asset, :time
|
4
|
+
|
5
|
+
def to_s
|
6
|
+
@text
|
7
|
+
end
|
8
|
+
|
9
|
+
def is_asset?
|
10
|
+
@asset
|
11
|
+
end
|
12
|
+
|
13
|
+
def head
|
14
|
+
@text.split("\n").first
|
15
|
+
end
|
16
|
+
|
17
|
+
def tail
|
18
|
+
@text.split("\n").last
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.head
|
22
|
+
#$1 = method, $2 = IP
|
23
|
+
/^Started (GET|POST|PUT|DELETE) ".+?" for (\d\d?\d?.\d\d?\d?.\d\d?\d?.\d\d?\d?) at .*$/
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.tail
|
27
|
+
# $1 = response
|
28
|
+
time = /\d+(ms|s)/
|
29
|
+
/^Completed (\d\d\d) .+? in (\d+ms)$/
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.asset_tail
|
33
|
+
# $1 = response, $2 = time
|
34
|
+
time = /\d+(ms|s)/
|
35
|
+
/^Served asset [0-9a-zA-Z\/._]+ - (\d\d\d) .*? \((\d+ms)\)$/
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_for(query)
|
39
|
+
eval query
|
40
|
+
rescue => e
|
41
|
+
LogTool::fatal_error("Bad filter. #{e.message}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "block.rb")
|
2
|
+
|
3
|
+
module LogTool
|
4
|
+
class Parser
|
5
|
+
|
6
|
+
def self.open_log(file_path)
|
7
|
+
yield File.open(file_path)
|
8
|
+
rescue Errno::ENOENT
|
9
|
+
puts "#{file_path}: No such file or directory"
|
10
|
+
exit 1
|
11
|
+
rescue Errno::EACCES
|
12
|
+
puts "#{file_path}: Permission denied"
|
13
|
+
exit 2
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_blocks(file_path)
|
17
|
+
blocks = []
|
18
|
+
block = nil
|
19
|
+
open_log(file_path) do |fh|
|
20
|
+
fh.read.split("\n").each do |line|
|
21
|
+
if line =~ Block.head
|
22
|
+
blocks << block if block
|
23
|
+
block = Block.new
|
24
|
+
block.method = $1
|
25
|
+
block.ip = $2
|
26
|
+
block.text = line + "\n"
|
27
|
+
elsif block and line =~ Block.tail
|
28
|
+
block.response = $1
|
29
|
+
block.time = $2
|
30
|
+
block.text += line + "\n"
|
31
|
+
block.freeze
|
32
|
+
elsif block and line =~ Block.asset_tail
|
33
|
+
block.response = $1
|
34
|
+
block.time = $2
|
35
|
+
block.text += line + "\n"
|
36
|
+
block.freeze
|
37
|
+
else
|
38
|
+
block.text += line + "\n" if block and !block.frozen?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
return blocks
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module LogTool
|
2
|
+
class Query
|
3
|
+
def initialize(blocks, options)
|
4
|
+
@blocks = blocks
|
5
|
+
@filter = parse_filter(options[:mode_args])
|
6
|
+
@target_data = parse_target_data(options[:mode_args])
|
7
|
+
end
|
8
|
+
|
9
|
+
def target_data_type
|
10
|
+
%W(ip head tail method time)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_target_data(args)
|
14
|
+
if target_data_type.include?(args[1])
|
15
|
+
args[1]
|
16
|
+
else
|
17
|
+
LogTool::fatal_error("Undefined target data type: #{args[1]}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def keywords
|
22
|
+
target_data_type + %W(ip and or not < > <= >= == != asset)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ip_regexp
|
26
|
+
/\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?/
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_filter(options)
|
30
|
+
filter = []
|
31
|
+
options.first.split.each do |word|
|
32
|
+
if keywords.include?(word)
|
33
|
+
filter << word
|
34
|
+
elsif word =~ ip_regexp
|
35
|
+
filter << "\"#{word}\""
|
36
|
+
elsif word =~ /\d+/
|
37
|
+
filter << word
|
38
|
+
else
|
39
|
+
LogTool::fatal_error("Invalid filter keyword: #{word}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
filter.join(' ')
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
@blocks.find_all{|b| b.test_for(@filter)}.each{|b| puts b.send(@target_data)}
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/logtool.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "logtool"
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ["Felipe Tanus"]
|
8
|
+
s.email = ["fotanus@jgmail.com"]
|
9
|
+
s.homepage = "https://github.com/fotanus/logtool"
|
10
|
+
s.summary = %q{logtool is a Rails log parser}
|
11
|
+
s.description = %q{logtool is a Rails log parser that can accepts complex queries}
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logtool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Felipe Tanus
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-01-17 00:00:00 -02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: logtool is a Rails log parser that can accepts complex queries
|
23
|
+
email:
|
24
|
+
- fotanus@jgmail.com
|
25
|
+
executables:
|
26
|
+
- logtool
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files:
|
30
|
+
- README.md
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- README.md
|
35
|
+
- Rakefile
|
36
|
+
- bin/logtool
|
37
|
+
- lib/logtool.rb
|
38
|
+
- lib/logtool/block.rb
|
39
|
+
- lib/logtool/parser.rb
|
40
|
+
- lib/logtool/query.rb
|
41
|
+
- logtool.gemspec
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: https://github.com/fotanus/logtool
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.6.2
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: logtool is a Rails log parser
|
76
|
+
test_files: []
|
77
|
+
|