racknga 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/text/news.textile +12 -0
- data/lib/racknga.rb +2 -1
- data/lib/racknga/access_log_parser.rb +147 -0
- data/lib/racknga/cache_database.rb +13 -3
- data/lib/racknga/exception_mail_notifier.rb +1 -1
- data/lib/racknga/log_database.rb +1 -0
- data/lib/racknga/log_entry.rb +85 -0
- data/lib/racknga/middleware/cache.rb +2 -2
- data/lib/racknga/middleware/deflater.rb +12 -2
- data/lib/racknga/middleware/exception_notifier.rb +0 -1
- data/lib/racknga/middleware/instance_name.rb +37 -7
- data/lib/racknga/middleware/jsonp.rb +1 -0
- data/lib/racknga/middleware/log.rb +7 -2
- data/lib/racknga/middleware/nginx_raw_uri.rb +65 -0
- data/lib/racknga/version.rb +1 -1
- data/test/{test-nginx-log-parser.rb → test-access-log-parser.rb} +114 -25
- data/test/test-middleware-instance-name.rb +26 -14
- data/test/test-middleware-nginx-raw-uri.rb +70 -0
- metadata +12 -8
- data/lib/racknga/nginx_access_log_parser.rb +0 -139
data/doc/text/news.textile
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
h1. NEWS
|
2
2
|
|
3
|
+
h2. 0.9.3: 2011-11-12
|
4
|
+
|
5
|
+
h3. Improvments
|
6
|
+
|
7
|
+
* [access-log-parser] Supported Apache log.
|
8
|
+
* [cache] Fixed unknown name errors.
|
9
|
+
* [cache] Fixed max age.
|
10
|
+
* [nginx] Added NginxRawURI middleware.
|
11
|
+
* [instance-name] Added branch name and Ruby version.
|
12
|
+
* [exception-mail-notifier] Used #message instead of #to_s.
|
13
|
+
* [logger] Logged also X-Runtime.
|
14
|
+
|
3
15
|
h2. 0.9.2: 2011-08-07
|
4
16
|
|
5
17
|
h3. Improvments
|
data/lib/racknga.rb
CHANGED
@@ -20,9 +20,10 @@ require 'rack'
|
|
20
20
|
|
21
21
|
require 'racknga/version'
|
22
22
|
require 'racknga/utils'
|
23
|
-
require "racknga/
|
23
|
+
require "racknga/access_log_parser"
|
24
24
|
require 'racknga/middleware/deflater'
|
25
25
|
require 'racknga/middleware/exception_notifier'
|
26
26
|
require 'racknga/middleware/jsonp'
|
27
27
|
require 'racknga/middleware/range'
|
28
28
|
require "racknga/middleware/instance_name"
|
29
|
+
require "racknga/middleware/nginx_raw_uri"
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
|
4
|
+
# Copyright (C) 2011 Kouhei Sutou <kou@clear-code.com>
|
5
|
+
#
|
6
|
+
# This library is free software; you can redistribute it and/or
|
7
|
+
# modify it under the terms of the GNU Lesser General Public
|
8
|
+
# License as published by the Free Software Foundation; either
|
9
|
+
# version 2.1 of the License, or (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This library is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
17
|
+
# License along with this library; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
19
|
+
|
20
|
+
require "racknga/log_entry"
|
21
|
+
require "racknga/reverse_line_reader"
|
22
|
+
|
23
|
+
module Racknga
|
24
|
+
# Supported formats:
|
25
|
+
# * combined (nginx's default format)
|
26
|
+
# * combined (Apache's predefined format)
|
27
|
+
# * combined_with_time_nginx (custom format with runtime)
|
28
|
+
# * combined_with_time_apache (custom format with runtime)
|
29
|
+
#
|
30
|
+
# Configurations:
|
31
|
+
# * combined
|
32
|
+
# * nginx
|
33
|
+
# log_format combined '$remote_addr - $remote_user [$time_local] '
|
34
|
+
# '"$request" $status $body_bytes_sent '
|
35
|
+
# '"$http_referer" "$http_user_agent"';
|
36
|
+
# access_log log/access.log combined
|
37
|
+
# * Apache
|
38
|
+
# CustomLog ${APACHE_LOG_DIR}/access.log combined
|
39
|
+
#
|
40
|
+
# * combined_with_time_nginx
|
41
|
+
# * nginx
|
42
|
+
# log_format combined_with_time '$remote_addr - $remote_user '
|
43
|
+
# '[$time_local, $upstream_http_x_runtime, $request_time] '
|
44
|
+
# '"$request" $status $body_bytes_sent '
|
45
|
+
# '"$http_referer" "$http_user_agent"';
|
46
|
+
# access_log log/access.log combined_with_time
|
47
|
+
#
|
48
|
+
# * combined_with_time_apache
|
49
|
+
# * Apache
|
50
|
+
# LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %{X-Runtime}o %D" combined_with_time
|
51
|
+
# CustomLog ${APACHE_LOG_DIR}/access.log combined_with_time
|
52
|
+
class AccessLogParser
|
53
|
+
include Enumerable
|
54
|
+
|
55
|
+
class FormatError < StandardError
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(line_reader)
|
59
|
+
@line_reader = line_reader
|
60
|
+
end
|
61
|
+
|
62
|
+
def each
|
63
|
+
@line_reader.each do |line|
|
64
|
+
line.force_encoding("UTF-8")
|
65
|
+
yield(parse_line(line)) if line.valid_encoding?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
REMOTE_ADDRESS = "[^\\x20]+"
|
71
|
+
REMOTE_USER = "[^\\x20]+"
|
72
|
+
TIME_LOCAL = "[^\\x20]+\\x20\\+\\d{4}"
|
73
|
+
RUNTIME = "(?:[\\d.]+|-)"
|
74
|
+
REQUEST_TIME = "[\\d.]+"
|
75
|
+
REQUEST = ".*?"
|
76
|
+
STATUS = "\\d{3}"
|
77
|
+
BODY_BYTES_SENT = "(?:\\d+|-)"
|
78
|
+
HTTP_REFERER = ".*?"
|
79
|
+
HTTP_USER_AGENT = "(?:\\\"|[^\"])*?"
|
80
|
+
COMBINED_FORMAT =
|
81
|
+
/^(#{REMOTE_ADDRESS})\x20
|
82
|
+
-\x20
|
83
|
+
(#{REMOTE_USER})\x20
|
84
|
+
\[(#{TIME_LOCAL})(?:,\x20(.+))?\]\x20+
|
85
|
+
"(#{REQUEST})"\x20
|
86
|
+
(#{STATUS})\x20
|
87
|
+
(#{BODY_BYTES_SENT})\x20
|
88
|
+
"(#{HTTP_REFERER})"\x20
|
89
|
+
"(#{HTTP_USER_AGENT})"
|
90
|
+
(?:\x20(.+))?$/x
|
91
|
+
def parse_line(line)
|
92
|
+
case line
|
93
|
+
when COMBINED_FORMAT
|
94
|
+
last_match = Regexp.last_match
|
95
|
+
options = {}
|
96
|
+
options[:remote_address] = last_match[1]
|
97
|
+
options[:remote_user] = last_match[2]
|
98
|
+
options[:time_local] = parse_local_time(last_match[3])
|
99
|
+
if last_match[4]
|
100
|
+
if /\A(#{RUNTIME}), (#{REQUEST_TIME})\z/ =~ last_match[4]
|
101
|
+
time_match = Regexp.last_match
|
102
|
+
options[:runtime] = time_match[1]
|
103
|
+
options[:request_time] = time_match[2]
|
104
|
+
else
|
105
|
+
message = "expected 'RUNTIME, REQUEST_TIME' time format: " +
|
106
|
+
"<#{last_match[4]}>: <#{line}>"
|
107
|
+
raise FormatError.new(message)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
options[:request] = last_match[5]
|
111
|
+
options[:status] = last_match[6].to_i
|
112
|
+
options[:body_bytes_sent] = last_match[7]
|
113
|
+
options[:http_referer] = last_match[8]
|
114
|
+
options[:http_user_agent] = last_match[9]
|
115
|
+
if last_match[10]
|
116
|
+
if /\A(#{RUNTIME}) (#{REQUEST_TIME})\z/ =~ last_match[10]
|
117
|
+
time_match = Regexp.last_match
|
118
|
+
runtime = time_match[1]
|
119
|
+
request_time = time_match[2]
|
120
|
+
request_time = request_time.to_i * 0.000_001 if request_time
|
121
|
+
options[:runtime] = runtime
|
122
|
+
options[:request_time] = request_time
|
123
|
+
else
|
124
|
+
message = "expected 'RUNTIME REQUEST_TIME' time format: " +
|
125
|
+
"<#{last_match[10]}>: <#{line}>"
|
126
|
+
raise FormatError.new(message)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
LogEntry.new(options)
|
130
|
+
else
|
131
|
+
raise FormatError.new("unsupported format log entry: <#{line}>")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_local_time(token)
|
136
|
+
day, month, year, hour, minute, second, _time_zone = token.split(/[\/: ]/)
|
137
|
+
_ = _time_zone # FIXME: suppress a warning. :<
|
138
|
+
Time.local(year, month, day, hour, minute, second)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class ReversedAccessLogParser < AccessLogParser
|
143
|
+
def initialize(line_reader)
|
144
|
+
@line_reader = ReverseLineReader.new(line_reader)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -53,17 +53,27 @@ module Racknga
|
|
53
53
|
# process from your Rack application
|
54
54
|
# process. (e.g. cron.) It's multi process safe.
|
55
55
|
def purge_old_responses
|
56
|
-
age_modulo = 2 **
|
56
|
+
age_modulo = 2 ** 31 - 1
|
57
57
|
age = configuration.age
|
58
58
|
previous_age = (age - 1).modulo(age_modulo)
|
59
|
-
|
59
|
+
next_age = (age + 1).modulo(age_modulo)
|
60
60
|
|
61
61
|
target_responses = responses.select do |record|
|
62
|
-
record.age ==
|
62
|
+
record.age == next_age
|
63
63
|
end
|
64
64
|
target_responses.each do |response|
|
65
65
|
response.key.delete
|
66
66
|
end
|
67
|
+
configuration.age = next_age
|
68
|
+
|
69
|
+
target_responses = responses.select do |record|
|
70
|
+
record.age <= previous_age
|
71
|
+
end
|
72
|
+
target_responses.each do |response|
|
73
|
+
response.key.delete
|
74
|
+
end
|
75
|
+
|
76
|
+
responses.defrag if responses.respond_to?(:defrag)
|
67
77
|
end
|
68
78
|
|
69
79
|
def ensure_database
|
data/lib/racknga/log_database.rb
CHANGED
@@ -0,0 +1,85 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
|
4
|
+
# Copyright (C) 2011 Kouhei Sutou <kou@clear-code.com>
|
5
|
+
#
|
6
|
+
# This library is free software; you can redistribute it and/or
|
7
|
+
# modify it under the terms of the GNU Lesser General Public
|
8
|
+
# License as published by the Free Software Foundation; either
|
9
|
+
# version 2.1 of the License, or (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This library is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
17
|
+
# License along with this library; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
19
|
+
|
20
|
+
module Racknga
|
21
|
+
class LogEntry
|
22
|
+
ATTRIBUTES = [
|
23
|
+
:remote_address,
|
24
|
+
:remote_user,
|
25
|
+
:time_local,
|
26
|
+
:runtime,
|
27
|
+
:request_time,
|
28
|
+
:request,
|
29
|
+
:status,
|
30
|
+
:body_bytes_sent,
|
31
|
+
:http_referer,
|
32
|
+
:http_user_agent,
|
33
|
+
]
|
34
|
+
|
35
|
+
attr_reader(*ATTRIBUTES)
|
36
|
+
def initialize(options=nil)
|
37
|
+
options ||= {}
|
38
|
+
@remote_address = options[:remote_address]
|
39
|
+
@remote_user = normalize_string_value(options[:remote_user])
|
40
|
+
@time_local = options[:time_local] || Time.at(0)
|
41
|
+
@runtime = normalize_float_value(options[:runtime])
|
42
|
+
@request_time = normalize_float_value(options[:request_time])
|
43
|
+
@request = options[:request]
|
44
|
+
@status = options[:status]
|
45
|
+
@body_bytes_sent = normalize_int_value(options[:body_bytes_sent])
|
46
|
+
@http_referer = normalize_string_value(options[:http_referer])
|
47
|
+
@http_user_agent = normalize_string_value(options[:http_user_agent])
|
48
|
+
end
|
49
|
+
|
50
|
+
def attributes
|
51
|
+
ATTRIBUTES.collect do |attribute|
|
52
|
+
__send__(attribute)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(other)
|
57
|
+
other.is_a?(self.class) and attributes == other.attributes
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def normalize_string_value(value)
|
62
|
+
if value.nil? or value == "-"
|
63
|
+
nil
|
64
|
+
else
|
65
|
+
value.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_float_value(value)
|
70
|
+
if value.nil?
|
71
|
+
value
|
72
|
+
else
|
73
|
+
value.to_f
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def normalize_int_value(value)
|
78
|
+
if value.nil? or value == "-"
|
79
|
+
nil
|
80
|
+
else
|
81
|
+
value.to_i
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -221,9 +221,9 @@ module Racknga
|
|
221
221
|
def log(tag, request)
|
222
222
|
return unless Middleware.const_defined?(:Log)
|
223
223
|
env = request.env
|
224
|
-
logger = env[Middleware::Log::
|
224
|
+
logger = env[Middleware::Log::LOGGER]
|
225
225
|
return if logger.nil?
|
226
|
-
start_time = env[
|
226
|
+
start_time = env[START_TIME]
|
227
227
|
runtime = Time.now - start_time
|
228
228
|
logger.log("cache-#{tag}", request.fullpath, :runtime => runtime)
|
229
229
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
#
|
3
|
-
# Copyright (C) 2010 Kouhei Sutou <kou@clear-code.com>
|
3
|
+
# Copyright (C) 2010-2011 Kouhei Sutou <kou@clear-code.com>
|
4
4
|
#
|
5
5
|
# This library is free software; you can redistribute it and/or
|
6
6
|
# modify it under the terms of the GNU Lesser General Public
|
@@ -63,7 +63,7 @@ module Racknga
|
|
63
63
|
|
64
64
|
# For Rack.
|
65
65
|
def call(environment)
|
66
|
-
if ie6?(environment)
|
66
|
+
if ie6?(environment) or not valid_accept_encoding?(environment)
|
67
67
|
@application.call(environment)
|
68
68
|
else
|
69
69
|
@deflater.call(environment)
|
@@ -74,6 +74,16 @@ module Racknga
|
|
74
74
|
def ie6?(environment)
|
75
75
|
/MSIE 6.0;/ =~ (environment["HTTP_USER_AGENT"] || '')
|
76
76
|
end
|
77
|
+
|
78
|
+
def valid_accept_encoding?(environment)
|
79
|
+
request = Rack::Request.new(environment)
|
80
|
+
begin
|
81
|
+
request.accept_encoding
|
82
|
+
true
|
83
|
+
rescue
|
84
|
+
false
|
85
|
+
end
|
86
|
+
end
|
77
87
|
end
|
78
88
|
end
|
79
89
|
end
|
@@ -68,6 +68,23 @@ module Racknga
|
|
68
68
|
`id --user --name`.strip
|
69
69
|
end
|
70
70
|
|
71
|
+
CURRENT_BRANCH_MARKER = /\A\* /
|
72
|
+
def branch
|
73
|
+
current_branch = nil
|
74
|
+
`git branch -a`.each_line do |line|
|
75
|
+
case line
|
76
|
+
when CURRENT_BRANCH_MARKER
|
77
|
+
current_branch = line.sub(CURRENT_BRANCH_MARKER, "").strip
|
78
|
+
break
|
79
|
+
end
|
80
|
+
end
|
81
|
+
current_branch
|
82
|
+
end
|
83
|
+
|
84
|
+
def ruby
|
85
|
+
RUBY_DESCRIPTION
|
86
|
+
end
|
87
|
+
|
71
88
|
private
|
72
89
|
DEFAULT_HEADER_NAME = "X-Responsed-By"
|
73
90
|
def header_name
|
@@ -83,9 +100,10 @@ module Racknga
|
|
83
100
|
def construct_header
|
84
101
|
format_header(format_application_name(application_name),
|
85
102
|
format_version(version),
|
86
|
-
format_revision(revision),
|
103
|
+
format_revision(branch, revision),
|
87
104
|
format_server(server),
|
88
|
-
format_user(user)
|
105
|
+
format_user(user),
|
106
|
+
format_ruby(ruby))
|
89
107
|
end
|
90
108
|
|
91
109
|
def format_header(*arguments)
|
@@ -104,9 +122,15 @@ module Racknga
|
|
104
122
|
end
|
105
123
|
end
|
106
124
|
|
107
|
-
def format_revision(revision)
|
125
|
+
def format_revision(branch, revision)
|
108
126
|
format_if_possible(revision) do
|
109
|
-
"(at #{revision})"
|
127
|
+
"(at #{revision}#{format_branch(branch)})"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def format_branch(branch)
|
132
|
+
format_if_possible(branch) do
|
133
|
+
" (#{branch})"
|
110
134
|
end
|
111
135
|
end
|
112
136
|
|
@@ -122,12 +146,18 @@ module Racknga
|
|
122
146
|
end
|
123
147
|
end
|
124
148
|
|
149
|
+
def format_ruby(ruby)
|
150
|
+
format_if_possible(ruby) do
|
151
|
+
"with #{ruby}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
125
155
|
def format_if_possible(data)
|
126
156
|
if data and (data.respond_to?(:to_s) and not data.to_s.empty?)
|
127
|
-
|
157
|
+
yield
|
158
|
+
else
|
159
|
+
nil
|
128
160
|
end
|
129
|
-
|
130
|
-
result
|
131
161
|
end
|
132
162
|
end
|
133
163
|
end
|
@@ -125,6 +125,7 @@ module Racknga
|
|
125
125
|
def update_content_type(header_hash)
|
126
126
|
content_type = header_hash["Content-Type"]
|
127
127
|
media_type, parameters = content_type.split(/\s*;\s*/, 2)
|
128
|
+
_ = media_type # FIXME: suppress a warning. :<
|
128
129
|
# We should use application/javascript not
|
129
130
|
# text/javascript when all IE <= 8 are deprecated. :<
|
130
131
|
updated_content_type = ["text/javascript", parameters].compact.join("; ")
|
@@ -70,9 +70,12 @@ module Racknga
|
|
70
70
|
private
|
71
71
|
def log(start_time, end_time, request, status, headers, body)
|
72
72
|
request_time = end_time - start_time
|
73
|
+
runtime = headers["X-Runtime"]
|
74
|
+
runtime_in_float = nil
|
75
|
+
runtime_in_float = runtime.to_f if runtime
|
73
76
|
length = headers["Content-Length"] || "-"
|
74
77
|
length = "-" if length == "0"
|
75
|
-
format = "%s - %s [%s] \"%s %s %s\" %s %s \"%s\" \"%s\" %0.8f"
|
78
|
+
format = "%s - %s [%s] \"%s %s %s\" %s %s \"%s\" \"%s\" %s %0.8f"
|
76
79
|
message = format % [request.ip || "-",
|
77
80
|
request.env["REMOTE_USER"] || "-",
|
78
81
|
end_time.dup.utc.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
@@ -83,12 +86,14 @@ module Racknga
|
|
83
86
|
length,
|
84
87
|
request.env["HTTP_REFERER"] || "-",
|
85
88
|
request.user_agent || "-",
|
89
|
+
runtime || "-",
|
86
90
|
request_time]
|
87
91
|
@logger.log("access",
|
88
92
|
request.fullpath,
|
89
93
|
:message => message,
|
90
94
|
:user_agent => request.user_agent,
|
91
|
-
:runtime =>
|
95
|
+
:runtime => runtime_in_float,
|
96
|
+
:request_time => request_time)
|
92
97
|
end
|
93
98
|
|
94
99
|
# @private
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License as published by the Free Software Foundation; either
|
8
|
+
# version 2.1 of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This library is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
# Lesser General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Lesser General Public
|
16
|
+
# License along with this library; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
18
|
+
|
19
|
+
module Racknga
|
20
|
+
module Middleware
|
21
|
+
# NOTE:
|
22
|
+
# This is a middleware that restores the unprocessed URI client requests
|
23
|
+
# as is. Usually, nginx-passenger stack unescapes percent encoding in URI
|
24
|
+
# and resolve relative paths (ie "." and "..").
|
25
|
+
# Most of time, processed URI isn't program. However, if you want to
|
26
|
+
# distinguish %2F (ie "/") from "/", it is.
|
27
|
+
#
|
28
|
+
# Passenger 3.x or later is required.
|
29
|
+
#
|
30
|
+
# Use this with following nginx configuration:
|
31
|
+
#
|
32
|
+
# ... {
|
33
|
+
# ...
|
34
|
+
# passenger_set_cgi_param HTTP_X_RAW_REQUEST_URI $request_uri;
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# Usage:
|
38
|
+
# require "racknga"
|
39
|
+
# use Racknga::Middleware::NginxRawURI
|
40
|
+
# run YourApplication
|
41
|
+
class NginxRawURI
|
42
|
+
RAW_REQUEST_URI_HEADER_NAME = "HTTP_X_RAW_REQUEST_URI"
|
43
|
+
def initialize(application)
|
44
|
+
@application = application
|
45
|
+
end
|
46
|
+
|
47
|
+
# For Rack.
|
48
|
+
def call(environment)
|
49
|
+
raw_uri = environment[RAW_REQUEST_URI_HEADER_NAME]
|
50
|
+
|
51
|
+
if raw_uri
|
52
|
+
restore_raw_uri(environment, raw_uri)
|
53
|
+
end
|
54
|
+
|
55
|
+
@application.call(environment)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def restore_raw_uri(environment, raw_uri)
|
60
|
+
environment["PATH_INFO"] = raw_uri.split("?").first
|
61
|
+
environment["REQUEST_URI"] = raw_uri
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/racknga/version.rb
CHANGED
@@ -19,30 +19,33 @@
|
|
19
19
|
|
20
20
|
require 'stringio'
|
21
21
|
|
22
|
-
module
|
22
|
+
module AccessLogParserTests
|
23
23
|
module Data
|
24
24
|
private
|
25
|
-
def
|
26
|
-
|
27
|
-
|
25
|
+
def time_local
|
26
|
+
"03/Aug/2011:16:58:01 +0900"
|
27
|
+
end
|
28
|
+
|
29
|
+
def log_line(content)
|
30
|
+
content
|
28
31
|
end
|
29
32
|
|
30
33
|
def usual_log_line
|
31
|
-
"127.0.0.1 - - #{time_log_component} " +
|
32
|
-
|
34
|
+
log_line("127.0.0.1 - - #{time_log_component} " +
|
35
|
+
"\"GET / HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
|
33
36
|
end
|
34
37
|
|
35
38
|
def usual_log_entry_options
|
36
39
|
{
|
37
40
|
:remote_address => "127.0.0.1",
|
38
|
-
:remote_user =>
|
41
|
+
:remote_user => nil,
|
39
42
|
:time_local => Time.local(2011, 8, 3, 16, 58, 1),
|
40
43
|
:runtime => runtime,
|
41
44
|
:request_time => request_time,
|
42
45
|
:request => "GET / HTTP/1.1",
|
43
46
|
:status => 200,
|
44
47
|
:body_bytes_sent => 613,
|
45
|
-
:http_referer =>
|
48
|
+
:http_referer => nil,
|
46
49
|
:http_user_agent => "Ruby",
|
47
50
|
}
|
48
51
|
end
|
@@ -52,8 +55,8 @@ module NginxAccessLogParserTests
|
|
52
55
|
end
|
53
56
|
|
54
57
|
def not_found_log_line
|
55
|
-
"127.0.0.1 - - #{time_log_component} " +
|
56
|
-
|
58
|
+
log_line("127.0.0.1 - - #{time_log_component} " +
|
59
|
+
"\"GET /the-truth.html HTTP/1.1\" 404 613 \"-\" \"Ruby\"")
|
57
60
|
end
|
58
61
|
|
59
62
|
def not_found_log_entry
|
@@ -71,8 +74,8 @@ module NginxAccessLogParserTests
|
|
71
74
|
def valid_utf8_log_line
|
72
75
|
path = utf8_path
|
73
76
|
|
74
|
-
"127.0.0.1 - - #{time_log_component} " +
|
75
|
-
|
77
|
+
log_line("127.0.0.1 - - #{time_log_component} " +
|
78
|
+
"\"GET #{path} HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
|
76
79
|
end
|
77
80
|
|
78
81
|
def valid_utf8_log_entry
|
@@ -90,8 +93,41 @@ module NginxAccessLogParserTests
|
|
90
93
|
def invalid_utf8_log_line
|
91
94
|
path = garbled_path
|
92
95
|
|
93
|
-
"127.0.0.1 - - #{time_log_component} " +
|
94
|
-
|
96
|
+
log_line("127.0.0.1 - - #{time_log_component} " +
|
97
|
+
"\"GET #{path} HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
|
98
|
+
end
|
99
|
+
|
100
|
+
def ipv6_log_line
|
101
|
+
log_line("::1 - - #{time_log_component} " +
|
102
|
+
"\"GET / HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
|
103
|
+
end
|
104
|
+
|
105
|
+
def ipv6_log_entry
|
106
|
+
options = {
|
107
|
+
:remote_address => "::1",
|
108
|
+
}
|
109
|
+
create_log_entry(usual_log_entry_options.merge(options))
|
110
|
+
end
|
111
|
+
|
112
|
+
def apache_combined_log_line
|
113
|
+
log_line("127.0.0.1 - - #{time_log_component} " +
|
114
|
+
"\"GET / HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
|
115
|
+
end
|
116
|
+
|
117
|
+
def apache_combined_log_entry
|
118
|
+
usual_log_entry
|
119
|
+
end
|
120
|
+
|
121
|
+
def no_body_bytes_sent_log_line
|
122
|
+
log_line("127.0.0.1 - - #{time_log_component} " +
|
123
|
+
"\"GET / HTTP/1.1\" 200 - \"-\" \"Ruby\"")
|
124
|
+
end
|
125
|
+
|
126
|
+
def no_body_bytes_sent_log_entry
|
127
|
+
options = {
|
128
|
+
:body_bytes_sent => nil,
|
129
|
+
}
|
130
|
+
create_log_entry(usual_log_entry_options.merge(options))
|
95
131
|
end
|
96
132
|
|
97
133
|
def bad_log_line
|
@@ -110,11 +146,11 @@ module NginxAccessLogParserTests
|
|
110
146
|
end
|
111
147
|
|
112
148
|
def create_log_parser(file)
|
113
|
-
Racknga::
|
149
|
+
Racknga::AccessLogParser.new(file)
|
114
150
|
end
|
115
151
|
|
116
152
|
def create_reversed_log_parser(file)
|
117
|
-
Racknga::
|
153
|
+
Racknga::ReversedAccessLogParser.new(file)
|
118
154
|
end
|
119
155
|
|
120
156
|
def join_lines(*lines)
|
@@ -137,18 +173,36 @@ module NginxAccessLogParserTests
|
|
137
173
|
end
|
138
174
|
|
139
175
|
module Tests
|
140
|
-
def
|
176
|
+
def test_usual_log
|
141
177
|
accesses = parse(join_lines(usual_log_line))
|
142
|
-
|
143
|
-
|
178
|
+
assert_equal([usual_log_entry],
|
179
|
+
accesses)
|
144
180
|
end
|
145
181
|
|
146
|
-
def
|
182
|
+
def test_ipv6_log
|
183
|
+
accesses = parse(join_lines(ipv6_log_line))
|
184
|
+
assert_equal([ipv6_log_entry],
|
185
|
+
accesses)
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_apache_combined_log
|
189
|
+
accesses = parse(join_lines(apache_combined_log_line))
|
190
|
+
assert_equal([apache_combined_log_entry],
|
191
|
+
accesses)
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_no_body_bytes_sent_log
|
195
|
+
accesses = parse(join_lines(no_body_bytes_sent_log_line))
|
196
|
+
assert_equal([no_body_bytes_sent_log_entry],
|
197
|
+
accesses)
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_no_log
|
147
201
|
accesses = parse(join_lines())
|
148
202
|
assert_equal([], accesses)
|
149
203
|
end
|
150
204
|
|
151
|
-
def
|
205
|
+
def test_multiple_logs
|
152
206
|
accesses = parse(join_lines(usual_log_line,
|
153
207
|
usual_log_line,
|
154
208
|
not_found_log_line))
|
@@ -158,7 +212,7 @@ module NginxAccessLogParserTests
|
|
158
212
|
accesses)
|
159
213
|
end
|
160
214
|
|
161
|
-
def
|
215
|
+
def test_reversed_parse
|
162
216
|
accesses = reversed_parse(join_lines(usual_log_line,
|
163
217
|
usual_log_line,
|
164
218
|
not_found_log_line))
|
@@ -168,8 +222,8 @@ module NginxAccessLogParserTests
|
|
168
222
|
accesses)
|
169
223
|
end
|
170
224
|
|
171
|
-
def
|
172
|
-
assert_raise(Racknga::
|
225
|
+
def test_bad_log
|
226
|
+
assert_raise(Racknga::AccessLogParser::FormatError) do
|
173
227
|
parse(join_lines(bad_log_line))
|
174
228
|
end
|
175
229
|
end
|
@@ -189,6 +243,12 @@ module NginxAccessLogParserTests
|
|
189
243
|
include Data
|
190
244
|
include Tests
|
191
245
|
|
246
|
+
private
|
247
|
+
def time_log_component
|
248
|
+
times = [time_local, runtime, request_time].compact
|
249
|
+
"[#{times.join(', ')}]"
|
250
|
+
end
|
251
|
+
|
192
252
|
def runtime
|
193
253
|
nil
|
194
254
|
end
|
@@ -198,11 +258,40 @@ module NginxAccessLogParserTests
|
|
198
258
|
end
|
199
259
|
end
|
200
260
|
|
201
|
-
class
|
261
|
+
class CombinedWithTimeNginxLogTest < Test::Unit::TestCase
|
262
|
+
include Environment
|
263
|
+
include Data
|
264
|
+
include Tests
|
265
|
+
|
266
|
+
private
|
267
|
+
def time_log_component
|
268
|
+
times = [time_local, runtime, request_time].compact
|
269
|
+
"[#{times.join(', ')}]"
|
270
|
+
end
|
271
|
+
|
272
|
+
def runtime
|
273
|
+
0.000573
|
274
|
+
end
|
275
|
+
|
276
|
+
def request_time
|
277
|
+
0.001
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
class CombinedWithTimeApacheLogTest < Test::Unit::TestCase
|
202
282
|
include Environment
|
203
283
|
include Data
|
204
284
|
include Tests
|
205
285
|
|
286
|
+
private
|
287
|
+
def time_log_component
|
288
|
+
"[#{time_local}]"
|
289
|
+
end
|
290
|
+
|
291
|
+
def log_line(content)
|
292
|
+
"#{content} #{runtime} #{(request_time * 1_000_000).to_i}"
|
293
|
+
end
|
294
|
+
|
206
295
|
def runtime
|
207
296
|
0.000573
|
208
297
|
end
|
@@ -18,39 +18,35 @@ class InstanceNameTest < Test::Unit::TestCase
|
|
18
18
|
include RackngaTestUtils
|
19
19
|
|
20
20
|
def test_no_option
|
21
|
-
|
22
|
-
user = default_instance_name.user
|
23
|
-
revision = default_instance_name.revision
|
21
|
+
footer_variables = extract_from_default_instance_name
|
24
22
|
|
25
23
|
instance_name_options({}) do
|
26
24
|
request
|
27
|
-
assert_header("Proc
|
25
|
+
assert_header("Proc #{default_footer(*footer_variables)}")
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
29
|
def test_application_name
|
32
30
|
application_name = "HelloWorld"
|
33
|
-
|
34
|
-
user = default_instance_name.user
|
35
|
-
revision = default_instance_name.revision
|
31
|
+
footer_variables = extract_from_default_instance_name
|
36
32
|
|
37
33
|
instance_name_options(:application_name => application_name) do
|
38
34
|
request
|
39
|
-
assert_header("#{application_name}
|
35
|
+
assert_header("#{application_name} " +
|
36
|
+
"#{default_footer(*footer_variables)}")
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
43
40
|
def test_both_application_name_and_version
|
44
41
|
application_name = "HelloWorld"
|
45
42
|
version = 1
|
46
|
-
|
47
|
-
user = default_instance_name.user
|
48
|
-
revision = default_instance_name.revision
|
43
|
+
footer_variables = extract_from_default_instance_name
|
49
44
|
|
50
45
|
instance_name_options(:application_name => application_name,
|
51
46
|
:version => version) do
|
52
47
|
request
|
53
|
-
assert_header("#{application_name} v#{version}
|
48
|
+
assert_header("#{application_name} v#{version} " +
|
49
|
+
"#{default_footer(*footer_variables)}")
|
54
50
|
end
|
55
51
|
end
|
56
52
|
|
@@ -72,7 +68,22 @@ class InstanceNameTest < Test::Unit::TestCase
|
|
72
68
|
end
|
73
69
|
|
74
70
|
def default_instance_name
|
75
|
-
@default_instance_name ||=
|
71
|
+
@default_instance_name ||=
|
72
|
+
create_instance_name(create_minimal_application).freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
def extract_from_default_instance_name
|
76
|
+
server = default_instance_name.server
|
77
|
+
user = default_instance_name.user
|
78
|
+
revision = default_instance_name.revision
|
79
|
+
branch = default_instance_name.branch
|
80
|
+
ruby = default_instance_name.ruby
|
81
|
+
|
82
|
+
[server, user, revision, branch, ruby]
|
83
|
+
end
|
84
|
+
|
85
|
+
def default_footer(server, user, revision, branch, ruby)
|
86
|
+
"(at #{revision} (#{branch})) on #{server} by #{user} with #{ruby}"
|
76
87
|
end
|
77
88
|
|
78
89
|
def create_minimal_application
|
@@ -93,6 +104,7 @@ class InstanceNameTest < Test::Unit::TestCase
|
|
93
104
|
end
|
94
105
|
|
95
106
|
def assert_header(expected_header)
|
96
|
-
|
107
|
+
actual_header = page.response_headers["X-Responsed-By"]
|
108
|
+
assert_equal(expected_header, actual_header)
|
97
109
|
end
|
98
110
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
|
2
|
+
#
|
3
|
+
# This library is free software; you can redistribute it and/or
|
4
|
+
# modify it under the terms of the GNU Lesser General Public
|
5
|
+
# License as published by the Free Software Foundation; either
|
6
|
+
# version 2.1 of the License, or (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This library is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
11
|
+
# Lesser General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Lesser General Public
|
14
|
+
# License along with this library; if not, write to the Free Software
|
15
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
16
|
+
|
17
|
+
class NginxRawURITest < Test::Unit::TestCase
|
18
|
+
include RackngaTestUtils
|
19
|
+
|
20
|
+
class PseudoNginx
|
21
|
+
def initialize(application)
|
22
|
+
@application = application
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(environment)
|
26
|
+
mimic_nginx_behavior(environment)
|
27
|
+
|
28
|
+
@application.call(environment)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def mimic_nginx_behavior(environment)
|
33
|
+
environment["HTTP_X_RAW_REQUEST_URI"] = environment["PATH_INFO"]
|
34
|
+
environment["PATH_INFO"] = Rack::Utils.unescape(environment["PATH_INFO"])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def app
|
39
|
+
application = Proc.new do |environment|
|
40
|
+
@environment = environment
|
41
|
+
|
42
|
+
response
|
43
|
+
end
|
44
|
+
|
45
|
+
raw_uri = Racknga::Middleware::NginxRawURI.new(application)
|
46
|
+
pseudo_nginx = PseudoNginx.new(raw_uri)
|
47
|
+
|
48
|
+
pseudo_nginx
|
49
|
+
end
|
50
|
+
|
51
|
+
def setup
|
52
|
+
Capybara.app = app
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_slash
|
56
|
+
path_info_with_slash = "/keywords/GNU%2fLinux"
|
57
|
+
|
58
|
+
visit(path_info_with_slash)
|
59
|
+
assert_equal(path_info_with_slash, path_info)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def response
|
64
|
+
[200, {"Content-Type" => "plain/text"}, ["this is a response."]]
|
65
|
+
end
|
66
|
+
|
67
|
+
def path_info
|
68
|
+
@environment["PATH_INFO"]
|
69
|
+
end
|
70
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: racknga
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 61
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
9
|
+
- 3
|
10
|
+
version: 0.9.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Kouhei Sutou
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-11-12 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|
@@ -145,17 +145,19 @@ files:
|
|
145
145
|
- Rakefile
|
146
146
|
- doc/text//news.textile
|
147
147
|
- lib/racknga.rb
|
148
|
+
- lib/racknga/access_log_parser.rb
|
148
149
|
- lib/racknga/cache_database.rb
|
149
150
|
- lib/racknga/exception_mail_notifier.rb
|
150
151
|
- lib/racknga/log_database.rb
|
152
|
+
- lib/racknga/log_entry.rb
|
151
153
|
- lib/racknga/middleware/cache.rb
|
152
154
|
- lib/racknga/middleware/deflater.rb
|
153
155
|
- lib/racknga/middleware/exception_notifier.rb
|
154
156
|
- lib/racknga/middleware/instance_name.rb
|
155
157
|
- lib/racknga/middleware/jsonp.rb
|
156
158
|
- lib/racknga/middleware/log.rb
|
159
|
+
- lib/racknga/middleware/nginx_raw_uri.rb
|
157
160
|
- lib/racknga/middleware/range.rb
|
158
|
-
- lib/racknga/nginx_access_log_parser.rb
|
159
161
|
- lib/racknga/reverse_line_reader.rb
|
160
162
|
- lib/racknga/utils.rb
|
161
163
|
- lib/racknga/version.rb
|
@@ -166,10 +168,11 @@ files:
|
|
166
168
|
- test/test-middleware-jsonp.rb
|
167
169
|
- test/racknga-test-utils.rb
|
168
170
|
- test/test-middleware-range.rb
|
171
|
+
- test/test-middleware-nginx-raw-uri.rb
|
169
172
|
- test/run-test.rb
|
170
173
|
- test/test-middleware-instance-name.rb
|
171
174
|
- test/test-middleware-cache.rb
|
172
|
-
- test/test-
|
175
|
+
- test/test-access-log-parser.rb
|
173
176
|
homepage: http://groonga.rubyforge.org/
|
174
177
|
licenses:
|
175
178
|
- LGPLv2.1 or later
|
@@ -199,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
202
|
requirements: []
|
200
203
|
|
201
204
|
rubyforge_project: groonga
|
202
|
-
rubygems_version: 1.
|
205
|
+
rubygems_version: 1.8.10
|
203
206
|
signing_key:
|
204
207
|
specification_version: 3
|
205
208
|
summary: A Rack middleware collection for rroonga features.
|
@@ -207,7 +210,8 @@ test_files:
|
|
207
210
|
- test/test-middleware-jsonp.rb
|
208
211
|
- test/racknga-test-utils.rb
|
209
212
|
- test/test-middleware-range.rb
|
213
|
+
- test/test-middleware-nginx-raw-uri.rb
|
210
214
|
- test/run-test.rb
|
211
215
|
- test/test-middleware-instance-name.rb
|
212
216
|
- test/test-middleware-cache.rb
|
213
|
-
- test/test-
|
217
|
+
- test/test-access-log-parser.rb
|
@@ -1,139 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
|
4
|
-
#
|
5
|
-
# This library is free software; you can redistribute it and/or
|
6
|
-
# modify it under the terms of the GNU Lesser General Public
|
7
|
-
# License as published by the Free Software Foundation; either
|
8
|
-
# version 2.1 of the License, or (at your option) any later version.
|
9
|
-
#
|
10
|
-
# This library is distributed in the hope that it will be useful,
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
-
# Lesser General Public License for more details.
|
14
|
-
#
|
15
|
-
# You should have received a copy of the GNU Lesser General Public
|
16
|
-
# License along with this library; if not, write to the Free Software
|
17
|
-
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
18
|
-
|
19
|
-
require "racknga/reverse_line_reader"
|
20
|
-
|
21
|
-
module Racknga
|
22
|
-
# Supported formats:
|
23
|
-
# * combined (default format)
|
24
|
-
# * combined_with_time (custom format for Passenger)
|
25
|
-
#
|
26
|
-
# Configurations in nginx:
|
27
|
-
# * combined
|
28
|
-
# log_format combined '$remote_addr - $remote_user [$time_local] '
|
29
|
-
# '"$request" $status $body_bytes_sent '
|
30
|
-
# '"$http_referer" "$http_user_agent"';
|
31
|
-
# access_log log/access.log combined
|
32
|
-
#
|
33
|
-
# * combined_with_time
|
34
|
-
# log_format combined_with_time '$remote_addr - $remote_user '
|
35
|
-
# '[$time_local, $upstream_http_x_runtime, $request_time] '
|
36
|
-
# '"$request" $status $body_bytes_sent '
|
37
|
-
# '"$http_referer" "$http_user_agent"';
|
38
|
-
# access_log log/access.log combined_with_time
|
39
|
-
class NginxAccessLogParser
|
40
|
-
include Enumerable
|
41
|
-
|
42
|
-
class FormatError < StandardError
|
43
|
-
end
|
44
|
-
|
45
|
-
def initialize(line_reader)
|
46
|
-
@line_reader = line_reader
|
47
|
-
end
|
48
|
-
|
49
|
-
def each
|
50
|
-
@line_reader.each do |line|
|
51
|
-
line.force_encoding("UTF-8")
|
52
|
-
yield(parse_line(line)) if line.valid_encoding?
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
REMOTE_ADDRESS = '(?:\d{1,3}\.){3}\d{1,3}'
|
57
|
-
REMOTE_USER = '[^ ]+'
|
58
|
-
TIME_LOCAL = '[^ ]+ \+\d{4}'
|
59
|
-
RUNTIME = '(?:[\d.]+|-)'
|
60
|
-
REQUEST_TIME = '[\d.]+'
|
61
|
-
REQUEST = '.*?'
|
62
|
-
STATUS = '\d{3}'
|
63
|
-
BODY_BYTES_SENT = '\d+'
|
64
|
-
HTTP_REFERER = '.*?'
|
65
|
-
HTTP_USER_AGENT = '(?:\\"|[^\"])*?' # '
|
66
|
-
LOG_FORMAT =
|
67
|
-
/\A(#{REMOTE_ADDRESS}) - (#{REMOTE_USER}) \[(#{TIME_LOCAL})(?:, (#{RUNTIME}), (#{REQUEST_TIME}))?\] "(#{REQUEST})" (#{STATUS}) (#{BODY_BYTES_SENT}) "(#{HTTP_REFERER})" "(#{HTTP_USER_AGENT})"\n\z/
|
68
|
-
def parse_line(line)
|
69
|
-
if line =~ LOG_FORMAT
|
70
|
-
last_match = Regexp.last_match
|
71
|
-
options = {}
|
72
|
-
options[:remote_address] = last_match[1]
|
73
|
-
options[:remote_user] = last_match[2]
|
74
|
-
parse_time_local(last_match[3], options)
|
75
|
-
options[:runtime] = last_match[4].to_f
|
76
|
-
options[:request_time] = last_match[5].to_f
|
77
|
-
options[:request] = last_match[6]
|
78
|
-
options[:status] = last_match[7].to_i
|
79
|
-
options[:body_bytes_sent] = last_match[8].to_i
|
80
|
-
options[:http_referer] = last_match[9]
|
81
|
-
options[:http_user_agent] = last_match[10]
|
82
|
-
LogEntry.new(options)
|
83
|
-
else
|
84
|
-
raise FormatError.new("ill-formatted log entry: #{line.inspect} !~ #{LOG_FORMAT}")
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def parse_time_local(token, options)
|
89
|
-
day, month, year, hour, minute, second, _time_zone = token.split(/[\/: ]/)
|
90
|
-
options[:time_local] = Time.local(year, month, day, hour, minute, second)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
class ReversedNginxAccessLogParser < NginxAccessLogParser
|
95
|
-
def initialize(line_reader)
|
96
|
-
@line_reader = ReverseLineReader.new(line_reader)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
class LogEntry
|
101
|
-
ATTRIBUTES = [
|
102
|
-
:remote_address,
|
103
|
-
:remote_user,
|
104
|
-
:time_local,
|
105
|
-
:runtime,
|
106
|
-
:request_time,
|
107
|
-
:request,
|
108
|
-
:status,
|
109
|
-
:body_bytes_sent,
|
110
|
-
:http_referer,
|
111
|
-
:http_user_agent,
|
112
|
-
]
|
113
|
-
|
114
|
-
attr_reader(*ATTRIBUTES)
|
115
|
-
def initialize(options=nil)
|
116
|
-
options ||= {}
|
117
|
-
@remote_address = options[:remote_address]
|
118
|
-
@remote_user = options[:remote_user]
|
119
|
-
@time_local = options[:time_local] || Time.at(0)
|
120
|
-
@runtime = options[:runtime] || 0.0
|
121
|
-
@request_time = options[:request_time] || 0.0
|
122
|
-
@request = options[:request]
|
123
|
-
@status = options[:status]
|
124
|
-
@body_bytes_sent = options[:body_bytes_sent]
|
125
|
-
@http_referer = options[:http_referer]
|
126
|
-
@http_user_agent = options[:http_user_agent]
|
127
|
-
end
|
128
|
-
|
129
|
-
def attributes
|
130
|
-
ATTRIBUTES.collect do |attribute|
|
131
|
-
__send__(attribute)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def ==(other)
|
136
|
-
attributes == other.attributes
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|