clogger 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/COPYING +165 -0
- data/GNUmakefile +37 -0
- data/History.txt +5 -0
- data/Manifest.txt +15 -0
- data/README.txt +132 -0
- data/Rakefile +30 -0
- data/ext/clogger_ext/clogger.c +800 -0
- data/ext/clogger_ext/extconf.rb +12 -0
- data/ext/clogger_ext/ruby_1_9_compat.h +23 -0
- data/lib/clogger.rb +133 -0
- data/lib/clogger/format.rb +25 -0
- data/lib/clogger/pure.rb +126 -0
- data/setup.rb +1585 -0
- data/test/test_clogger.rb +349 -0
- metadata +84 -0
data/.gitignore
ADDED
data/COPYING
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
2
|
+
Version 3, 29 June 2007
|
3
|
+
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
6
|
+
of this license document, but changing it is not allowed.
|
7
|
+
|
8
|
+
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
11
|
+
License, supplemented by the additional permissions listed below.
|
12
|
+
|
13
|
+
0. Additional Definitions.
|
14
|
+
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
17
|
+
General Public License.
|
18
|
+
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
20
|
+
other than an Application or a Combined Work as defined below.
|
21
|
+
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
25
|
+
of using an interface provided by the Library.
|
26
|
+
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
28
|
+
Application with the Library. The particular version of the Library
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
30
|
+
Version".
|
31
|
+
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
35
|
+
based on the Application, and not on the Linked Version.
|
36
|
+
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
38
|
+
object code and/or source code for the Application, including any data
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
41
|
+
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
43
|
+
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
46
|
+
|
47
|
+
2. Conveying Modified Versions.
|
48
|
+
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
51
|
+
that uses the facility (other than as an argument passed when the
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
53
|
+
version:
|
54
|
+
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
56
|
+
ensure that, in the event an Application does not supply the
|
57
|
+
function or data, the facility still operates, and performs
|
58
|
+
whatever part of its purpose remains meaningful, or
|
59
|
+
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
61
|
+
this License applicable to that copy.
|
62
|
+
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
64
|
+
|
65
|
+
The object code form of an Application may incorporate material from
|
66
|
+
a header file that is part of the Library. You may convey such object
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
68
|
+
material is not limited to numerical parameters, data structure
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
71
|
+
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
73
|
+
Library is used in it and that the Library and its use are
|
74
|
+
covered by this License.
|
75
|
+
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
77
|
+
document.
|
78
|
+
|
79
|
+
4. Combined Works.
|
80
|
+
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
82
|
+
taken together, effectively do not restrict modification of the
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
85
|
+
the following:
|
86
|
+
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
88
|
+
the Library is used in it and that the Library and its use are
|
89
|
+
covered by this License.
|
90
|
+
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
92
|
+
document.
|
93
|
+
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
95
|
+
execution, include the copyright notice for the Library among
|
96
|
+
these notices, as well as a reference directing the user to the
|
97
|
+
copies of the GNU GPL and this license document.
|
98
|
+
|
99
|
+
d) Do one of the following:
|
100
|
+
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
102
|
+
License, and the Corresponding Application Code in a form
|
103
|
+
suitable for, and under terms that permit, the user to
|
104
|
+
recombine or relink the Application with a modified version of
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
107
|
+
Corresponding Source.
|
108
|
+
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
111
|
+
a copy of the Library already present on the user's computer
|
112
|
+
system, and (b) will operate properly with a modified version
|
113
|
+
of the Library that is interface-compatible with the Linked
|
114
|
+
Version.
|
115
|
+
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
117
|
+
be required to provide such information under section 6 of the
|
118
|
+
GNU GPL, and only to the extent that such information is
|
119
|
+
necessary to install and execute a modified version of the
|
120
|
+
Combined Work produced by recombining or relinking the
|
121
|
+
Application with a modified version of the Linked Version. (If
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
126
|
+
for conveying Corresponding Source.)
|
127
|
+
|
128
|
+
5. Combined Libraries.
|
129
|
+
|
130
|
+
You may place library facilities that are a work based on the
|
131
|
+
Library side by side in a single library together with other library
|
132
|
+
facilities that are not Applications and are not covered by this
|
133
|
+
License, and convey such a combined library under terms of your
|
134
|
+
choice, if you do both of the following:
|
135
|
+
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
137
|
+
on the Library, uncombined with any other library facilities,
|
138
|
+
conveyed under the terms of this License.
|
139
|
+
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
141
|
+
is a work based on the Library, and explaining where to find the
|
142
|
+
accompanying uncombined form of the same work.
|
143
|
+
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
145
|
+
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
148
|
+
versions will be similar in spirit to the present version, but may
|
149
|
+
differ in detail to address new problems or concerns.
|
150
|
+
|
151
|
+
Each version is given a distinguishing version number. If the
|
152
|
+
Library as you received it specifies that a certain numbered version
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
154
|
+
applies to it, you have the option of following the terms and
|
155
|
+
conditions either of that published version or of any later version
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
160
|
+
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
164
|
+
permanent authorization for you to choose that version for the
|
165
|
+
Library.
|
data/GNUmakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
all:: test
|
2
|
+
ruby = ruby
|
3
|
+
|
4
|
+
-include local.mk
|
5
|
+
|
6
|
+
ifeq ($(DLEXT),) # "so" for Linux
|
7
|
+
DLEXT := $(shell $(ruby) -rrbconfig -e 'puts Config::CONFIG["DLEXT"]')
|
8
|
+
endif
|
9
|
+
|
10
|
+
ifeq ($(RUBY_VERSION),)
|
11
|
+
RUBY_VERSION := $(shell $(ruby) -e 'puts RUBY_VERSION')
|
12
|
+
endif
|
13
|
+
|
14
|
+
ext/clogger_ext/Makefile: ext/clogger_ext/clogger.c ext/clogger_ext/extconf.rb
|
15
|
+
cd ext/clogger_ext && $(ruby) extconf.rb
|
16
|
+
|
17
|
+
ext/clogger_ext/clogger.$(DLEXT): ext/clogger_ext/Makefile
|
18
|
+
$(MAKE) -C ext/clogger_ext
|
19
|
+
|
20
|
+
clean:
|
21
|
+
-$(MAKE) -C ext/clogger_ext clean
|
22
|
+
$(RM) ext/clogger_ext/Makefile lib/clogger_ext.$(DLEXT)
|
23
|
+
|
24
|
+
test-ext: ext/clogger_ext/clogger.$(DLEXT)
|
25
|
+
$(ruby) -Iext/clogger_ext:lib test/test_clogger.rb
|
26
|
+
|
27
|
+
test-pure:
|
28
|
+
$(ruby) -Ilib test/test_clogger.rb
|
29
|
+
|
30
|
+
test: test-ext test-pure
|
31
|
+
|
32
|
+
Manifest.txt:
|
33
|
+
git ls-files > $@+
|
34
|
+
cmp $@+ $@ || mv $@+ $@
|
35
|
+
$(RM) -f $@+
|
36
|
+
|
37
|
+
.PHONY: test doc Manifest.txt
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
.gitignore
|
2
|
+
COPYING
|
3
|
+
GNUmakefile
|
4
|
+
History.txt
|
5
|
+
Manifest.txt
|
6
|
+
README.txt
|
7
|
+
Rakefile
|
8
|
+
ext/clogger_ext/clogger.c
|
9
|
+
ext/clogger_ext/extconf.rb
|
10
|
+
ext/clogger_ext/ruby_1_9_compat.h
|
11
|
+
lib/clogger.rb
|
12
|
+
lib/clogger/format.rb
|
13
|
+
lib/clogger/pure.rb
|
14
|
+
setup.rb
|
15
|
+
test/test_clogger.rb
|
data/README.txt
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
= Clogger - configurable request logging for Rack
|
2
|
+
|
3
|
+
* http://clogger.rubyforge.org/
|
4
|
+
* mailto:clogger@librelist.com
|
5
|
+
|
6
|
+
== DESCRIPTION
|
7
|
+
|
8
|
+
Clogger is Rack middleware for logging HTTP requests. The log format
|
9
|
+
is customizable so you can specify exactly which fields to log.
|
10
|
+
|
11
|
+
== FEATURES
|
12
|
+
|
13
|
+
* highly customizable with easy-to-read nginx-like log format variables.
|
14
|
+
|
15
|
+
* pre-defines Apache Common Log Format, Apache Combined Log Format and
|
16
|
+
Rack::CommonLogger (as distributed by Rack 1.0) formats.
|
17
|
+
|
18
|
+
* Untrusted values are escaped (all HTTP headers, request URI components)
|
19
|
+
to make life easier for HTTP log parsers. The following bytes are escaped:
|
20
|
+
|
21
|
+
' (single quote)
|
22
|
+
" (double quote)
|
23
|
+
all bytes in the range of \x00-\x1f
|
24
|
+
|
25
|
+
== SYNOPSIS
|
26
|
+
|
27
|
+
Clogger may be loaded as Rack middleware in your config.ru:
|
28
|
+
|
29
|
+
require "clogger"
|
30
|
+
use Clogger,
|
31
|
+
:format => Clogger::Format::Combined,
|
32
|
+
:logger => File.open("/path/to/log", "ab")
|
33
|
+
run YourApplication.new
|
34
|
+
|
35
|
+
If you're using Rails 2.3.x or later, in your config/environment.rb
|
36
|
+
somewhere inside the "Rails::Initializer.run do |config|" block:
|
37
|
+
|
38
|
+
config.middleware.use 'Clogger',
|
39
|
+
:format => Clogger::Format::Combined,
|
40
|
+
:logger => File.open("/path/to/log", "ab")
|
41
|
+
|
42
|
+
== VARIABLES
|
43
|
+
|
44
|
+
* $http_* - HTTP request headers (e.g. $http_user_agent)
|
45
|
+
* $sent_http_* - HTTP response headers (e.g. $sent_http_content_length)
|
46
|
+
* $cookie_* - HTTP request cookie (e.g. $cookie_session_id)
|
47
|
+
Rack::Request#cookies must have been used by the underlying application
|
48
|
+
to parse the cookies into a hash.
|
49
|
+
* $request_method - the HTTP request method (e.g. GET, POST, HEAD, ...)
|
50
|
+
* $path_info - path component requested (e.g. /index.html)
|
51
|
+
* $query_string - request query string (not including leading "?")
|
52
|
+
* $request_uri - the URI requested ($path_info?$query_string)
|
53
|
+
* $request - the first line of the HTTP request
|
54
|
+
($request_method $request_uri $http_version)
|
55
|
+
* $request_time, $request_time{PRECISION} - time taken for request
|
56
|
+
(including response body iteration). PRECISION defaults to 3
|
57
|
+
(milliseconds) if not specified but may be specified 0(seconds) to
|
58
|
+
6(microseconds).
|
59
|
+
* $time_local, $time_local{FORMAT} - current local time, FORMAT defaults to
|
60
|
+
"%d/%b/%Y:%H:%M:%S %z" but accepts any strftime(3)-compatible format
|
61
|
+
* $time_utc, $time_utc{FORMAT} - like $time_local, except with UTC
|
62
|
+
* $usec - current time in seconds.microseconds since the Epoch
|
63
|
+
* $msec - current time in seconds.milliseconds since the Epoch
|
64
|
+
* $body_bytes_sent - bytes in the response body (Apache: %B)
|
65
|
+
* $response_length - body_bytes_sent, except "-" instead of "0" (Apache: %b)
|
66
|
+
* $remote_user - HTTP-authenticated user
|
67
|
+
* $remote_addr - IP of the requesting client socket
|
68
|
+
* $ip - X-Forwarded-For request header if available, $remote_addr if not
|
69
|
+
* $pid - process ID of the current process
|
70
|
+
* $e{Thread.current} - Thread processing the request
|
71
|
+
* $e{Actor.current} - Actor processing the request (Revactor or Rubinius)
|
72
|
+
|
73
|
+
== REQUIREMENTS
|
74
|
+
|
75
|
+
* Ruby, Rack
|
76
|
+
|
77
|
+
== DEVELOPMENT
|
78
|
+
|
79
|
+
The latest development happens in git and is published to the following:
|
80
|
+
|
81
|
+
git://rubyforge.org/clogger.git
|
82
|
+
git://git.bogomips.org/mirrors/clogger.git
|
83
|
+
|
84
|
+
You may also browse and download snapshot tarballs:
|
85
|
+
|
86
|
+
* http://clogger.rubyforge.org/git?p=clogger.git (gitweb)
|
87
|
+
* http://git.bogomips.org/cgit/mirrors/clogger.git (cgit)
|
88
|
+
|
89
|
+
The mailing list (see below) is central for coordination and
|
90
|
+
development. Patches should always be sent inline
|
91
|
+
(git format-patch -M + git send-email) so we can reply to them inline.
|
92
|
+
|
93
|
+
== CONTACT
|
94
|
+
|
95
|
+
All feedback (bug reports, user/development dicussion, patches, pull
|
96
|
+
requests) go to the mailing list.
|
97
|
+
|
98
|
+
* mailto:clogger@librelist.com
|
99
|
+
|
100
|
+
Do not send HTML mail or attachments. Do not top post.
|
101
|
+
|
102
|
+
== INSTALL
|
103
|
+
|
104
|
+
For all Rubygems users:
|
105
|
+
|
106
|
+
gem install clogger
|
107
|
+
|
108
|
+
If you're using MRI 1.8/1.9 and have a build environment, you can also try:
|
109
|
+
|
110
|
+
gem install clogger_ext
|
111
|
+
|
112
|
+
If you do not use Rubygems, you may also use setup.rb from tarballs from
|
113
|
+
the Rubyforge project page:
|
114
|
+
|
115
|
+
* http://rubyforge.org/frs/?group_id=8896
|
116
|
+
|
117
|
+
== LICENSE
|
118
|
+
|
119
|
+
Copyright (C) 2009 Eric Wong <normalperson@yhbt.net> and contributors.
|
120
|
+
|
121
|
+
Clogger is free software; you can redistribute it and/or modify it under
|
122
|
+
the terms of the GNU Lesser General Public License as published by the
|
123
|
+
Free Software Foundation, version 3.0.
|
124
|
+
|
125
|
+
Clogger is distributed in the hope that it will be useful, but WITHOUT ANY
|
126
|
+
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
127
|
+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
128
|
+
License for more details.
|
129
|
+
|
130
|
+
You should have received a copy of the GNU Lesser General Public License
|
131
|
+
along with Clogger; if not, write to the Free Software Foundation, Inc.,
|
132
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
$LOAD_PATH << 'lib'
|
3
|
+
require 'clogger'
|
4
|
+
begin
|
5
|
+
require 'rake/extensiontask'
|
6
|
+
Rake::ExtensionTask.new('clogger_ext')
|
7
|
+
rescue LoadError
|
8
|
+
warn "rake-compiler not available, cross compiling disabled"
|
9
|
+
end
|
10
|
+
|
11
|
+
common = lambda do |hoe|
|
12
|
+
title = hoe.paragraphs_of("README.txt", 0).first.sub(/^= /, '')
|
13
|
+
hoe.version = Clogger::VERSION
|
14
|
+
hoe.summary = title.split(/\s*-\s*/, 2).last
|
15
|
+
hoe.description = hoe.paragraphs_of("README.txt", 3)
|
16
|
+
hoe.rubyforge_name = 'clogger'
|
17
|
+
hoe.author = 'Eric Wong'
|
18
|
+
hoe.email = 'clogger@librelist.com'
|
19
|
+
hoe.spec_extras.merge!('rdoc_options' => [ "--title", title ])
|
20
|
+
hoe.remote_rdoc_dir = ''
|
21
|
+
end
|
22
|
+
|
23
|
+
if ENV['CLOGGER_EXT']
|
24
|
+
Hoe.spec('clogger_ext') do
|
25
|
+
common.call(self)
|
26
|
+
self.spec_extras.merge!(:extensions => Dir.glob('ext/*/extconf.rb'))
|
27
|
+
end
|
28
|
+
else
|
29
|
+
Hoe.spec('clogger') { common.call(self) }
|
30
|
+
end
|
@@ -0,0 +1,800 @@
|
|
1
|
+
#define _BSD_SOURCE
|
2
|
+
#include <ruby.h>
|
3
|
+
#include <assert.h>
|
4
|
+
#include <unistd.h>
|
5
|
+
#include <sys/types.h>
|
6
|
+
#include <sys/time.h>
|
7
|
+
#include <time.h>
|
8
|
+
#include <errno.h>
|
9
|
+
#ifdef HAVE_FCNTL_H
|
10
|
+
# include <fcntl.h>
|
11
|
+
#endif
|
12
|
+
#include "ruby_1_9_compat.h"
|
13
|
+
|
14
|
+
/* in case _BSD_SOURCE doesn't give us this macro */
|
15
|
+
#ifndef timersub
|
16
|
+
# define timersub(a, b, result) \
|
17
|
+
do { \
|
18
|
+
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
|
19
|
+
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
|
20
|
+
if ((result)->tv_usec < 0) { \
|
21
|
+
--(result)->tv_sec; \
|
22
|
+
(result)->tv_usec += 1000000; \
|
23
|
+
} \
|
24
|
+
} while (0)
|
25
|
+
#endif
|
26
|
+
|
27
|
+
/* give GCC hints for better branch prediction
|
28
|
+
* (we layout branches so that ASCII characters are handled faster) */
|
29
|
+
#if defined(__GNUC__) && (__GNUC__ >= 3)
|
30
|
+
# define likely(x) __builtin_expect (!!(x), 1)
|
31
|
+
# define unlikely(x) __builtin_expect (!!(x), 0)
|
32
|
+
#else
|
33
|
+
# define unlikely(x) (x)
|
34
|
+
# define likely(x) (x)
|
35
|
+
#endif
|
36
|
+
|
37
|
+
enum clogger_opcode {
|
38
|
+
CL_OP_LITERAL = 0,
|
39
|
+
CL_OP_REQUEST,
|
40
|
+
CL_OP_RESPONSE,
|
41
|
+
CL_OP_SPECIAL,
|
42
|
+
CL_OP_EVAL,
|
43
|
+
CL_OP_TIME_LOCAL,
|
44
|
+
CL_OP_TIME_UTC,
|
45
|
+
CL_OP_REQUEST_TIME,
|
46
|
+
CL_OP_TIME,
|
47
|
+
CL_OP_COOKIE
|
48
|
+
};
|
49
|
+
|
50
|
+
enum clogger_special {
|
51
|
+
CL_SP_body_bytes_sent = 0,
|
52
|
+
CL_SP_status,
|
53
|
+
CL_SP_request,
|
54
|
+
CL_SP_request_length,
|
55
|
+
CL_SP_response_length,
|
56
|
+
CL_SP_ip,
|
57
|
+
CL_SP_pid
|
58
|
+
};
|
59
|
+
|
60
|
+
struct clogger {
|
61
|
+
VALUE app;
|
62
|
+
|
63
|
+
VALUE fmt_ops;
|
64
|
+
VALUE logger;
|
65
|
+
VALUE log_buf;
|
66
|
+
|
67
|
+
VALUE env;
|
68
|
+
VALUE cookies;
|
69
|
+
VALUE response;
|
70
|
+
|
71
|
+
off_t body_bytes_sent;
|
72
|
+
struct timeval tv_start;
|
73
|
+
|
74
|
+
int fd;
|
75
|
+
int wrap_body;
|
76
|
+
int need_resp;
|
77
|
+
int reentrant; /* tri-state, -1:auto, 1/0 true/false */
|
78
|
+
};
|
79
|
+
|
80
|
+
static ID ltlt_id;
|
81
|
+
static ID call_id;
|
82
|
+
static ID each_id;
|
83
|
+
static ID close_id;
|
84
|
+
static ID to_i_id;
|
85
|
+
static ID to_s_id;
|
86
|
+
static ID size_id;
|
87
|
+
static VALUE cClogger;
|
88
|
+
static VALUE mFormat;
|
89
|
+
|
90
|
+
/* common hash lookup keys */
|
91
|
+
static VALUE g_HTTP_X_FORWARDED_FOR;
|
92
|
+
static VALUE g_REMOTE_ADDR;
|
93
|
+
static VALUE g_REQUEST_METHOD;
|
94
|
+
static VALUE g_PATH_INFO;
|
95
|
+
static VALUE g_QUERY_STRING;
|
96
|
+
static VALUE g_HTTP_VERSION;
|
97
|
+
static VALUE g_rack_errors;
|
98
|
+
static VALUE g_rack_input;
|
99
|
+
static VALUE g_rack_multithread;
|
100
|
+
static VALUE g_dash;
|
101
|
+
static VALUE g_empty;
|
102
|
+
static VALUE g_space;
|
103
|
+
static VALUE g_question_mark;
|
104
|
+
static VALUE g_rack_request_cookie_hash;
|
105
|
+
|
106
|
+
#define LOG_BUF_INIT_SIZE 128
|
107
|
+
|
108
|
+
static void init_buffers(struct clogger *c)
|
109
|
+
{
|
110
|
+
c->log_buf = rb_str_buf_new(LOG_BUF_INIT_SIZE);
|
111
|
+
}
|
112
|
+
|
113
|
+
static inline int need_escape(unsigned c)
|
114
|
+
{
|
115
|
+
assert(c <= 0xff);
|
116
|
+
return !!(c == '\'' || c == '"' || (c >= 0 && c <= 0x1f));
|
117
|
+
}
|
118
|
+
|
119
|
+
/* we are encoding-agnostic, clients can send us all sorts of junk */
|
120
|
+
static VALUE byte_xs(VALUE from)
|
121
|
+
{
|
122
|
+
static const char esc[] = "0123456789ABCDEF";
|
123
|
+
unsigned char *new_ptr;
|
124
|
+
unsigned char *ptr = (unsigned char *)RSTRING_PTR(from);
|
125
|
+
long len = RSTRING_LEN(from);
|
126
|
+
long new_len = len;
|
127
|
+
VALUE rv;
|
128
|
+
|
129
|
+
for (; --len >= 0; ptr++) {
|
130
|
+
unsigned c = *ptr;
|
131
|
+
|
132
|
+
if (unlikely(need_escape(c)))
|
133
|
+
new_len += 3; /* { '\', 'x', 'X', 'X' } */
|
134
|
+
}
|
135
|
+
|
136
|
+
len = RSTRING_LEN(from);
|
137
|
+
if (new_len == len)
|
138
|
+
return from;
|
139
|
+
|
140
|
+
rv = rb_str_new(0, new_len);
|
141
|
+
new_ptr = (unsigned char *)RSTRING_PTR(rv);
|
142
|
+
ptr = (unsigned char *)RSTRING_PTR(from);
|
143
|
+
for (; --len >= 0; ptr++) {
|
144
|
+
unsigned c = *ptr;
|
145
|
+
|
146
|
+
if (unlikely(need_escape(c))) {
|
147
|
+
*new_ptr++ = '\\';
|
148
|
+
*new_ptr++ = 'x';
|
149
|
+
*new_ptr++ = esc[c >> 4];
|
150
|
+
*new_ptr++ = esc[c & 0xf];
|
151
|
+
} else {
|
152
|
+
*new_ptr++ = c;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
assert(RSTRING_PTR(rv)[RSTRING_LEN(rv)] == '\0');
|
156
|
+
|
157
|
+
return rv;
|
158
|
+
}
|
159
|
+
|
160
|
+
/* strcasecmp isn't locale independent, so we roll our own */
|
161
|
+
static int str_case_eq(VALUE a, VALUE b)
|
162
|
+
{
|
163
|
+
long alen = RSTRING_LEN(a);
|
164
|
+
long blen = RSTRING_LEN(b);
|
165
|
+
|
166
|
+
if (alen == blen) {
|
167
|
+
const char *aptr = RSTRING_PTR(a);
|
168
|
+
const char *bptr = RSTRING_PTR(b);
|
169
|
+
|
170
|
+
for (; alen--; ++aptr, ++bptr) {
|
171
|
+
if ((*bptr == *aptr)
|
172
|
+
|| (*aptr >= 'A' && *aptr <= 'Z' &&
|
173
|
+
(*aptr | 0x20) == *bptr))
|
174
|
+
continue;
|
175
|
+
return 0;
|
176
|
+
}
|
177
|
+
return 1;
|
178
|
+
}
|
179
|
+
return 0;
|
180
|
+
}
|
181
|
+
|
182
|
+
struct response_ops { long nr; VALUE ops; };
|
183
|
+
|
184
|
+
/* this can be worse than O(M*N) :<... but C loops are fast ... */
|
185
|
+
static VALUE swap_sent_headers(VALUE kv, VALUE memo)
|
186
|
+
{
|
187
|
+
struct response_ops *tmp = (struct response_ops *)memo;
|
188
|
+
VALUE key = RARRAY_PTR(kv)[0];
|
189
|
+
long i = RARRAY_LEN(tmp->ops);
|
190
|
+
VALUE *ary = RARRAY_PTR(tmp->ops);
|
191
|
+
VALUE value;
|
192
|
+
|
193
|
+
for (; --i >= 0; ary++) {
|
194
|
+
VALUE *op = RARRAY_PTR(*ary);
|
195
|
+
enum clogger_opcode opcode = NUM2INT(op[0]);
|
196
|
+
|
197
|
+
if (opcode != CL_OP_RESPONSE)
|
198
|
+
continue;
|
199
|
+
assert(RARRAY_LEN(*ary) == 2);
|
200
|
+
if (!str_case_eq(key, op[1]))
|
201
|
+
continue;
|
202
|
+
|
203
|
+
value = RARRAY_PTR(kv)[1];
|
204
|
+
op[0] = INT2NUM(CL_OP_LITERAL);
|
205
|
+
op[1] = byte_xs(rb_obj_as_string(value));
|
206
|
+
|
207
|
+
if (!--tmp->nr)
|
208
|
+
rb_iter_break();
|
209
|
+
return Qnil;
|
210
|
+
}
|
211
|
+
return Qnil;
|
212
|
+
}
|
213
|
+
|
214
|
+
static VALUE sent_headers_ops(struct clogger *c)
|
215
|
+
{
|
216
|
+
struct response_ops tmp;
|
217
|
+
long i, len;
|
218
|
+
VALUE *ary;
|
219
|
+
|
220
|
+
if (!c->need_resp)
|
221
|
+
return c->fmt_ops;
|
222
|
+
|
223
|
+
tmp.nr = 0;
|
224
|
+
tmp.ops = rb_ary_dup(c->fmt_ops);
|
225
|
+
len = RARRAY_LEN(tmp.ops);
|
226
|
+
ary = RARRAY_PTR(tmp.ops);
|
227
|
+
|
228
|
+
for (i = 0; i < len; ++i) {
|
229
|
+
VALUE *op = RARRAY_PTR(ary[i]);
|
230
|
+
|
231
|
+
if (NUM2INT(op[0]) == CL_OP_RESPONSE) {
|
232
|
+
assert(RARRAY_LEN(ary[i]) == 2);
|
233
|
+
ary[i] = rb_ary_dup(ary[i]);
|
234
|
+
++tmp.nr;
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
rb_iterate(rb_each, RARRAY_PTR(c->response)[1],
|
239
|
+
swap_sent_headers, (VALUE)&tmp);
|
240
|
+
|
241
|
+
return tmp.ops;
|
242
|
+
}
|
243
|
+
|
244
|
+
static void clogger_mark(void *ptr)
|
245
|
+
{
|
246
|
+
struct clogger *c = ptr;
|
247
|
+
|
248
|
+
rb_gc_mark_locations(&c->app, &c->response);
|
249
|
+
}
|
250
|
+
|
251
|
+
static VALUE clogger_alloc(VALUE klass)
|
252
|
+
{
|
253
|
+
struct clogger *c;
|
254
|
+
|
255
|
+
return Data_Make_Struct(klass, struct clogger, clogger_mark, 0, c);
|
256
|
+
}
|
257
|
+
|
258
|
+
static struct clogger *clogger_get(VALUE self)
|
259
|
+
{
|
260
|
+
struct clogger *c;
|
261
|
+
|
262
|
+
Data_Get_Struct(self, struct clogger, c);
|
263
|
+
assert(c);
|
264
|
+
return c;
|
265
|
+
}
|
266
|
+
|
267
|
+
static VALUE obj_fileno(VALUE obj)
|
268
|
+
{
|
269
|
+
return rb_funcall(obj, rb_intern("fileno"), 0);
|
270
|
+
}
|
271
|
+
|
272
|
+
/* only for writing to regular files, not stupid crap like NFS */
|
273
|
+
static void write_full(int fd, const void *buf, size_t count)
|
274
|
+
{
|
275
|
+
ssize_t r;
|
276
|
+
|
277
|
+
while (count > 0) {
|
278
|
+
r = write(fd, buf, count);
|
279
|
+
|
280
|
+
if (r == count) { /* overwhelmingly likely */
|
281
|
+
return;
|
282
|
+
} else if (r > 0) {
|
283
|
+
count -= r;
|
284
|
+
buf += r;
|
285
|
+
} else {
|
286
|
+
if (errno == EINTR || errno == EAGAIN)
|
287
|
+
continue; /* poor souls on NFS and like: */
|
288
|
+
if (!errno)
|
289
|
+
errno = ENOSPC;
|
290
|
+
rb_sys_fail("write");
|
291
|
+
}
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
/*
|
296
|
+
* allow us to use write_full() iff we detect a blocking file
|
297
|
+
* descriptor that wouldn't play nicely with Ruby threading/fibers
|
298
|
+
*/
|
299
|
+
static int raw_fd(VALUE fileno)
|
300
|
+
{
|
301
|
+
#if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
|
302
|
+
int fd;
|
303
|
+
int flags;
|
304
|
+
|
305
|
+
if (NIL_P(fileno))
|
306
|
+
return -1;
|
307
|
+
fd = NUM2INT(fileno);
|
308
|
+
|
309
|
+
flags = fcntl(fd, F_GETFL);
|
310
|
+
if (flags < 0)
|
311
|
+
rb_sys_fail("fcntl");
|
312
|
+
|
313
|
+
return (flags & O_NONBLOCK) ? -1 : fd;
|
314
|
+
#else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
|
315
|
+
return -1;
|
316
|
+
#endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
|
317
|
+
}
|
318
|
+
|
319
|
+
/* :nodoc: */
|
320
|
+
static VALUE clogger_reentrant(VALUE self)
|
321
|
+
{
|
322
|
+
return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
|
323
|
+
}
|
324
|
+
|
325
|
+
/* :nodoc: */
|
326
|
+
static VALUE clogger_wrap_body(VALUE self)
|
327
|
+
{
|
328
|
+
return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
|
329
|
+
}
|
330
|
+
|
331
|
+
static void append_status(struct clogger *c, VALUE status)
|
332
|
+
{
|
333
|
+
char buf[sizeof("999")];
|
334
|
+
int nr;
|
335
|
+
|
336
|
+
if (TYPE(status) != T_FIXNUM) {
|
337
|
+
status = rb_funcall(status, to_i_id, 0);
|
338
|
+
/* no way it's a valid status code (at least not HTTP/1.1) */
|
339
|
+
if (TYPE(status) != T_FIXNUM) {
|
340
|
+
rb_str_buf_append(c->log_buf, g_dash);
|
341
|
+
return;
|
342
|
+
}
|
343
|
+
}
|
344
|
+
|
345
|
+
nr = NUM2INT(status);
|
346
|
+
if (nr >= 100 && nr <= 999) {
|
347
|
+
nr = snprintf(buf, sizeof(buf), "%03d", nr);
|
348
|
+
assert(nr == 3);
|
349
|
+
rb_str_buf_cat(c->log_buf, buf, nr);
|
350
|
+
} else {
|
351
|
+
/* raise?, swap for 500? */
|
352
|
+
rb_str_buf_append(c->log_buf, g_dash);
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
/* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
|
357
|
+
static void append_ip(struct clogger *c)
|
358
|
+
{
|
359
|
+
VALUE env = c->env;
|
360
|
+
VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
|
361
|
+
|
362
|
+
if (NIL_P(tmp)) {
|
363
|
+
/* can't be faked on any real server, so no escape */
|
364
|
+
tmp = rb_hash_aref(env, g_REMOTE_ADDR);
|
365
|
+
if (NIL_P(tmp))
|
366
|
+
tmp = g_dash;
|
367
|
+
} else {
|
368
|
+
tmp = byte_xs(tmp);
|
369
|
+
}
|
370
|
+
rb_str_buf_append(c->log_buf, tmp);
|
371
|
+
}
|
372
|
+
|
373
|
+
static void append_body_bytes_sent(struct clogger *c)
|
374
|
+
{
|
375
|
+
char buf[(sizeof(off_t) * 8) / 3 + 1];
|
376
|
+
const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
|
377
|
+
int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
|
378
|
+
|
379
|
+
assert(nr > 0 && nr < sizeof(buf));
|
380
|
+
rb_str_buf_cat(c->log_buf, buf, nr);
|
381
|
+
}
|
382
|
+
|
383
|
+
static void append_tv(struct clogger *c, const VALUE *op, struct timeval *tv)
|
384
|
+
{
|
385
|
+
char buf[sizeof(".000000") + ((sizeof(tv->tv_sec) * 8) / 3)];
|
386
|
+
int nr;
|
387
|
+
char *fmt = RSTRING_PTR(op[1]);
|
388
|
+
int div = NUM2INT(op[2]);
|
389
|
+
|
390
|
+
nr = snprintf(buf, sizeof(buf), fmt,
|
391
|
+
(int)tv->tv_sec, (int)(tv->tv_usec / div));
|
392
|
+
assert(nr > 0 && nr < sizeof(buf));
|
393
|
+
rb_str_buf_cat(c->log_buf, buf, nr);
|
394
|
+
}
|
395
|
+
|
396
|
+
static void append_request_time_fmt(struct clogger *c, const VALUE *op)
|
397
|
+
{
|
398
|
+
struct timeval now, d;
|
399
|
+
|
400
|
+
gettimeofday(&now, NULL);
|
401
|
+
timersub(&now, &c->tv_start, &d);
|
402
|
+
append_tv(c, op, &d);
|
403
|
+
}
|
404
|
+
|
405
|
+
static void append_time_fmt(struct clogger *c, const VALUE *op)
|
406
|
+
{
|
407
|
+
struct timeval now;
|
408
|
+
|
409
|
+
gettimeofday(&now, NULL);
|
410
|
+
append_tv(c, op, &now);
|
411
|
+
}
|
412
|
+
|
413
|
+
static void append_request(struct clogger *c)
|
414
|
+
{
|
415
|
+
VALUE tmp;
|
416
|
+
VALUE env = c->env;
|
417
|
+
|
418
|
+
/* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
|
419
|
+
tmp = rb_hash_aref(env, g_REQUEST_METHOD);
|
420
|
+
rb_str_buf_append(c->log_buf, NIL_P(tmp) ? g_empty : tmp);
|
421
|
+
rb_str_buf_append(c->log_buf, g_space);
|
422
|
+
|
423
|
+
/* broken clients can send " and other questionable URIs */
|
424
|
+
tmp = rb_hash_aref(env, g_PATH_INFO);
|
425
|
+
rb_str_buf_append(c->log_buf, NIL_P(tmp) ? g_empty : byte_xs(tmp));
|
426
|
+
|
427
|
+
tmp = rb_hash_aref(env, g_QUERY_STRING);
|
428
|
+
if (RSTRING_LEN(tmp) != 0) {
|
429
|
+
rb_str_buf_append(c->log_buf, g_question_mark);
|
430
|
+
rb_str_buf_append(c->log_buf, byte_xs(tmp));
|
431
|
+
}
|
432
|
+
rb_str_buf_append(c->log_buf, g_space);
|
433
|
+
|
434
|
+
/* HTTP_VERSION can be injected by malicious clients */
|
435
|
+
tmp = rb_hash_aref(env, g_HTTP_VERSION);
|
436
|
+
rb_str_buf_append(c->log_buf, NIL_P(tmp) ? g_empty : byte_xs(tmp));
|
437
|
+
}
|
438
|
+
|
439
|
+
static void append_request_length(struct clogger *c)
|
440
|
+
{
|
441
|
+
VALUE tmp = rb_hash_aref(c->env, g_rack_input);
|
442
|
+
if (NIL_P(tmp)) {
|
443
|
+
rb_str_buf_append(c->log_buf, g_dash);
|
444
|
+
} else {
|
445
|
+
tmp = rb_funcall(tmp, size_id, 0);
|
446
|
+
rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
static void append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt)
|
451
|
+
{
|
452
|
+
/* you'd have to be a moron to use formats this big... */
|
453
|
+
char buf[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
|
454
|
+
size_t nr;
|
455
|
+
struct tm tmp;
|
456
|
+
time_t t = time(NULL);
|
457
|
+
|
458
|
+
if (op == CL_OP_TIME_LOCAL)
|
459
|
+
localtime_r(&t, &tmp);
|
460
|
+
else if (op == CL_OP_TIME_UTC)
|
461
|
+
gmtime_r(&t, &tmp);
|
462
|
+
else
|
463
|
+
assert(0 && "unknown op");
|
464
|
+
|
465
|
+
nr = strftime(buf, sizeof(buf), RSTRING_PTR(fmt), &tmp);
|
466
|
+
if (nr == 0 || nr == sizeof(buf))
|
467
|
+
rb_str_buf_append(c->log_buf, g_dash);
|
468
|
+
else
|
469
|
+
rb_str_buf_cat(c->log_buf, buf, nr);
|
470
|
+
}
|
471
|
+
|
472
|
+
static void append_pid(struct clogger *c)
|
473
|
+
{
|
474
|
+
char buf[(sizeof(pid_t) * 8) / 3 + 1];
|
475
|
+
int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
|
476
|
+
|
477
|
+
assert(nr > 0 && nr < sizeof(buf));
|
478
|
+
rb_str_buf_cat(c->log_buf, buf, nr);
|
479
|
+
}
|
480
|
+
|
481
|
+
static void append_eval(struct clogger *c, VALUE str)
|
482
|
+
{
|
483
|
+
int state = -1;
|
484
|
+
VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
|
485
|
+
|
486
|
+
rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
|
487
|
+
rb_str_buf_append(c->log_buf, rv);
|
488
|
+
}
|
489
|
+
|
490
|
+
static void append_cookie(struct clogger *c, VALUE key)
|
491
|
+
{
|
492
|
+
VALUE cookie;
|
493
|
+
|
494
|
+
if (c->cookies == Qfalse)
|
495
|
+
c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
|
496
|
+
|
497
|
+
if (NIL_P(c->cookies)) {
|
498
|
+
cookie = g_dash;
|
499
|
+
} else {
|
500
|
+
cookie = rb_hash_aref(c->cookies, key);
|
501
|
+
if (NIL_P(cookie))
|
502
|
+
cookie = g_dash;
|
503
|
+
}
|
504
|
+
rb_str_buf_append(c->log_buf, cookie);
|
505
|
+
}
|
506
|
+
|
507
|
+
static void append_request_env(struct clogger *c, VALUE key)
|
508
|
+
{
|
509
|
+
VALUE tmp = rb_hash_aref(c->env, key);
|
510
|
+
|
511
|
+
tmp = NIL_P(tmp) ? g_dash : byte_xs(rb_obj_as_string(tmp));
|
512
|
+
rb_str_buf_append(c->log_buf, tmp);
|
513
|
+
}
|
514
|
+
|
515
|
+
static void special_var(struct clogger *c, enum clogger_special var)
|
516
|
+
{
|
517
|
+
switch (var) {
|
518
|
+
case CL_SP_body_bytes_sent:
|
519
|
+
append_body_bytes_sent(c);
|
520
|
+
break;
|
521
|
+
case CL_SP_status:
|
522
|
+
append_status(c, RARRAY_PTR(c->response)[0]);
|
523
|
+
break;
|
524
|
+
case CL_SP_request:
|
525
|
+
append_request(c);
|
526
|
+
break;
|
527
|
+
case CL_SP_request_length:
|
528
|
+
append_request_length(c);
|
529
|
+
break;
|
530
|
+
case CL_SP_response_length:
|
531
|
+
if (c->body_bytes_sent == 0)
|
532
|
+
rb_str_buf_append(c->log_buf, g_dash);
|
533
|
+
else
|
534
|
+
append_body_bytes_sent(c);
|
535
|
+
break;
|
536
|
+
case CL_SP_ip:
|
537
|
+
append_ip(c);
|
538
|
+
break;
|
539
|
+
case CL_SP_pid:
|
540
|
+
append_pid(c);
|
541
|
+
}
|
542
|
+
}
|
543
|
+
|
544
|
+
static VALUE cwrite(struct clogger *c)
|
545
|
+
{
|
546
|
+
const VALUE ops = sent_headers_ops(c);
|
547
|
+
const VALUE *ary = RARRAY_PTR(ops);
|
548
|
+
long i = RARRAY_LEN(ops);
|
549
|
+
VALUE dst = c->log_buf;
|
550
|
+
|
551
|
+
rb_str_set_len(dst, 0);
|
552
|
+
|
553
|
+
for (; --i >= 0; ary++) {
|
554
|
+
const VALUE *op = RARRAY_PTR(*ary);
|
555
|
+
enum clogger_opcode opcode = NUM2INT(op[0]);
|
556
|
+
|
557
|
+
switch (opcode) {
|
558
|
+
case CL_OP_LITERAL:
|
559
|
+
rb_str_buf_append(dst, op[1]);
|
560
|
+
break;
|
561
|
+
case CL_OP_REQUEST:
|
562
|
+
append_request_env(c, op[1]);
|
563
|
+
break;
|
564
|
+
case CL_OP_RESPONSE:
|
565
|
+
/* headers we found already got swapped for literals */
|
566
|
+
rb_str_buf_append(dst, g_dash);
|
567
|
+
break;
|
568
|
+
case CL_OP_SPECIAL:
|
569
|
+
special_var(c, NUM2INT(op[1]));
|
570
|
+
break;
|
571
|
+
case CL_OP_EVAL:
|
572
|
+
append_eval(c, op[1]);
|
573
|
+
break;
|
574
|
+
case CL_OP_TIME_LOCAL:
|
575
|
+
case CL_OP_TIME_UTC:
|
576
|
+
append_time(c, opcode, op[1]);
|
577
|
+
break;
|
578
|
+
case CL_OP_REQUEST_TIME:
|
579
|
+
append_request_time_fmt(c, op);
|
580
|
+
break;
|
581
|
+
case CL_OP_TIME:
|
582
|
+
append_time_fmt(c, op);
|
583
|
+
break;
|
584
|
+
case CL_OP_COOKIE:
|
585
|
+
append_cookie(c, op[1]);
|
586
|
+
break;
|
587
|
+
}
|
588
|
+
}
|
589
|
+
|
590
|
+
if (c->fd >= 0) {
|
591
|
+
write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
|
592
|
+
} else {
|
593
|
+
VALUE logger = c->logger;
|
594
|
+
|
595
|
+
if (NIL_P(logger))
|
596
|
+
logger = rb_hash_aref(c->env, g_rack_errors);
|
597
|
+
rb_funcall(logger, ltlt_id, 1, dst);
|
598
|
+
}
|
599
|
+
|
600
|
+
return Qnil;
|
601
|
+
}
|
602
|
+
|
603
|
+
/**
|
604
|
+
* call-seq:
|
605
|
+
* Clogger.new(app, :logger => $stderr, :format => string) => obj
|
606
|
+
*
|
607
|
+
* Creates a new Clogger object that wraps +app+. +:logger+ may
|
608
|
+
* be any object that responds to the "<<" method with a string argument.
|
609
|
+
*/
|
610
|
+
static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
|
611
|
+
{
|
612
|
+
struct clogger *c = clogger_get(self);
|
613
|
+
VALUE o = Qnil;
|
614
|
+
VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
|
615
|
+
|
616
|
+
rb_scan_args(argc, argv, "11", &c->app, &o);
|
617
|
+
c->fd = -1;
|
618
|
+
c->logger = Qnil;
|
619
|
+
c->reentrant = -1; /* auto-detect */
|
620
|
+
|
621
|
+
if (TYPE(o) == T_HASH) {
|
622
|
+
VALUE tmp;
|
623
|
+
|
624
|
+
c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
|
625
|
+
if (!NIL_P(c->logger))
|
626
|
+
c->fd = raw_fd(rb_rescue(obj_fileno, c->logger, 0, 0));
|
627
|
+
|
628
|
+
tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
|
629
|
+
if (!NIL_P(tmp))
|
630
|
+
fmt = tmp;
|
631
|
+
}
|
632
|
+
|
633
|
+
init_buffers(c);
|
634
|
+
c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 1, fmt);
|
635
|
+
|
636
|
+
if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
|
637
|
+
1, c->fmt_ops))
|
638
|
+
c->need_resp = 1;
|
639
|
+
if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
|
640
|
+
1, c->fmt_ops))
|
641
|
+
c->wrap_body = 1;
|
642
|
+
|
643
|
+
return self;
|
644
|
+
}
|
645
|
+
|
646
|
+
static VALUE body_iter_i(VALUE str, VALUE memop)
|
647
|
+
{
|
648
|
+
off_t *len = (off_t *)memop;
|
649
|
+
|
650
|
+
*len += RSTRING_LEN(str);
|
651
|
+
|
652
|
+
return rb_yield(str);
|
653
|
+
}
|
654
|
+
|
655
|
+
static VALUE wrap_each(struct clogger *c)
|
656
|
+
{
|
657
|
+
VALUE body = RARRAY_PTR(c->response)[2];
|
658
|
+
|
659
|
+
rb_need_block();
|
660
|
+
c->body_bytes_sent = 0;
|
661
|
+
rb_iterate(rb_each, body, body_iter_i, (VALUE)&c->body_bytes_sent);
|
662
|
+
|
663
|
+
return body;
|
664
|
+
}
|
665
|
+
|
666
|
+
/**
|
667
|
+
* call-seq:
|
668
|
+
* clogger.each { |part| socket.write(part) }
|
669
|
+
*
|
670
|
+
* Delegates the body#each call to the underlying +body+ object
|
671
|
+
* while tracking the number of bytes yielded. This will log
|
672
|
+
* the request.
|
673
|
+
*/
|
674
|
+
static VALUE clogger_each(VALUE self)
|
675
|
+
{
|
676
|
+
struct clogger *c = clogger_get(self);
|
677
|
+
|
678
|
+
return rb_ensure(wrap_each, (VALUE)c, cwrite, (VALUE)c);
|
679
|
+
}
|
680
|
+
|
681
|
+
/**
|
682
|
+
* call-seq:
|
683
|
+
* clogger.close
|
684
|
+
*
|
685
|
+
* Delegates the body#close call to the underlying +body+ object.
|
686
|
+
* This is only used when Clogger is wrapping the +body+ of a Rack
|
687
|
+
* response and should be automatically called by the web server.
|
688
|
+
*/
|
689
|
+
static VALUE clogger_close(VALUE self)
|
690
|
+
{
|
691
|
+
struct clogger *c = clogger_get(self);
|
692
|
+
|
693
|
+
return rb_funcall(RARRAY_PTR(c->response)[2], close_id, 0);
|
694
|
+
}
|
695
|
+
|
696
|
+
/* :nodoc: */
|
697
|
+
static VALUE clogger_fileno(VALUE self)
|
698
|
+
{
|
699
|
+
struct clogger *c = clogger_get(self);
|
700
|
+
|
701
|
+
return c->fd < 0 ? Qnil : INT2NUM(c->fd);
|
702
|
+
}
|
703
|
+
|
704
|
+
static void ccall(struct clogger *c, VALUE env)
|
705
|
+
{
|
706
|
+
gettimeofday(&c->tv_start, NULL);
|
707
|
+
c->env = env;
|
708
|
+
c->cookies = Qfalse;
|
709
|
+
c->response = rb_funcall(c->app, call_id, 1, env);
|
710
|
+
}
|
711
|
+
|
712
|
+
/*
|
713
|
+
* call-seq:
|
714
|
+
* clogger.call(env) => [ status, headers, body ]
|
715
|
+
*
|
716
|
+
* calls the wrapped Rack application with +env+, returns the
|
717
|
+
* [status, headers, body ] tuplet required by Rack.
|
718
|
+
*/
|
719
|
+
static VALUE clogger_call(VALUE self, VALUE env)
|
720
|
+
{
|
721
|
+
struct clogger *c = clogger_get(self);
|
722
|
+
|
723
|
+
if (c->wrap_body) {
|
724
|
+
VALUE tmp;
|
725
|
+
|
726
|
+
if (c->reentrant < 0) {
|
727
|
+
tmp = rb_hash_aref(env, g_rack_multithread);
|
728
|
+
c->reentrant = Qfalse == tmp ? 0 : 1;
|
729
|
+
}
|
730
|
+
if (c->reentrant) {
|
731
|
+
self = rb_obj_dup(self);
|
732
|
+
c = clogger_get(self);
|
733
|
+
}
|
734
|
+
|
735
|
+
ccall(c, env);
|
736
|
+
tmp = rb_ary_dup(c->response);
|
737
|
+
rb_ary_store(tmp, 2, self);
|
738
|
+
return tmp;
|
739
|
+
}
|
740
|
+
|
741
|
+
ccall(c, env);
|
742
|
+
cwrite(c);
|
743
|
+
|
744
|
+
return c->response;
|
745
|
+
}
|
746
|
+
|
747
|
+
/* :nodoc */
|
748
|
+
static VALUE clogger_init_copy(VALUE clone, VALUE orig)
|
749
|
+
{
|
750
|
+
struct clogger *a = clogger_get(orig);
|
751
|
+
struct clogger *b = clogger_get(clone);
|
752
|
+
|
753
|
+
memcpy(b, a, sizeof(struct clogger));
|
754
|
+
init_buffers(b);
|
755
|
+
|
756
|
+
return clone;
|
757
|
+
}
|
758
|
+
|
759
|
+
#define CONST_GLOBAL_STR2(var, val) do { \
|
760
|
+
g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
|
761
|
+
rb_global_variable(&g_##var); \
|
762
|
+
} while (0)
|
763
|
+
|
764
|
+
#define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
|
765
|
+
|
766
|
+
void Init_clogger_ext(void)
|
767
|
+
{
|
768
|
+
ltlt_id = rb_intern("<<");
|
769
|
+
call_id = rb_intern("call");
|
770
|
+
each_id = rb_intern("each");
|
771
|
+
close_id = rb_intern("close");
|
772
|
+
to_i_id = rb_intern("to_i");
|
773
|
+
to_s_id = rb_intern("to_s");
|
774
|
+
size_id = rb_intern("size");
|
775
|
+
cClogger = rb_define_class("Clogger", rb_cObject);
|
776
|
+
mFormat = rb_define_module_under(cClogger, "Format");
|
777
|
+
rb_define_alloc_func(cClogger, clogger_alloc);
|
778
|
+
rb_define_method(cClogger, "initialize", clogger_init, -1);
|
779
|
+
rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
|
780
|
+
rb_define_method(cClogger, "call", clogger_call, 1);
|
781
|
+
rb_define_method(cClogger, "each", clogger_each, 0);
|
782
|
+
rb_define_method(cClogger, "close", clogger_close, 0);
|
783
|
+
rb_define_method(cClogger, "fileno", clogger_fileno, 0);
|
784
|
+
rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
|
785
|
+
rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
|
786
|
+
CONST_GLOBAL_STR(REMOTE_ADDR);
|
787
|
+
CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
|
788
|
+
CONST_GLOBAL_STR(REQUEST_METHOD);
|
789
|
+
CONST_GLOBAL_STR(PATH_INFO);
|
790
|
+
CONST_GLOBAL_STR(QUERY_STRING);
|
791
|
+
CONST_GLOBAL_STR(HTTP_VERSION);
|
792
|
+
CONST_GLOBAL_STR2(rack_errors, "rack.errors");
|
793
|
+
CONST_GLOBAL_STR2(rack_input, "rack.input");
|
794
|
+
CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
|
795
|
+
CONST_GLOBAL_STR2(dash, "-");
|
796
|
+
CONST_GLOBAL_STR2(empty, "");
|
797
|
+
CONST_GLOBAL_STR2(space, " ");
|
798
|
+
CONST_GLOBAL_STR2(question_mark, "?");
|
799
|
+
CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
|
800
|
+
}
|