clogger 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ Makefile
2
+ *.log
3
+ *.rbc
4
+ *.so
5
+ *.o
6
+ *.a
7
+ *.bundle
8
+ .DS_Store
9
+ /.config
10
+ /InstalledFiles
11
+ /doc
12
+ /local.mk
13
+ /pkg
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
@@ -0,0 +1,5 @@
1
+ === 0.0.1 / 2009-08-28
2
+
3
+ * 1 major enhancement
4
+
5
+ * initial release
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
+ }