racknga 0.9.2 → 0.9.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.
- 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
|