12 #include "ruby_1_9_compat.h"
14 /* in case _BSD_SOURCE doesn't give us this macro */
16 # define timersub(a, b, result) \
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) { \
22 (result)->tv_usec += 1000000; \
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)
33 # define unlikely(x) (x)
34 # define likely(x) (x)
50 enum clogger_special
{
51 CL_SP_body_bytes_sent
= 0,
55 CL_SP_response_length
,
72 off_t body_bytes_sent
;
73 struct timeval tv_start
;
78 int reentrant
; /* tri-state, -1:auto, 1/0 true/false */
88 static VALUE cClogger
;
91 /* common hash lookup keys */
92 static VALUE g_HTTP_X_FORWARDED_FOR
;
93 static VALUE g_REMOTE_ADDR
;
94 static VALUE g_REQUEST_METHOD
;
95 static VALUE g_PATH_INFO
;
96 static VALUE g_REQUEST_URI
;
97 static VALUE g_QUERY_STRING
;
98 static VALUE g_HTTP_VERSION
;
99 static VALUE g_rack_errors
;
100 static VALUE g_rack_input
;
101 static VALUE g_rack_multithread
;
103 static VALUE g_empty
;
104 static VALUE g_space
;
105 static VALUE g_question_mark
;
106 static VALUE g_rack_request_cookie_hash
;
107 static VALUE g_bad_app_response
;
109 #define LOG_BUF_INIT_SIZE 128
111 static void init_buffers(struct clogger
*c
)
113 c
->log_buf
= rb_str_buf_new(LOG_BUF_INIT_SIZE
);
116 static inline int need_escape(unsigned c
)
119 return !!(c
== '\'' || c
== '"' || (c
>= 0 && c
<= 0x1f));
122 /* we are encoding-agnostic, clients can send us all sorts of junk */
123 static VALUE
byte_xs(VALUE from
)
125 static const char esc
[] = "0123456789ABCDEF";
126 unsigned char *new_ptr
;
127 unsigned char *ptr
= (unsigned char *)RSTRING_PTR(from
);
128 long len
= RSTRING_LEN(from
);
132 for (; --len
>= 0; ptr
++) {
135 if (unlikely(need_escape(c
)))
136 new_len
+= 3; /* { '\', 'x', 'X', 'X' } */
139 len
= RSTRING_LEN(from
);
143 rv
= rb_str_new(0, new_len
);
144 new_ptr
= (unsigned char *)RSTRING_PTR(rv
);
145 ptr
= (unsigned char *)RSTRING_PTR(from
);
146 for (; --len
>= 0; ptr
++) {
149 if (unlikely(need_escape(c
))) {
152 *new_ptr
++ = esc
[c
>> 4];
153 *new_ptr
++ = esc
[c
& 0xf];
158 assert(RSTRING_PTR(rv
)[RSTRING_LEN(rv
)] == '\0');
163 /* strcasecmp isn't locale independent, so we roll our own */
164 static int str_case_eq(VALUE a
, VALUE b
)
166 long alen
= RSTRING_LEN(a
);
167 long blen
= RSTRING_LEN(b
);
170 const char *aptr
= RSTRING_PTR(a
);
171 const char *bptr
= RSTRING_PTR(b
);
173 for (; alen
--; ++aptr
, ++bptr
) {
175 || (*aptr
>= 'A' && *aptr
<= 'Z' &&
176 (*aptr
| 0x20) == *bptr
))
185 struct response_ops
{ long nr
; VALUE ops
; };
187 /* this can be worse than O(M*N) :<... but C loops are fast ... */
188 static VALUE
swap_sent_headers(VALUE kv
, VALUE memo
)
190 struct response_ops
*tmp
= (struct response_ops
*)memo
;
191 VALUE key
= rb_obj_as_string(RARRAY_PTR(kv
)[0]);
192 long i
= RARRAY_LEN(tmp
->ops
);
193 VALUE
*ary
= RARRAY_PTR(tmp
->ops
);
196 for (; --i
>= 0; ary
++) {
197 VALUE
*op
= RARRAY_PTR(*ary
);
198 enum clogger_opcode opcode
= NUM2INT(op
[0]);
200 if (opcode
!= CL_OP_RESPONSE
)
202 assert(RARRAY_LEN(*ary
) == 2);
203 if (!str_case_eq(key
, op
[1]))
206 value
= RARRAY_PTR(kv
)[1];
207 op
[0] = INT2NUM(CL_OP_LITERAL
);
208 op
[1] = byte_xs(rb_obj_as_string(value
));
217 static VALUE
sent_headers_ops(struct clogger
*c
)
219 struct response_ops tmp
;
227 tmp
.ops
= rb_ary_dup(c
->fmt_ops
);
228 len
= RARRAY_LEN(tmp
.ops
);
229 ary
= RARRAY_PTR(tmp
.ops
);
231 for (i
= 0; i
< len
; ++i
) {
232 VALUE
*op
= RARRAY_PTR(ary
[i
]);
234 if (NUM2INT(op
[0]) == CL_OP_RESPONSE
) {
235 assert(RARRAY_LEN(ary
[i
]) == 2);
236 ary
[i
] = rb_ary_dup(ary
[i
]);
241 rb_iterate(rb_each
, RARRAY_PTR(c
->response
)[1],
242 swap_sent_headers
, (VALUE
)&tmp
);
247 static void clogger_mark(void *ptr
)
249 struct clogger
*c
= ptr
;
252 rb_gc_mark(c
->fmt_ops
);
253 rb_gc_mark(c
->logger
);
254 rb_gc_mark(c
->log_buf
);
256 rb_gc_mark(c
->cookies
);
257 rb_gc_mark(c
->response
);
260 static VALUE
clogger_alloc(VALUE klass
)
264 return Data_Make_Struct(klass
, struct clogger
, clogger_mark
, 0, c
);
267 static struct clogger
*clogger_get(VALUE self
)
271 Data_Get_Struct(self
, struct clogger
, c
);
276 static VALUE
obj_fileno(VALUE obj
)
278 return rb_funcall(obj
, rb_intern("fileno"), 0);
281 /* only for writing to regular files, not stupid crap like NFS */
282 static void write_full(int fd
, const void *buf
, size_t count
)
287 r
= write(fd
, buf
, count
);
289 if (r
== count
) { /* overwhelmingly likely */
295 if (errno
== EINTR
|| errno
== EAGAIN
)
296 continue; /* poor souls on NFS and like: */
299 rb_sys_fail("write");
305 * allow us to use write_full() iff we detect a blocking file
306 * descriptor that wouldn't play nicely with Ruby threading/fibers
308 static int raw_fd(VALUE fileno
)
310 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
316 fd
= NUM2INT(fileno
);
318 flags
= fcntl(fd
, F_GETFL
);
320 rb_sys_fail("fcntl");
322 return (flags
& O_NONBLOCK
) ? -1 : fd
;
323 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
325 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
329 static VALUE
clogger_reentrant(VALUE self
)
331 return clogger_get(self
)->reentrant
== 0 ? Qfalse
: Qtrue
;
335 static VALUE
clogger_wrap_body(VALUE self
)
337 return clogger_get(self
)->wrap_body
== 0 ? Qfalse
: Qtrue
;
340 static void append_status(struct clogger
*c
)
342 char buf
[sizeof("999")];
344 VALUE status
= RARRAY_PTR(c
->response
)[0];
346 if (TYPE(status
) != T_FIXNUM
) {
347 status
= rb_funcall(status
, to_i_id
, 0);
348 /* no way it's a valid status code (at least not HTTP/1.1) */
349 if (TYPE(status
) != T_FIXNUM
) {
350 rb_str_buf_append(c
->log_buf
, g_dash
);
355 nr
= NUM2INT(status
);
356 if (nr
>= 100 && nr
<= 999) {
357 nr
= snprintf(buf
, sizeof(buf
), "%03d", nr
);
359 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
361 /* raise?, swap for 500? */
362 rb_str_buf_append(c
->log_buf
, g_dash
);
366 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
367 static void append_ip(struct clogger
*c
)
370 VALUE tmp
= rb_hash_aref(env
, g_HTTP_X_FORWARDED_FOR
);
373 /* can't be faked on any real server, so no escape */
374 tmp
= rb_hash_aref(env
, g_REMOTE_ADDR
);
380 rb_str_buf_append(c
->log_buf
, tmp
);
383 static void append_body_bytes_sent(struct clogger
*c
)
385 char buf
[(sizeof(off_t
) * 8) / 3 + 1];
386 const char *fmt
= sizeof(off_t
) == sizeof(long) ? "%ld" : "%lld";
387 int nr
= snprintf(buf
, sizeof(buf
), fmt
, c
->body_bytes_sent
);
389 assert(nr
> 0 && nr
< sizeof(buf
));
390 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
393 static void append_tv(struct clogger
*c
, const VALUE
*op
, struct timeval
*tv
)
395 char buf
[sizeof(".000000") + ((sizeof(tv
->tv_sec
) * 8) / 3)];
397 char *fmt
= RSTRING_PTR(op
[1]);
398 int div
= NUM2INT(op
[2]);
400 nr
= snprintf(buf
, sizeof(buf
), fmt
,
401 (int)tv
->tv_sec
, (int)(tv
->tv_usec
/ div
));
402 assert(nr
> 0 && nr
< sizeof(buf
));
403 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
406 static void append_request_time_fmt(struct clogger
*c
, const VALUE
*op
)
408 struct timeval now
, d
;
410 gettimeofday(&now
, NULL
);
411 timersub(&now
, &c
->tv_start
, &d
);
412 append_tv(c
, op
, &d
);
415 static void append_time_fmt(struct clogger
*c
, const VALUE
*op
)
419 gettimeofday(&now
, NULL
);
420 append_tv(c
, op
, &now
);
423 static void append_request_uri(struct clogger
*c
)
427 tmp
= rb_hash_aref(c
->env
, g_REQUEST_URI
);
429 tmp
= rb_hash_aref(c
->env
, g_PATH_INFO
);
431 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
432 tmp
= rb_hash_aref(c
->env
, g_QUERY_STRING
);
433 if (!NIL_P(tmp
) && RSTRING_LEN(tmp
) != 0) {
434 rb_str_buf_append(c
->log_buf
, g_question_mark
);
435 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
438 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
442 static void append_request(struct clogger
*c
)
446 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
447 tmp
= rb_hash_aref(c
->env
, g_REQUEST_METHOD
);
449 rb_str_buf_append(c
->log_buf
, tmp
);
451 rb_str_buf_append(c
->log_buf
, g_space
);
453 append_request_uri(c
);
455 rb_str_buf_append(c
->log_buf
, g_space
);
457 /* HTTP_VERSION can be injected by malicious clients */
458 tmp
= rb_hash_aref(c
->env
, g_HTTP_VERSION
);
460 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
463 static void append_request_length(struct clogger
*c
)
465 VALUE tmp
= rb_hash_aref(c
->env
, g_rack_input
);
467 rb_str_buf_append(c
->log_buf
, g_dash
);
469 tmp
= rb_funcall(tmp
, size_id
, 0);
470 rb_str_buf_append(c
->log_buf
, rb_funcall(tmp
, to_s_id
, 0));
474 static void append_time(struct clogger
*c
, enum clogger_opcode op
, VALUE fmt
)
476 /* you'd have to be a moron to use formats this big... */
477 char buf
[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
480 time_t t
= time(NULL
);
482 if (op
== CL_OP_TIME_LOCAL
)
483 localtime_r(&t
, &tmp
);
484 else if (op
== CL_OP_TIME_UTC
)
487 assert(0 && "unknown op");
489 nr
= strftime(buf
, sizeof(buf
), RSTRING_PTR(fmt
), &tmp
);
490 if (nr
== 0 || nr
== sizeof(buf
))
491 rb_str_buf_append(c
->log_buf
, g_dash
);
493 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
496 static void append_pid(struct clogger
*c
)
498 char buf
[(sizeof(pid_t
) * 8) / 3 + 1];
499 int nr
= snprintf(buf
, sizeof(buf
), "%d", (int)getpid());
501 assert(nr
> 0 && nr
< sizeof(buf
));
502 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
505 static void append_eval(struct clogger
*c
, VALUE str
)
508 VALUE rv
= rb_eval_string_protect(RSTRING_PTR(str
), &state
);
510 rv
= state
== 0 ? rb_obj_as_string(rv
) : g_dash
;
511 rb_str_buf_append(c
->log_buf
, rv
);
514 static void append_cookie(struct clogger
*c
, VALUE key
)
518 if (c
->cookies
== Qfalse
)
519 c
->cookies
= rb_hash_aref(c
->env
, g_rack_request_cookie_hash
);
521 if (NIL_P(c
->cookies
)) {
524 cookie
= rb_hash_aref(c
->cookies
, key
);
528 rb_str_buf_append(c
->log_buf
, cookie
);
531 static void append_request_env(struct clogger
*c
, VALUE key
)
533 VALUE tmp
= rb_hash_aref(c
->env
, key
);
535 tmp
= NIL_P(tmp
) ? g_dash
: byte_xs(rb_obj_as_string(tmp
));
536 rb_str_buf_append(c
->log_buf
, tmp
);
539 static void special_var(struct clogger
*c
, enum clogger_special var
)
542 case CL_SP_body_bytes_sent
:
543 append_body_bytes_sent(c
);
551 case CL_SP_request_length
:
552 append_request_length(c
);
554 case CL_SP_response_length
:
555 if (c
->body_bytes_sent
== 0)
556 rb_str_buf_append(c
->log_buf
, g_dash
);
558 append_body_bytes_sent(c
);
566 case CL_SP_request_uri
:
567 append_request_uri(c
);
571 static VALUE
cwrite(struct clogger
*c
)
573 const VALUE ops
= sent_headers_ops(c
);
574 const VALUE
*ary
= RARRAY_PTR(ops
);
575 long i
= RARRAY_LEN(ops
);
576 VALUE dst
= c
->log_buf
;
578 rb_str_set_len(dst
, 0);
580 for (; --i
>= 0; ary
++) {
581 const VALUE
*op
= RARRAY_PTR(*ary
);
582 enum clogger_opcode opcode
= NUM2INT(op
[0]);
586 rb_str_buf_append(dst
, op
[1]);
589 append_request_env(c
, op
[1]);
592 /* headers we found already got swapped for literals */
593 rb_str_buf_append(dst
, g_dash
);
596 special_var(c
, NUM2INT(op
[1]));
599 append_eval(c
, op
[1]);
601 case CL_OP_TIME_LOCAL
:
603 append_time(c
, opcode
, op
[1]);
605 case CL_OP_REQUEST_TIME
:
606 append_request_time_fmt(c
, op
);
609 append_time_fmt(c
, op
);
612 append_cookie(c
, op
[1]);
618 write_full(c
->fd
, RSTRING_PTR(dst
), RSTRING_LEN(dst
));
620 VALUE logger
= c
->logger
;
623 logger
= rb_hash_aref(c
->env
, g_rack_errors
);
624 rb_funcall(logger
, ltlt_id
, 1, dst
);
632 * Clogger.new(app, :logger => $stderr, :format => string) => obj
634 * Creates a new Clogger object that wraps +app+. +:logger+ may
635 * be any object that responds to the "<<" method with a string argument.
637 static VALUE
clogger_init(int argc
, VALUE
*argv
, VALUE self
)
639 struct clogger
*c
= clogger_get(self
);
641 VALUE fmt
= rb_const_get(mFormat
, rb_intern("Common"));
643 rb_scan_args(argc
, argv
, "11", &c
->app
, &o
);
646 c
->reentrant
= -1; /* auto-detect */
648 if (TYPE(o
) == T_HASH
) {
651 c
->logger
= rb_hash_aref(o
, ID2SYM(rb_intern("logger")));
652 if (!NIL_P(c
->logger
))
653 c
->fd
= raw_fd(rb_rescue(obj_fileno
, c
->logger
, 0, 0));
655 tmp
= rb_hash_aref(o
, ID2SYM(rb_intern("format")));
661 c
->fmt_ops
= rb_funcall(self
, rb_intern("compile_format"), 1, fmt
);
663 if (Qtrue
== rb_funcall(self
, rb_intern("need_response_headers?"),
666 if (Qtrue
== rb_funcall(self
, rb_intern("need_wrap_body?"),
673 static VALUE
body_iter_i(VALUE str
, VALUE memop
)
675 off_t
*len
= (off_t
*)memop
;
677 str
= rb_obj_as_string(str
);
678 *len
+= RSTRING_LEN(str
);
680 return rb_yield(str
);
683 static VALUE
wrap_each(struct clogger
*c
)
685 VALUE body
= RARRAY_PTR(c
->response
)[2];
687 c
->body_bytes_sent
= 0;
688 rb_iterate(rb_each
, body
, body_iter_i
, (VALUE
)&c
->body_bytes_sent
);
695 * clogger.each { |part| socket.write(part) }
697 * Delegates the body#each call to the underlying +body+ object
698 * while tracking the number of bytes yielded. This will log
701 static VALUE
clogger_each(VALUE self
)
703 struct clogger
*c
= clogger_get(self
);
707 return rb_ensure(wrap_each
, (VALUE
)c
, cwrite
, (VALUE
)c
);
714 * Delegates the body#close call to the underlying +body+ object.
715 * This is only used when Clogger is wrapping the +body+ of a Rack
716 * response and should be automatically called by the web server.
718 static VALUE
clogger_close(VALUE self
)
720 struct clogger
*c
= clogger_get(self
);
722 return rb_funcall(RARRAY_PTR(c
->response
)[2], close_id
, 0);
726 static VALUE
clogger_fileno(VALUE self
)
728 struct clogger
*c
= clogger_get(self
);
730 return c
->fd
< 0 ? Qnil
: INT2NUM(c
->fd
);
733 static void ccall(struct clogger
*c
, VALUE env
)
737 gettimeofday(&c
->tv_start
, NULL
);
740 rv
= rb_funcall(c
->app
, call_id
, 1, env
);
741 if (TYPE(rv
) == T_ARRAY
&& RARRAY_LEN(rv
) == 3) {
744 c
->response
= g_bad_app_response
;
746 rb_raise(rb_eTypeError
,
747 "app response not a 3 element Array: %s",
748 RSTRING_PTR(rb_inspect(rv
)));
754 * clogger.call(env) => [ status, headers, body ]
756 * calls the wrapped Rack application with +env+, returns the
757 * [status, headers, body ] tuplet required by Rack.
759 static VALUE
clogger_call(VALUE self
, VALUE env
)
761 struct clogger
*c
= clogger_get(self
);
766 if (c
->reentrant
< 0) {
767 tmp
= rb_hash_aref(env
, g_rack_multithread
);
768 c
->reentrant
= Qfalse
== tmp
? 0 : 1;
771 self
= rb_obj_dup(self
);
772 c
= clogger_get(self
);
776 tmp
= rb_ary_dup(c
->response
);
777 rb_ary_store(tmp
, 2, self
);
788 static VALUE
clogger_init_copy(VALUE clone
, VALUE orig
)
790 struct clogger
*a
= clogger_get(orig
);
791 struct clogger
*b
= clogger_get(clone
);
793 memcpy(b
, a
, sizeof(struct clogger
));
799 #define CONST_GLOBAL_STR2(var, val) do { \
800 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
801 rb_global_variable(&g_##var); \
804 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
806 static void init_bad_response(void)
808 g_bad_app_response
= rb_ary_new();
809 rb_ary_store(g_bad_app_response
, 0, INT2NUM(500));
810 rb_ary_store(g_bad_app_response
, 1, rb_obj_freeze(rb_hash_new()));
811 rb_ary_store(g_bad_app_response
, 2, rb_obj_freeze(rb_ary_new()));
812 rb_obj_freeze(g_bad_app_response
);
813 rb_global_variable(&g_bad_app_response
);
816 void Init_clogger_ext(void)
818 ltlt_id
= rb_intern("<<");
819 call_id
= rb_intern("call");
820 each_id
= rb_intern("each");
821 close_id
= rb_intern("close");
822 to_i_id
= rb_intern("to_i");
823 to_s_id
= rb_intern("to_s");
824 size_id
= rb_intern("size");
825 cClogger
= rb_define_class("Clogger", rb_cObject
);
826 mFormat
= rb_define_module_under(cClogger
, "Format");
827 rb_define_alloc_func(cClogger
, clogger_alloc
);
828 rb_define_method(cClogger
, "initialize", clogger_init
, -1);
829 rb_define_method(cClogger
, "initialize_copy", clogger_init_copy
, 1);
830 rb_define_method(cClogger
, "call", clogger_call
, 1);
831 rb_define_method(cClogger
, "each", clogger_each
, 0);
832 rb_define_method(cClogger
, "close", clogger_close
, 0);
833 rb_define_method(cClogger
, "fileno", clogger_fileno
, 0);
834 rb_define_method(cClogger
, "wrap_body?", clogger_wrap_body
, 0);
835 rb_define_method(cClogger
, "reentrant?", clogger_reentrant
, 0);
836 CONST_GLOBAL_STR(REMOTE_ADDR
);
837 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR
);
838 CONST_GLOBAL_STR(REQUEST_METHOD
);
839 CONST_GLOBAL_STR(PATH_INFO
);
840 CONST_GLOBAL_STR(QUERY_STRING
);
841 CONST_GLOBAL_STR(REQUEST_URI
);
842 CONST_GLOBAL_STR(HTTP_VERSION
);
843 CONST_GLOBAL_STR2(rack_errors
, "rack.errors");
844 CONST_GLOBAL_STR2(rack_input
, "rack.input");
845 CONST_GLOBAL_STR2(rack_multithread
, "rack.multithread");
846 CONST_GLOBAL_STR2(dash
, "-");
847 CONST_GLOBAL_STR2(empty
, "");
848 CONST_GLOBAL_STR2(space
, " ");
849 CONST_GLOBAL_STR2(question_mark
, "?");
850 CONST_GLOBAL_STR2(rack_request_cookie_hash
, "rack.request.cookie_hash");