http-log-analyzer 0.3
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Rakefile +3 -0
- data/TODO.txt +32 -0
- data/bin/http-log-analyzer +47 -0
- data/http-log-analyzer.gemspec +28 -0
- data/lib/http-log-analyzer.rb +24 -0
- data/lib/http-log-analyzer/element.rb +19 -0
- data/lib/http-log-analyzer/element/referer.rb +39 -0
- data/lib/http-log-analyzer/element/request.rb +21 -0
- data/lib/http-log-analyzer/element/source.rb +32 -0
- data/lib/http-log-analyzer/element/status.rb +23 -0
- data/lib/http-log-analyzer/element/user_agent.rb +38 -0
- data/lib/http-log-analyzer/entry.rb +28 -0
- data/lib/http-log-analyzer/importer.rb +90 -0
- data/lib/http-log-analyzer/stats.rb +79 -0
- data/lib/http-log-analyzer/version.rb +5 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5b4140aef82a3b635f50830132f5725b21a2f753d17f78cab80ef918807d3410
|
4
|
+
data.tar.gz: 91d4884ba4b346211cfdb5707aca91229f5df5dea3c8e6e96657e500b2025ee5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e3ed61db57fb6c32a382695c4d518429e936972c3ec954e0199d3eba54199ec6c47d1b226335a2e3aad59242f790c19cfa8746f0436ea5fce46b57585ba6878f
|
7
|
+
data.tar.gz: 8df22a7d0d7344c119caf3cbf05f7d3a678f794094713223179dd7e4634f4d75532303d623d1d44fff1fd05c7a038e0deed99e868fbe91f2e33e7b4ba2590569
|
data/.gitignore
ADDED
data/Rakefile
ADDED
data/TODO.txt
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
collapse referers by domain of referer
|
2
|
+
|
3
|
+
ensure domain is not in referers list
|
4
|
+
|
5
|
+
remove 'android-app://com.google.android.gm' from referers list
|
6
|
+
or count under Google?
|
7
|
+
|
8
|
+
cache log files (as Marshalled data) to reduce parsing time
|
9
|
+
|
10
|
+
move ignore-data to external files
|
11
|
+
|
12
|
+
use external list for user-agent strings
|
13
|
+
http://www.useragentstring.com/pages/useragentstring.php?name=All
|
14
|
+
https://www.webmasterworld.com/search_engine_spiders/4849056.htm
|
15
|
+
|
16
|
+
detect users & sessions
|
17
|
+
|
18
|
+
store parsed data
|
19
|
+
store cache in files
|
20
|
+
|
21
|
+
generate monthly reports
|
22
|
+
|
23
|
+
detect users & sessions
|
24
|
+
first parse, then process
|
25
|
+
|
26
|
+
add configuration file (in Ruby)
|
27
|
+
site/report name
|
28
|
+
domain names
|
29
|
+
ignored IPs
|
30
|
+
|
31
|
+
use more recent geo-IP databases?
|
32
|
+
https://dev.maxmind.com/geoip/geoip2/downloadable/
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'http-log-analyzer'
|
4
|
+
require 'simple_option_parser'
|
5
|
+
|
6
|
+
options = SimpleOptionParser.parse(ARGV,
|
7
|
+
domain: nil,
|
8
|
+
ignored_cities: nil,
|
9
|
+
ignored_browsers: nil,
|
10
|
+
ignored_referers: nil,
|
11
|
+
ignored_paths: nil,
|
12
|
+
period: nil,
|
13
|
+
)
|
14
|
+
|
15
|
+
%i{ignored_cities ignored_browsers ignored_referers ignored_paths}.each do |key|
|
16
|
+
if (file = options[key])
|
17
|
+
options[key] = HTTPLogAnalyzer::Importer.parse_list(File.read(file))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
importer = HTTPLogAnalyzer::Importer.new(options)
|
22
|
+
|
23
|
+
last_filename = nil
|
24
|
+
file_size = nil
|
25
|
+
ARGF.each_line do |line|
|
26
|
+
if ARGF.filename != last_filename
|
27
|
+
last_filename = ARGF.filename
|
28
|
+
if ARGF.filename == '-'
|
29
|
+
file_size = 1
|
30
|
+
else
|
31
|
+
file_size = File.size(ARGF.filename)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
STDERR.print "%-20s %6d %3d%%\r" % [
|
35
|
+
ARGF.filename,
|
36
|
+
ARGF.lineno,
|
37
|
+
ARGF.filename == '-' ? 100 : ((ARGF.pos.to_f / file_size) * 100).ceil,
|
38
|
+
]
|
39
|
+
begin
|
40
|
+
importer.process_line(ARGF.filename, ARGF.lineno, line)
|
41
|
+
rescue HTTPLogAnalyzer::ParseError => e
|
42
|
+
warn "#{e}: #{ARGF.filename}:#{ARGF.lineno}: #{line}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
STDERR.puts ' ' * ENV['COLUMNS'].to_i
|
46
|
+
|
47
|
+
importer.report
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'lib/http-log-analyzer/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'http-log-analyzer'
|
5
|
+
s.version = HTTPLogAnalyzer::VERSION
|
6
|
+
s.author = 'John Labovitz'
|
7
|
+
s.email = 'johnl@johnlabovitz.com'
|
8
|
+
|
9
|
+
s.summary = %q{Analyze HTTP log files.}
|
10
|
+
# s.description = %q{}
|
11
|
+
s.homepage = 'http://github.com/jslabovitz/http-log-analyzer'
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
17
|
+
s.require_path = 'lib'
|
18
|
+
|
19
|
+
s.add_dependency 'addressable', '~> 2.5'
|
20
|
+
s.add_dependency 'geoip', '~> 1.6'
|
21
|
+
s.add_dependency 'http-log-parser', '~> 0'
|
22
|
+
s.add_dependency 'mime-types', '~> 3.1'
|
23
|
+
s.add_dependency 'simple_option_parser', '~> 0.3'
|
24
|
+
s.add_dependency 'user_agent_parser', '~> 2.4'
|
25
|
+
|
26
|
+
s.add_development_dependency 'rake', '~> 12.3'
|
27
|
+
s.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'net/http/status'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
require 'http_log_parser'
|
5
|
+
require 'user_agent_parser'
|
6
|
+
require 'addressable/uri'
|
7
|
+
require 'mime/types'
|
8
|
+
require 'geoip'
|
9
|
+
|
10
|
+
module HTTPLogAnalyzer
|
11
|
+
|
12
|
+
class ParseError < Exception; end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'http-log-analyzer/entry'
|
17
|
+
require 'http-log-analyzer/importer'
|
18
|
+
require 'http-log-analyzer/stats'
|
19
|
+
require 'http-log-analyzer/element'
|
20
|
+
require 'http-log-analyzer/element/referer'
|
21
|
+
require 'http-log-analyzer/element/request'
|
22
|
+
require 'http-log-analyzer/element/source'
|
23
|
+
require 'http-log-analyzer/element/status'
|
24
|
+
require 'http-log-analyzer/element/user_agent'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Element
|
4
|
+
|
5
|
+
def self.parse(string)
|
6
|
+
$cache ||= {}
|
7
|
+
$cache[self] ||= {}
|
8
|
+
$cache[self][string] ||= new.tap { |e| e.parse(string) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def normalize_uri!(uri)
|
12
|
+
uri.normalize!
|
13
|
+
uri.scheme = 'http' if uri.scheme == 'https'
|
14
|
+
uri.host = uri.host.downcase.sub(/^(www|m)\./, '').sub(/\.$/, '') if uri.host
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Element
|
4
|
+
|
5
|
+
class Referer < Element
|
6
|
+
|
7
|
+
QueryKeys = %w{q p searchfor wd}
|
8
|
+
|
9
|
+
attr_accessor :uri
|
10
|
+
attr_accessor :query
|
11
|
+
|
12
|
+
def parse(string)
|
13
|
+
if string != '-'
|
14
|
+
@uri = Addressable::URI.parse(string) or raise ParseError, "Can't parse URI: #{string}"
|
15
|
+
normalize_uri!(@uri)
|
16
|
+
if @uri.host
|
17
|
+
# normalize Facebook link-shims
|
18
|
+
if @uri.host =~ /^(l|lm)\.facebook\.com$/ || @uri.path.start_with?('/l.php')
|
19
|
+
@uri.host = 'facebook.com'
|
20
|
+
@uri.path = '/'
|
21
|
+
@uri.query = nil
|
22
|
+
end
|
23
|
+
if (values = @uri.query_values)
|
24
|
+
QueryKeys.each do |key|
|
25
|
+
if (value = values[key]) && !value.empty?
|
26
|
+
@query = values[key]
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Element
|
4
|
+
|
5
|
+
class Request < Element
|
6
|
+
|
7
|
+
attr_accessor :uri
|
8
|
+
attr_accessor :mime_types
|
9
|
+
|
10
|
+
def parse(string)
|
11
|
+
@method, uri_string, @version = string.split(/\s+/)
|
12
|
+
@uri = Addressable::URI.parse(uri_string) or raise ParseError, "Can't parse URI: #{uri_string}"
|
13
|
+
normalize_uri!(@uri)
|
14
|
+
@mime_types = MIME::Types.type_for(@uri.path)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Element
|
4
|
+
|
5
|
+
class Source < Element
|
6
|
+
|
7
|
+
attr_accessor :address
|
8
|
+
attr_accessor :country
|
9
|
+
attr_accessor :region
|
10
|
+
attr_accessor :city
|
11
|
+
|
12
|
+
def parse(string)
|
13
|
+
result = $geo_ip.city(string)
|
14
|
+
if result
|
15
|
+
city, region, country = %i{city_name real_region_name country_name}.map do |key|
|
16
|
+
value = result.send(key)
|
17
|
+
value.to_s.empty? ? nil : value
|
18
|
+
end
|
19
|
+
@address = result.ip
|
20
|
+
@country = country
|
21
|
+
@region = [region, country].join(', ') if region
|
22
|
+
@city = [city, region, country].join(', ') if city
|
23
|
+
else
|
24
|
+
@ip = string
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Element
|
4
|
+
|
5
|
+
class Status < Element
|
6
|
+
|
7
|
+
attr_accessor :code
|
8
|
+
attr_accessor :message
|
9
|
+
|
10
|
+
def parse(string)
|
11
|
+
@code = string.to_i
|
12
|
+
@message = Net::HTTP::STATUS_CODES[@code]
|
13
|
+
end
|
14
|
+
|
15
|
+
def class
|
16
|
+
@code / 100
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Element
|
4
|
+
|
5
|
+
class UserAgent < Element
|
6
|
+
|
7
|
+
attr_accessor :browser
|
8
|
+
attr_accessor :system
|
9
|
+
|
10
|
+
def parse(string)
|
11
|
+
if string != '-'
|
12
|
+
user_agent = $user_agent_parser.parse(string)
|
13
|
+
@browser = case (family = user_agent.family.strip)
|
14
|
+
when nil, 'Other'
|
15
|
+
'other'
|
16
|
+
else
|
17
|
+
family
|
18
|
+
end
|
19
|
+
@system = case (name = user_agent.os.name.strip)
|
20
|
+
when /^Windows\s/
|
21
|
+
'Windows'
|
22
|
+
when /^Mac OS/i
|
23
|
+
'macOS'
|
24
|
+
when 'Other', nil
|
25
|
+
'other'
|
26
|
+
when 'Ubuntu', 'Fedora', 'SUSE'
|
27
|
+
'Linux'
|
28
|
+
else
|
29
|
+
name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Entry
|
4
|
+
|
5
|
+
attr_accessor :timestamp
|
6
|
+
attr_accessor :source
|
7
|
+
attr_accessor :request
|
8
|
+
attr_accessor :status
|
9
|
+
attr_accessor :referer
|
10
|
+
attr_accessor :user_agent
|
11
|
+
|
12
|
+
def initialize(info)
|
13
|
+
# 12/Feb/2016:09:59:04 +0000
|
14
|
+
@timestamp = DateTime.strptime(info[:datetime], '%d/%b/%Y:%H:%M:%S %Z')
|
15
|
+
@source = Element::Source.parse(info[:ip])
|
16
|
+
@request = Element::Request.parse(info[:request])
|
17
|
+
@status = Element::Status.parse(info[:status])
|
18
|
+
@referer = Element::Referer.parse(info[:referer])
|
19
|
+
@user_agent = Element::UserAgent.parse(info[:user_agent])
|
20
|
+
end
|
21
|
+
|
22
|
+
def calendar_week
|
23
|
+
'%s - %s' % [1, 7].map { |d| DateTime.commercial(@timestamp.year, @timestamp.cweek, d).to_date }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Importer
|
4
|
+
|
5
|
+
StatusKeys = {
|
6
|
+
4 => :client_error_statuses,
|
7
|
+
5 => :server_error_statuses,
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(
|
11
|
+
domain:,
|
12
|
+
ignored_ips: nil,
|
13
|
+
ignored_cities: nil,
|
14
|
+
ignored_browsers: nil,
|
15
|
+
ignored_referers: nil,
|
16
|
+
ignored_paths: nil,
|
17
|
+
period: nil
|
18
|
+
)
|
19
|
+
@domain = domain
|
20
|
+
@ignored_ips = ignored_ips || {}
|
21
|
+
@ignored_cities = ignored_cities || {}
|
22
|
+
@ignored_browsers = ignored_browsers || {}
|
23
|
+
@ignored_referers = ignored_referers || {}
|
24
|
+
@ignored_paths = ignored_paths || {}
|
25
|
+
@period = period ? parse_period(period) : nil
|
26
|
+
@log_parser = HttpLogParser.new
|
27
|
+
$user_agent_parser = UserAgentParser::Parser.new
|
28
|
+
$geo_ip = GeoIP.new(File.expand_path('/usr/local/var/GeoIP/GeoLiteCity.dat'))
|
29
|
+
@stats = Stats.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_period(period)
|
33
|
+
Range.new(*period.split(' - ', 2).map { |d| DateTime.parse(d) })
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_line(file, line_num, line)
|
37
|
+
begin
|
38
|
+
data = @log_parser.parse_line(line)
|
39
|
+
rescue => e
|
40
|
+
raise ParseError, "Can't parse line: #{line}"
|
41
|
+
end
|
42
|
+
entry = Entry.new(data)
|
43
|
+
if @period && !@period.cover?(entry.timestamp)
|
44
|
+
# ignore timestamp out of specified period
|
45
|
+
elsif ignore?(entry)
|
46
|
+
@ignored_ips[entry.source.address] = true
|
47
|
+
elsif page?(entry)
|
48
|
+
if (statuses_key = StatusKeys[entry.status.class])
|
49
|
+
@stats.add(statuses_key, "#{entry.status.code}: #{entry.request.uri.path}")
|
50
|
+
end
|
51
|
+
@stats.add(:source_country, entry.source.country)
|
52
|
+
@stats.add(:source_region, entry.source.region)
|
53
|
+
@stats.add(:source_city, entry.source.city)
|
54
|
+
@stats.add(:wv_source_city, entry.source.city) if entry.source.region == 'West Virginia, United States'
|
55
|
+
@stats.add(:pages, entry.request.uri.path)
|
56
|
+
@stats.add(:via, entry.referer.uri) unless entry.referer&.uri&.host == @domain
|
57
|
+
@stats.add(:searches, entry.referer.query) if entry.referer&.query
|
58
|
+
@stats.add(:browsers, entry.user_agent.browser)
|
59
|
+
@stats.add(:systems, entry.user_agent.system)
|
60
|
+
@stats.add(:dates, entry.calendar_week)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def ignore?(entry)
|
65
|
+
@ignored_ips[entry.source.address] ||
|
66
|
+
@ignored_browsers[entry.user_agent.browser] ||
|
67
|
+
@ignored_cities[entry.source.city] ||
|
68
|
+
(entry.referer&.uri && @ignored_referers[entry.referer&.uri&.host]) ||
|
69
|
+
@ignored_paths[entry.request.uri.path]
|
70
|
+
end
|
71
|
+
|
72
|
+
def page?(entry)
|
73
|
+
entry.request.uri.path !~ %r{/.*?https?://} && # bad URL construction (from bot)
|
74
|
+
!entry.request.uri.path.empty? &&
|
75
|
+
entry.request.mime_types.empty? # pages have empty MIME types
|
76
|
+
end
|
77
|
+
|
78
|
+
def report
|
79
|
+
@stats.report
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.parse_list(text)
|
83
|
+
Hash[
|
84
|
+
text.split(/\n/).map { |s| s.sub(/#.*/, '').strip }.reject(&:empty?).map { |s| [s, true] }
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module HTTPLogAnalyzer
|
2
|
+
|
3
|
+
class Stats
|
4
|
+
|
5
|
+
Sections = {
|
6
|
+
via: %q{Top 20 sites people came from (aka 'referers')},
|
7
|
+
searches: %q{Searches},
|
8
|
+
source_country: %q{Top 20 countries visitors came from},
|
9
|
+
source_region: %q{Top 20 regions visitors came from},
|
10
|
+
source_city: %q{Top 20 cities visitors came from},
|
11
|
+
wv_source_city: %q{Top 20 WV cities visitors came from},
|
12
|
+
browsers: %q{Top 20 browsers of visitors},
|
13
|
+
systems: %q{Top 20 operating systems of visitors},
|
14
|
+
pages: %q{Top 20 pages visited at our site},
|
15
|
+
informational_statuses: %q{Informational: request received, continuing process},
|
16
|
+
success_statuses: %q{Success: action successfully received, understood, accepted},
|
17
|
+
redirection_statuses: %q{Redirection: further action must be taken in order to complete the request},
|
18
|
+
client_error_statuses: %q{Client error: request contains bad syntax or cannot be fulfilled},
|
19
|
+
server_error_statuses: %q{Server error: server failed to fulfill an apparently valid request},
|
20
|
+
dates: %q{Visitors per week},
|
21
|
+
}
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@stats = Hash[ Sections.keys.map { |k| [k, {}] } ]
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(key, value)
|
28
|
+
if value
|
29
|
+
table = @stats[key] or raise "No stats for key #{key.inspect}"
|
30
|
+
table[value] ||= 0
|
31
|
+
table[value] += 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def report
|
36
|
+
@stats.each do |field, counts|
|
37
|
+
next if counts.empty?
|
38
|
+
puts; puts "#{Sections[field]}:"
|
39
|
+
total = counts.values.sum
|
40
|
+
if field == :dates
|
41
|
+
counts = counts.sort_by { |k, v| k }
|
42
|
+
else
|
43
|
+
counts = counts.sort_by { |k, v| v }.reverse[0..19]
|
44
|
+
end
|
45
|
+
counts.each do |label, count|
|
46
|
+
puts ' %s' % BarGraphItem.new(label: label, count: count, total: total)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class BarGraphItem
|
52
|
+
|
53
|
+
def initialize(label:, count:, total:, width: 50)
|
54
|
+
@label = label
|
55
|
+
@count = count
|
56
|
+
@total = total
|
57
|
+
@width = width
|
58
|
+
@fraction = @count.to_f / @total
|
59
|
+
end
|
60
|
+
|
61
|
+
def bar
|
62
|
+
n = (@fraction * @width).ceil.to_i
|
63
|
+
('.' * (@width - n)) + ('#' * n)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
'%s %3d%% %6d %s' % [
|
68
|
+
bar,
|
69
|
+
(@fraction * 100).ceil.to_i,
|
70
|
+
@count,
|
71
|
+
@label,
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: http-log-analyzer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.3'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Labovitz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: addressable
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: geoip
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: http-log-parser
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mime-types
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simple_option_parser
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: user_agent_parser
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.4'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '12.3'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '12.3'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubygems-tasks
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.2'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.2'
|
125
|
+
description:
|
126
|
+
email: johnl@johnlabovitz.com
|
127
|
+
executables:
|
128
|
+
- http-log-analyzer
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- Rakefile
|
134
|
+
- TODO.txt
|
135
|
+
- bin/http-log-analyzer
|
136
|
+
- http-log-analyzer.gemspec
|
137
|
+
- lib/http-log-analyzer.rb
|
138
|
+
- lib/http-log-analyzer/element.rb
|
139
|
+
- lib/http-log-analyzer/element/referer.rb
|
140
|
+
- lib/http-log-analyzer/element/request.rb
|
141
|
+
- lib/http-log-analyzer/element/source.rb
|
142
|
+
- lib/http-log-analyzer/element/status.rb
|
143
|
+
- lib/http-log-analyzer/element/user_agent.rb
|
144
|
+
- lib/http-log-analyzer/entry.rb
|
145
|
+
- lib/http-log-analyzer/importer.rb
|
146
|
+
- lib/http-log-analyzer/stats.rb
|
147
|
+
- lib/http-log-analyzer/version.rb
|
148
|
+
homepage: http://github.com/jslabovitz/http-log-analyzer
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 2.7.6
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: Analyze HTTP log files.
|
172
|
+
test_files: []
|