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
,
74 off_t body_bytes_sent
;
75 struct timeval tv_start
;
80 int reentrant
; /* tri-state, -1:auto, 1/0 true/false */
90 static ID sq_brace_id
;
92 static VALUE cClogger
;
94 static VALUE cHeaderHash
;
96 /* common hash lookup keys */
97 static VALUE g_HTTP_X_FORWARDED_FOR
;
98 static VALUE g_REMOTE_ADDR
;
99 static VALUE g_REQUEST_METHOD
;
100 static VALUE g_PATH_INFO
;
101 static VALUE g_REQUEST_URI
;
102 static VALUE g_QUERY_STRING
;
103 static VALUE g_HTTP_VERSION
;
104 static VALUE g_rack_errors
;
105 static VALUE g_rack_input
;
106 static VALUE g_rack_multithread
;
108 static VALUE g_empty
;
109 static VALUE g_space
;
110 static VALUE g_question_mark
;
111 static VALUE g_rack_request_cookie_hash
;
113 #define LOG_BUF_INIT_SIZE 128
115 static void init_buffers(struct clogger
*c
)
117 c
->log_buf
= rb_str_buf_new(LOG_BUF_INIT_SIZE
);
120 static inline int need_escape(unsigned c
)
123 return !!(c
== '\'' || c
== '"' || (c
>= 0 && c
<= 0x1f));
126 /* we are encoding-agnostic, clients can send us all sorts of junk */
127 static VALUE
byte_xs(VALUE from
)
129 static const char esc
[] = "0123456789ABCDEF";
130 unsigned char *new_ptr
;
131 unsigned char *ptr
= (unsigned char *)RSTRING_PTR(from
);
132 long len
= RSTRING_LEN(from
);
136 for (; --len
>= 0; ptr
++) {
139 if (unlikely(need_escape(c
)))
140 new_len
+= 3; /* { '\', 'x', 'X', 'X' } */
143 len
= RSTRING_LEN(from
);
147 rv
= rb_str_new(0, new_len
);
148 new_ptr
= (unsigned char *)RSTRING_PTR(rv
);
149 ptr
= (unsigned char *)RSTRING_PTR(from
);
150 for (; --len
>= 0; ptr
++) {
153 if (unlikely(need_escape(c
))) {
156 *new_ptr
++ = esc
[c
>> 4];
157 *new_ptr
++ = esc
[c
& 0xf];
162 assert(RSTRING_PTR(rv
)[RSTRING_LEN(rv
)] == '\0');
167 static void clogger_mark(void *ptr
)
169 struct clogger
*c
= ptr
;
172 rb_gc_mark(c
->fmt_ops
);
173 rb_gc_mark(c
->logger
);
174 rb_gc_mark(c
->log_buf
);
176 rb_gc_mark(c
->cookies
);
177 rb_gc_mark(c
->status
);
178 rb_gc_mark(c
->headers
);
182 static VALUE
clogger_alloc(VALUE klass
)
186 return Data_Make_Struct(klass
, struct clogger
, clogger_mark
, 0, c
);
189 static struct clogger
*clogger_get(VALUE self
)
193 Data_Get_Struct(self
, struct clogger
, c
);
198 static VALUE
obj_fileno(VALUE obj
)
200 return rb_funcall(obj
, rb_intern("fileno"), 0);
203 static VALUE
obj_enable_sync(VALUE obj
)
205 return rb_funcall(obj
, rb_intern("sync="), 1, Qtrue
);
208 /* only for writing to regular files, not stupid crap like NFS */
209 static void write_full(int fd
, const void *buf
, size_t count
)
214 r
= write(fd
, buf
, count
);
216 if (r
== count
) { /* overwhelmingly likely */
222 if (errno
== EINTR
|| errno
== EAGAIN
)
223 continue; /* poor souls on NFS and like: */
226 rb_sys_fail("write");
232 * allow us to use write_full() iff we detect a blocking file
233 * descriptor that wouldn't play nicely with Ruby threading/fibers
235 static int raw_fd(VALUE fileno
)
237 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
243 fd
= NUM2INT(fileno
);
245 flags
= fcntl(fd
, F_GETFL
);
247 rb_sys_fail("fcntl");
249 return (flags
& O_NONBLOCK
) ? -1 : fd
;
250 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
252 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
256 static VALUE
clogger_reentrant(VALUE self
)
258 return clogger_get(self
)->reentrant
== 0 ? Qfalse
: Qtrue
;
262 static VALUE
clogger_wrap_body(VALUE self
)
264 return clogger_get(self
)->wrap_body
== 0 ? Qfalse
: Qtrue
;
267 static void append_status(struct clogger
*c
)
269 char buf
[sizeof("999")];
271 VALUE status
= c
->status
;
273 if (TYPE(status
) != T_FIXNUM
) {
274 status
= rb_funcall(status
, to_i_id
, 0);
275 /* no way it's a valid status code (at least not HTTP/1.1) */
276 if (TYPE(status
) != T_FIXNUM
) {
277 rb_str_buf_append(c
->log_buf
, g_dash
);
282 nr
= NUM2INT(status
);
283 if (nr
>= 100 && nr
<= 999) {
284 nr
= snprintf(buf
, sizeof(buf
), "%03d", nr
);
286 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
288 /* raise?, swap for 500? */
289 rb_str_buf_append(c
->log_buf
, g_dash
);
293 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
294 static void append_ip(struct clogger
*c
)
297 VALUE tmp
= rb_hash_aref(env
, g_HTTP_X_FORWARDED_FOR
);
300 /* can't be faked on any real server, so no escape */
301 tmp
= rb_hash_aref(env
, g_REMOTE_ADDR
);
307 rb_str_buf_append(c
->log_buf
, tmp
);
310 static void append_body_bytes_sent(struct clogger
*c
)
312 char buf
[(sizeof(off_t
) * 8) / 3 + 1];
313 const char *fmt
= sizeof(off_t
) == sizeof(long) ? "%ld" : "%lld";
314 int nr
= snprintf(buf
, sizeof(buf
), fmt
, c
->body_bytes_sent
);
316 assert(nr
> 0 && nr
< sizeof(buf
));
317 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
320 static void append_tv(struct clogger
*c
, const VALUE
*op
, struct timeval
*tv
)
322 char buf
[sizeof(".000000") + ((sizeof(tv
->tv_sec
) * 8) / 3)];
324 char *fmt
= RSTRING_PTR(op
[1]);
325 int div
= NUM2INT(op
[2]);
327 nr
= snprintf(buf
, sizeof(buf
), fmt
,
328 (int)tv
->tv_sec
, (int)(tv
->tv_usec
/ div
));
329 assert(nr
> 0 && nr
< sizeof(buf
));
330 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
333 static void append_request_time_fmt(struct clogger
*c
, const VALUE
*op
)
335 struct timeval now
, d
;
337 gettimeofday(&now
, NULL
);
338 timersub(&now
, &c
->tv_start
, &d
);
339 append_tv(c
, op
, &d
);
342 static void append_time_fmt(struct clogger
*c
, const VALUE
*op
)
346 gettimeofday(&now
, NULL
);
347 append_tv(c
, op
, &now
);
350 static void append_request_uri(struct clogger
*c
)
354 tmp
= rb_hash_aref(c
->env
, g_REQUEST_URI
);
356 tmp
= rb_hash_aref(c
->env
, g_PATH_INFO
);
358 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
359 tmp
= rb_hash_aref(c
->env
, g_QUERY_STRING
);
360 if (!NIL_P(tmp
) && RSTRING_LEN(tmp
) != 0) {
361 rb_str_buf_append(c
->log_buf
, g_question_mark
);
362 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
365 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
369 static void append_request(struct clogger
*c
)
373 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
374 tmp
= rb_hash_aref(c
->env
, g_REQUEST_METHOD
);
376 rb_str_buf_append(c
->log_buf
, tmp
);
378 rb_str_buf_append(c
->log_buf
, g_space
);
380 append_request_uri(c
);
382 /* HTTP_VERSION can be injected by malicious clients */
383 tmp
= rb_hash_aref(c
->env
, g_HTTP_VERSION
);
385 rb_str_buf_append(c
->log_buf
, g_space
);
386 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
390 static void append_request_length(struct clogger
*c
)
392 VALUE tmp
= rb_hash_aref(c
->env
, g_rack_input
);
394 rb_str_buf_append(c
->log_buf
, g_dash
);
396 tmp
= rb_funcall(tmp
, size_id
, 0);
397 rb_str_buf_append(c
->log_buf
, rb_funcall(tmp
, to_s_id
, 0));
401 static void append_time(struct clogger
*c
, enum clogger_opcode op
, VALUE fmt
)
403 /* you'd have to be a moron to use formats this big... */
404 char buf
[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
407 time_t t
= time(NULL
);
409 if (op
== CL_OP_TIME_LOCAL
)
410 localtime_r(&t
, &tmp
);
411 else if (op
== CL_OP_TIME_UTC
)
414 assert(0 && "unknown op");
416 nr
= strftime(buf
, sizeof(buf
), RSTRING_PTR(fmt
), &tmp
);
417 if (nr
== 0 || nr
== sizeof(buf
))
418 rb_str_buf_append(c
->log_buf
, g_dash
);
420 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
423 static void append_pid(struct clogger
*c
)
425 char buf
[(sizeof(pid_t
) * 8) / 3 + 1];
426 int nr
= snprintf(buf
, sizeof(buf
), "%d", (int)getpid());
428 assert(nr
> 0 && nr
< sizeof(buf
));
429 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
432 static void append_eval(struct clogger
*c
, VALUE str
)
435 VALUE rv
= rb_eval_string_protect(RSTRING_PTR(str
), &state
);
437 rv
= state
== 0 ? rb_obj_as_string(rv
) : g_dash
;
438 rb_str_buf_append(c
->log_buf
, rv
);
441 static void append_cookie(struct clogger
*c
, VALUE key
)
445 if (c
->cookies
== Qfalse
)
446 c
->cookies
= rb_hash_aref(c
->env
, g_rack_request_cookie_hash
);
448 if (NIL_P(c
->cookies
)) {
451 cookie
= rb_hash_aref(c
->cookies
, key
);
455 rb_str_buf_append(c
->log_buf
, cookie
);
458 static void append_request_env(struct clogger
*c
, VALUE key
)
460 VALUE tmp
= rb_hash_aref(c
->env
, key
);
462 tmp
= NIL_P(tmp
) ? g_dash
: byte_xs(rb_obj_as_string(tmp
));
463 rb_str_buf_append(c
->log_buf
, tmp
);
466 static void append_response(struct clogger
*c
, VALUE key
)
470 assert(rb_obj_class(c
->headers
) == cHeaderHash
);
472 v
= rb_funcall(c
->headers
, sq_brace_id
, 1, key
);
473 v
= NIL_P(v
) ? g_dash
: byte_xs(rb_obj_as_string(v
));
474 rb_str_buf_append(c
->log_buf
, v
);
477 static void special_var(struct clogger
*c
, enum clogger_special var
)
480 case CL_SP_body_bytes_sent
:
481 append_body_bytes_sent(c
);
489 case CL_SP_request_length
:
490 append_request_length(c
);
492 case CL_SP_response_length
:
493 if (c
->body_bytes_sent
== 0)
494 rb_str_buf_append(c
->log_buf
, g_dash
);
496 append_body_bytes_sent(c
);
504 case CL_SP_request_uri
:
505 append_request_uri(c
);
509 static VALUE
cwrite(struct clogger
*c
)
511 const VALUE ops
= c
->fmt_ops
;
512 const VALUE
*ary
= RARRAY_PTR(ops
);
513 long i
= RARRAY_LEN(ops
);
514 VALUE dst
= c
->log_buf
;
516 rb_str_set_len(dst
, 0);
518 for (; --i
>= 0; ary
++) {
519 const VALUE
*op
= RARRAY_PTR(*ary
);
520 enum clogger_opcode opcode
= NUM2INT(op
[0]);
524 rb_str_buf_append(dst
, op
[1]);
527 append_request_env(c
, op
[1]);
530 append_response(c
, op
[1]);
533 special_var(c
, NUM2INT(op
[1]));
536 append_eval(c
, op
[1]);
538 case CL_OP_TIME_LOCAL
:
540 append_time(c
, opcode
, op
[1]);
542 case CL_OP_REQUEST_TIME
:
543 append_request_time_fmt(c
, op
);
546 append_time_fmt(c
, op
);
549 append_cookie(c
, op
[1]);
555 write_full(c
->fd
, RSTRING_PTR(dst
), RSTRING_LEN(dst
));
557 VALUE logger
= c
->logger
;
560 logger
= rb_hash_aref(c
->env
, g_rack_errors
);
561 rb_funcall(logger
, ltlt_id
, 1, dst
);
569 * Clogger.new(app, :logger => $stderr, :format => string) => obj
571 * Creates a new Clogger object that wraps +app+. +:logger+ may
572 * be any object that responds to the "<<" method with a string argument.
574 static VALUE
clogger_init(int argc
, VALUE
*argv
, VALUE self
)
576 struct clogger
*c
= clogger_get(self
);
578 VALUE fmt
= rb_const_get(mFormat
, rb_intern("Common"));
580 rb_scan_args(argc
, argv
, "11", &c
->app
, &o
);
583 c
->reentrant
= -1; /* auto-detect */
585 if (TYPE(o
) == T_HASH
) {
588 c
->logger
= rb_hash_aref(o
, ID2SYM(rb_intern("logger")));
589 if (!NIL_P(c
->logger
)) {
590 rb_rescue(obj_enable_sync
, c
->logger
, 0, 0);
591 c
->fd
= raw_fd(rb_rescue(obj_fileno
, c
->logger
, 0, 0));
594 tmp
= rb_hash_aref(o
, ID2SYM(rb_intern("format")));
600 c
->fmt_ops
= rb_funcall(self
, rb_intern("compile_format"), 2, fmt
, o
);
602 if (Qtrue
== rb_funcall(self
, rb_intern("need_response_headers?"),
605 if (Qtrue
== rb_funcall(self
, rb_intern("need_wrap_body?"),
612 static VALUE
body_iter_i(VALUE str
, VALUE memop
)
614 off_t
*len
= (off_t
*)memop
;
616 str
= rb_obj_as_string(str
);
617 *len
+= RSTRING_LEN(str
);
619 return rb_yield(str
);
622 static VALUE
wrap_each(struct clogger
*c
)
624 c
->body_bytes_sent
= 0;
625 rb_iterate(rb_each
, c
->body
, body_iter_i
, (VALUE
)&c
->body_bytes_sent
);
632 * clogger.each { |part| socket.write(part) }
634 * Delegates the body#each call to the underlying +body+ object
635 * while tracking the number of bytes yielded. This will log
638 static VALUE
clogger_each(VALUE self
)
640 struct clogger
*c
= clogger_get(self
);
644 return rb_ensure(wrap_each
, (VALUE
)c
, cwrite
, (VALUE
)c
);
651 * Delegates the body#close call to the underlying +body+ object.
652 * This is only used when Clogger is wrapping the +body+ of a Rack
653 * response and should be automatically called by the web server.
655 static VALUE
clogger_close(VALUE self
)
657 struct clogger
*c
= clogger_get(self
);
659 return rb_funcall(c
->body
, close_id
, 0);
663 static VALUE
clogger_fileno(VALUE self
)
665 struct clogger
*c
= clogger_get(self
);
667 return c
->fd
< 0 ? Qnil
: INT2NUM(c
->fd
);
670 static VALUE
ccall(struct clogger
*c
, VALUE env
)
674 gettimeofday(&c
->tv_start
, NULL
);
677 rv
= rb_funcall(c
->app
, call_id
, 1, env
);
678 if (TYPE(rv
) == T_ARRAY
&& RARRAY_LEN(rv
) == 3) {
679 VALUE
*tmp
= RARRAY_PTR(rv
);
685 if (c
->need_resp
&& cHeaderHash
!= rb_obj_class(c
->headers
)) {
686 c
->headers
= rb_funcall(cHeaderHash
, new_id
, 1, tmp
[1]);
689 rb_ary_store(rv
, 1, c
->headers
);
692 c
->status
= INT2NUM(500);
693 c
->headers
= c
->body
= rb_ary_new();
695 rb_raise(rb_eTypeError
,
696 "app response not a 3 element Array: %s",
697 RSTRING_PTR(rb_inspect(rv
)));
705 * clogger.call(env) => [ status, headers, body ]
707 * calls the wrapped Rack application with +env+, returns the
708 * [status, headers, body ] tuplet required by Rack.
710 static VALUE
clogger_call(VALUE self
, VALUE env
)
712 struct clogger
*c
= clogger_get(self
);
716 if (c
->reentrant
< 0) {
717 VALUE tmp
= rb_hash_aref(env
, g_rack_multithread
);
718 c
->reentrant
= Qfalse
== tmp
? 0 : 1;
721 self
= rb_obj_dup(self
);
722 c
= clogger_get(self
);
728 rb_ary_store(rv
, 2, self
);
740 static VALUE
clogger_init_copy(VALUE clone
, VALUE orig
)
742 struct clogger
*a
= clogger_get(orig
);
743 struct clogger
*b
= clogger_get(clone
);
745 memcpy(b
, a
, sizeof(struct clogger
));
751 #define CONST_GLOBAL_STR2(var, val) do { \
752 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
753 rb_global_variable(&g_##var); \
756 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
758 static void init_rack_utils_header_hash(void)
762 extra
double quotes below are to disable
rdoc (and so is avoiding comments
)
763 let me know
if there is a better way
...
766 mRack
= rb_define_module("Rack""");
767 mUtils
= rb_define_module_under(mRack
, "Utils""");
768 cHeaderHash
= rb_define_class_under(mUtils
, "HeaderHash""", rb_cHash
);
771 void Init_clogger_ext(void)
773 ltlt_id
= rb_intern("<<");
774 call_id
= rb_intern("call");
775 each_id
= rb_intern("each");
776 close_id
= rb_intern("close");
777 to_i_id
= rb_intern("to_i");
778 to_s_id
= rb_intern("to_s");
779 size_id
= rb_intern("size");
780 sq_brace_id
= rb_intern("[]");
781 new_id
= rb_intern("new");
782 cClogger
= rb_define_class("Clogger", rb_cObject
);
783 mFormat
= rb_define_module_under(cClogger
, "Format");
784 rb_define_alloc_func(cClogger
, clogger_alloc
);
785 rb_define_method(cClogger
, "initialize", clogger_init
, -1);
786 rb_define_method(cClogger
, "initialize_copy", clogger_init_copy
, 1);
787 rb_define_method(cClogger
, "call", clogger_call
, 1);
788 rb_define_method(cClogger
, "each", clogger_each
, 0);
789 rb_define_method(cClogger
, "close", clogger_close
, 0);
790 rb_define_method(cClogger
, "fileno", clogger_fileno
, 0);
791 rb_define_method(cClogger
, "wrap_body?", clogger_wrap_body
, 0);
792 rb_define_method(cClogger
, "reentrant?", clogger_reentrant
, 0);
793 CONST_GLOBAL_STR(REMOTE_ADDR
);
794 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR
);
795 CONST_GLOBAL_STR(REQUEST_METHOD
);
796 CONST_GLOBAL_STR(PATH_INFO
);
797 CONST_GLOBAL_STR(QUERY_STRING
);
798 CONST_GLOBAL_STR(REQUEST_URI
);
799 CONST_GLOBAL_STR(HTTP_VERSION
);
800 CONST_GLOBAL_STR2(rack_errors
, "rack.errors");
801 CONST_GLOBAL_STR2(rack_input
, "rack.input");
802 CONST_GLOBAL_STR2(rack_multithread
, "rack.multithread");
803 CONST_GLOBAL_STR2(dash
, "-");
804 CONST_GLOBAL_STR2(empty
, "");
805 CONST_GLOBAL_STR2(space
, " ");
806 CONST_GLOBAL_STR2(question_mark
, "?");
807 CONST_GLOBAL_STR2(rack_request_cookie_hash
, "rack.request.cookie_hash");
808 init_rack_utils_header_hash();