ntail 0.0.6 → 0.0.7

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.
@@ -0,0 +1,14 @@
1
+ module NginxTail
2
+ module ProxyAddresses
3
+
4
+ def self.included(base) # :nodoc:
5
+ base.class_eval do
6
+
7
+ # this ensures the below module methods actually make sense...
8
+ raise "Class #{base.name} should implement instance method 'proxy_addresses'" unless base.instance_methods.include? 'proxy_addresses'
9
+
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ require 'socket'
2
+
3
+ begin
4
+ require 'geoip'
5
+ rescue LoadError
6
+ # NOOP (optional dependency)
7
+ end
8
+
9
+ module NginxTail
10
+ module RemoteAddr
11
+
12
+ def self.included(base) # :nodoc:
13
+ base.class_eval do
14
+
15
+ def self.to_host_s(remote_addr)
16
+ Socket::getaddrinfo(remote_addr, nil)[0][2]
17
+ end
18
+
19
+ def self.to_country_s(remote_addr)
20
+ record = if defined? GeoIP # ie. if the optional GeoIP gem is installed
21
+ if File.exists?('/usr/share/GeoIP/GeoIP.dat') # ie. if the GeoIP country database is installed
22
+ GeoIP.new('/usr/share/GeoIP/GeoIP.dat').country(remote_addr)
23
+ end
24
+ end
25
+ record ? record[5] : 'N/A'
26
+ end
27
+
28
+ def self.to_city_s(remote_addr)
29
+ record = if defined? GeoIP # ie. if the optional GeoIP gem is installed
30
+ if File.exists?('/usr/share/GeoIP/GeoIPCity.dat') # ie. if the GeoIP city database is installed
31
+ GeoIP.new('/usr/share/GeoIP/GeoIPCity.dat').city(remote_addr)
32
+ end
33
+ end
34
+ record ? record[7] : 'N/A'
35
+ end
36
+
37
+ # this ensures the below module methods actually make sense...
38
+ raise "Class #{base.name} should implement instance method 'remote_addr'" unless base.instance_methods.include? 'remote_addr'
39
+
40
+ end
41
+ end
42
+
43
+ def to_host_s()
44
+ self.class.to_host_s(self.remote_addr)
45
+ end
46
+
47
+ def to_country_s()
48
+ self.class.to_country_s(self.remote_addr)
49
+ end
50
+
51
+ def to_city_s()
52
+ self.class.to_city_s(self.remote_addr)
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ module NginxTail
2
+ module RemoteUser
3
+
4
+ #
5
+ # to easily identify remote and authenticated users, for filtering and formatting purposes
6
+ #
7
+ # e.g. add all employees as authenticated remote users (from your webserver's .htaccess file)
8
+ #
9
+
10
+ UNKNOWN_REMOTE_USER = "-".freeze # the 'default' nginx value for the $remote_user variable
11
+
12
+ def self.included(base) # :nodoc:
13
+ base.class_eval do
14
+
15
+ @@authenticated_users = []
16
+
17
+ # mainly (solely?) for testing purposes...
18
+ def self.reset_authenticated_users
19
+ while !@@authenticated_users.empty? ; @@authenticated_users.pop ; end
20
+ end
21
+
22
+ # mainly (solely?) for testing purposes...
23
+ def self.authenticated_users
24
+ @@authenticated_users.dup
25
+ end
26
+
27
+ def self.add_authenticated_user(authenticated_user)
28
+ raise "Cannot add unkown remote user" if self.unknown_remote_user? authenticated_user
29
+ (@@authenticated_users << authenticated_user).uniq!
30
+ end
31
+
32
+ def self.unknown_remote_user?(remote_user)
33
+ remote_user == UNKNOWN_REMOTE_USER
34
+ end
35
+
36
+ def self.remote_user?(remote_user)
37
+ !self.unknown_remote_user?(remote_user)
38
+ end
39
+
40
+ def self.authenticated_user?(remote_user)
41
+ self.remote_user?(remote_user) && @@authenticated_users.include?(remote_user)
42
+ end
43
+
44
+ # this ensures the below module methods actually make sense...
45
+ raise "Class #{base.name} should implement instance method 'remote_user'" unless base.instance_methods.include? 'remote_user'
46
+
47
+ end
48
+ end
49
+
50
+ def unknown_remote_user?
51
+ self.class.unknown_remote_user?(self.remote_user)
52
+ end
53
+
54
+ def remote_user?
55
+ self.class.remote_user?(self.remote_user)
56
+ end
57
+
58
+ def authenticated_user?
59
+ self.class.authenticated_user?(self.remote_user)
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,33 @@
1
+ module NginxTail
2
+ module Request
3
+
4
+ UNKNOWN_REQUEST = "-".freeze # the 'default' nginx value for $request variable
5
+
6
+ def self.included(base) # :nodoc:
7
+ base.class_eval do
8
+
9
+ def self.unknown_request?(request)
10
+ request == UNKNOWN_REQUEST
11
+ end
12
+
13
+ # this ensures the below module methods actually make sense...
14
+ raise "Class #{base.name} should implement instance method 'request'" unless base.instance_methods.include? 'request'
15
+
16
+ end
17
+ end
18
+
19
+ def unknown_request?
20
+ self.class.unknown_request?(self.request)
21
+ end
22
+
23
+ def to_request_s
24
+ if self.unknown_request?
25
+ self.request
26
+ else
27
+ # note: we exclude the HTTP version info...
28
+ "%s %s" % [self.to_http_method_s, self.to_uri_s]
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,68 @@
1
+ module NginxTail
2
+ module Status
3
+
4
+ NGINX_MAGIC_STATUS = '499' # ex-standard HTTP response code specific to nginx, in addition to http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
5
+
6
+ def self.included(base) # :nodoc:
7
+ base.class_eval do
8
+
9
+ # Informational 1xx
10
+ def self.information_status?(status)
11
+ # (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_TO_OBJ[status.to_s] <= Net::HTTPInformation
12
+ (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_CLASS_TO_OBJ[(status.to_i / 100).to_s] == Net::HTTPInformation
13
+ end
14
+
15
+ # Successful 2xx
16
+ def self.success_status?(status)
17
+ # (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_TO_OBJ[status.to_s] <= Net::HTTPSuccess
18
+ (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_CLASS_TO_OBJ[(status.to_i / 100).to_s] == Net::HTTPSuccess
19
+ end
20
+
21
+ # Redirection 3xx
22
+ def self.redirect_status?(status)
23
+ # (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_TO_OBJ[status.to_s] <= Net::HTTPRedirection
24
+ (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_CLASS_TO_OBJ[(status.to_i / 100).to_s] == Net::HTTPRedirection
25
+ end
26
+
27
+ # Client Error 4xx
28
+ def self.client_error_status?(status)
29
+ # (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_TO_OBJ[status.to_s] <= Net::HTTPClientError
30
+ (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_CLASS_TO_OBJ[(status.to_i / 100).to_s] == Net::HTTPClientError
31
+ end
32
+
33
+ # Internal Server Error 5xx
34
+ def self.server_error_status?(status)
35
+ # (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_TO_OBJ[status.to_s] <= Net::HTTPServerError
36
+ (status.to_s != NGINX_MAGIC_STATUS) and Net::HTTPResponse::CODE_CLASS_TO_OBJ[(status.to_i / 100).to_s] == Net::HTTPServerError
37
+ end
38
+
39
+ # this ensures the below module methods actually make sense...
40
+ raise "Class #{base.name} should implement instance method 'status'" unless base.instance_methods.include? 'status'
41
+
42
+ end
43
+ end
44
+
45
+ def information_status?
46
+ self.class.information_status?(self.status)
47
+ end
48
+
49
+ def success_status?
50
+ self.class.success_status?(self.status)
51
+ end
52
+
53
+ def redirect_status?
54
+ self.class.redirect_status?(self.status)
55
+ end
56
+
57
+ def client_error_status?
58
+ self.class.client_error_status?(self.status)
59
+ end
60
+
61
+ def server_error_status?
62
+ self.class.server_error_status?(self.status)
63
+ end
64
+
65
+ end
66
+ end
67
+
68
+
@@ -0,0 +1,38 @@
1
+ require 'date'
2
+
3
+ module NginxTail
4
+ module TimeLocal
5
+
6
+ def self.included(base) # :nodoc:
7
+ base.class_eval do
8
+
9
+ # >> DateTime.strptime("13/Apr/2010:04:45:51 +0100", '%d/%b/%Y:%T %z').to_s
10
+ # => "2010-04-13T04:45:51+01:00"
11
+ # >> DateTime.strptime("13/Apr/2010:04:45:51 +0100", '%d/%b/%Y:%H:%M:%S %z').to_s
12
+ # => "2010-04-13T04:45:51+01:00"
13
+ # >> _
14
+
15
+ def self.to_date(time_local)
16
+ DateTime.strptime(time_local, '%d/%b/%Y:%T %z')
17
+ end
18
+
19
+ def self.to_date_s(time_local, format = "%Y-%m-%d %X")
20
+ self.to_date(time_local).strftime(format)
21
+ end
22
+
23
+ # this ensures the below module methods actually make sense...
24
+ raise "Class #{base.name} should implement instance method 'time_local'" unless base.instance_methods.include? 'time_local'
25
+
26
+ end
27
+ end
28
+
29
+ def to_date
30
+ self.class.to_date(self.time_local)
31
+ end
32
+
33
+ def to_date_s
34
+ self.class.to_date_s(self.time_local)
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ module NginxTail
2
+ module Uri
3
+
4
+ def self.included(base) # :nodoc:
5
+ base.class_eval do
6
+
7
+ def self.to_uri_s(uri)
8
+ uri || "-" # will be nil if $request == "-" (ie. "dodgy" HTTP requests)
9
+ end
10
+
11
+ # this ensures the below module methods actually make sense...
12
+ raise "Class #{base.name} should implement instance method 'uri'" unless base.instance_methods.include? 'uri'
13
+
14
+ end
15
+ end
16
+
17
+ def to_uri_s
18
+ self.class.to_uri_s(self.uri)
19
+ end
20
+
21
+ end
22
+ end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ntail}
8
- s.version = "0.0.6"
8
+ s.version = "0.0.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Peter Vandenberk"]
12
- s.date = %q{2010-12-21}
12
+ s.date = %q{2011-01-06}
13
13
  s.default_executable = %q{ntail}
14
14
  s.description = %q{A tail(1)-like utility for nginx log files. It supports parsing, filtering and formatting individual log lines.}
15
15
  s.email = %q{pvandenberk@mac.com}
@@ -29,9 +29,34 @@ Gem::Specification.new do |s|
29
29
  "bin/ntail",
30
30
  "lib/ntail.rb",
31
31
  "lib/ntail/application.rb",
32
+ "lib/ntail/body_bytes_sent.rb",
33
+ "lib/ntail/http_method.rb",
34
+ "lib/ntail/http_referer.rb",
35
+ "lib/ntail/http_user_agent.rb",
36
+ "lib/ntail/http_version.rb",
37
+ "lib/ntail/known_ip_addresses.rb",
38
+ "lib/ntail/local_ip_addresses.rb",
32
39
  "lib/ntail/log_line.rb",
40
+ "lib/ntail/proxy_addresses.rb",
41
+ "lib/ntail/remote_addr.rb",
42
+ "lib/ntail/remote_user.rb",
43
+ "lib/ntail/request.rb",
44
+ "lib/ntail/status.rb",
45
+ "lib/ntail/time_local.rb",
46
+ "lib/ntail/uri.rb",
33
47
  "ntail.gemspec",
34
48
  "test/helper.rb",
49
+ "test/ntail/test_http_method.rb",
50
+ "test/ntail/test_http_referer.rb",
51
+ "test/ntail/test_http_user_agent.rb",
52
+ "test/ntail/test_known_ip_addresses.rb",
53
+ "test/ntail/test_local_ip_addresses.rb",
54
+ "test/ntail/test_log_line.rb",
55
+ "test/ntail/test_remote_addr.rb",
56
+ "test/ntail/test_remote_user.rb",
57
+ "test/ntail/test_request.rb",
58
+ "test/ntail/test_status.rb",
59
+ "test/ntail/test_time_local.rb",
35
60
  "test/test_ntail.rb"
36
61
  ]
37
62
  s.homepage = %q{http://github.com/pvdb/ntail}
@@ -41,6 +66,17 @@ Gem::Specification.new do |s|
41
66
  s.summary = %q{A tail(1)-like utility for nginx log files}
42
67
  s.test_files = [
43
68
  "test/helper.rb",
69
+ "test/ntail/test_http_method.rb",
70
+ "test/ntail/test_http_referer.rb",
71
+ "test/ntail/test_http_user_agent.rb",
72
+ "test/ntail/test_known_ip_addresses.rb",
73
+ "test/ntail/test_local_ip_addresses.rb",
74
+ "test/ntail/test_log_line.rb",
75
+ "test/ntail/test_remote_addr.rb",
76
+ "test/ntail/test_remote_user.rb",
77
+ "test/ntail/test_request.rb",
78
+ "test/ntail/test_status.rb",
79
+ "test/ntail/test_time_local.rb",
44
80
  "test/test_ntail.rb"
45
81
  ]
46
82
 
@@ -55,6 +91,7 @@ Gem::Specification.new do |s|
55
91
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
56
92
  s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
57
93
  s.add_development_dependency(%q<rcov>, [">= 0"])
94
+ s.add_development_dependency(%q<geoip>, [">= 0"])
58
95
  else
59
96
  s.add_dependency(%q<rainbow>, [">= 0"])
60
97
  s.add_dependency(%q<user-agent>, [">= 0"])
@@ -62,6 +99,7 @@ Gem::Specification.new do |s|
62
99
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
63
100
  s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
64
101
  s.add_dependency(%q<rcov>, [">= 0"])
102
+ s.add_dependency(%q<geoip>, [">= 0"])
65
103
  end
66
104
  else
67
105
  s.add_dependency(%q<rainbow>, [">= 0"])
@@ -70,6 +108,7 @@ Gem::Specification.new do |s|
70
108
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
71
109
  s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
72
110
  s.add_dependency(%q<rcov>, [">= 0"])
111
+ s.add_dependency(%q<geoip>, [">= 0"])
73
112
  end
74
113
  end
75
114
 
@@ -15,4 +15,77 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
15
  require 'ntail'
16
16
 
17
17
  class Test::Unit::TestCase
18
+
19
+ def random_ip_address
20
+ ((1..4).map { Kernel.rand(256) }).join('.')
21
+ end
22
+
23
+ def local_ip_address
24
+ # http://en.wikipedia.org/wiki/IP_address#IPv4_private_addresses
25
+ (['192', '168'] + (1..2).map { Kernel.rand(256) }).join('.')
26
+ end
27
+
28
+ #
29
+ # http://wiki.nginx.org/NginxHttpLogModule#log_format
30
+ #
31
+ # There is a predefined log format called "combined":
32
+ #
33
+ # log_format combined '$remote_addr - $remote_user [$time_local] '
34
+ # '"$request" $status $body_bytes_sent '
35
+ # '"$http_referer" "$http_user_agent"';
36
+ #
37
+
38
+ LOG_FORMAT_COMBINED = '%s - %s [%s] ' \
39
+ '"%s" %d %d ' \
40
+ '"%s" "%s"'
41
+
42
+ DEFAULT_VALUES = {
43
+ :remote_addr => '72.46.130.42',
44
+ :remote_user => '-',
45
+ :time_local => '01/Jan/2010:04:00:29 +0000',
46
+ # :request => 'GET /index.html HTTP/1.1',
47
+ :http_method => 'GET',
48
+ :uri => '/index.html',
49
+ :http_version => 'HTTP/1.1',
50
+ :status => 200,
51
+ :body_bytes_sent => 6918,
52
+ :http_referer => 'http://www.google.com/search?q=example',
53
+ :http_user_agent => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10'
54
+ }
55
+
56
+ REQUEST_FORMAT = '%s %s %s'
57
+
58
+ def random_raw_line(options = {})
59
+ options = DEFAULT_VALUES.merge options
60
+ options[:request] ||= REQUEST_FORMAT % [
61
+ options[:http_method],
62
+ options[:uri],
63
+ options[:http_version]
64
+ ]
65
+ LOG_FORMAT_COMBINED % [
66
+ options[:remote_addr],
67
+ options[:remote_user],
68
+ options[:time_local],
69
+ options[:request],
70
+ options[:status],
71
+ options[:body_bytes_sent],
72
+ options[:http_referer],
73
+ options[:http_user_agent],
74
+ ]
75
+ end
76
+
77
+ def random_log_line(options = {})
78
+ NginxTail::LogLine.new(random_raw_line(options))
79
+ end
80
+
81
+ def bad_request_raw_line
82
+ # a "bad request", resulting in a 400 status, is logged by nginx as follows:
83
+ # 121.8.101.138 - - [28/Dec/2010:23:50:58 +0000] "-" 400 0 "-" "-"
84
+ '121.8.101.138 - - [28/Dec/2010:23:50:58 +0000] "-" 400 0 "-" "-"'
85
+ end
86
+
87
+ def bad_request_log_line
88
+ NginxTail::LogLine.new(bad_request_raw_line)
89
+ end
90
+
18
91
  end