use RB_GC_GUARD instead of volatile
[clogger.git] / ext / clogger_ext / clogger.c
blobed0153490eb6a8fffffbaa63efa75eba7a092560
1 #include <ruby.h>
2 #ifdef HAVE_RUBY_IO_H
3 # include <ruby/io.h>
4 #else
5 # include <rubyio.h>
6 #endif
7 #include <assert.h>
8 #include <unistd.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <sys/time.h>
12 #include <errno.h>
13 #ifdef HAVE_FCNTL_H
14 # include <fcntl.h>
15 #endif
16 #ifndef _POSIX_C_SOURCE
17 # define _POSIX_C_SOURCE 200112L
18 #endif
19 #include <time.h>
20 #include <stdlib.h>
21 #include <stdio.h> /* snprintf */
22 #include "ruby_1_9_compat.h"
23 #include "broken_system_compat.h"
24 #include "blocking_helpers.h"
27 * Availability of a monotonic clock needs to be detected at runtime
28 * since we could've been built on a different system than we're run
29 * under.
31 static clockid_t hopefully_CLOCK_MONOTONIC;
33 static void check_clock(void)
35 struct timespec now;
37 hopefully_CLOCK_MONOTONIC = CLOCK_MONOTONIC;
39 /* we can't check this reliably at compile time */
40 if (clock_gettime(CLOCK_MONOTONIC, &now) == 0)
41 return;
43 if (clock_gettime(CLOCK_REALTIME, &now) == 0) {
44 hopefully_CLOCK_MONOTONIC = CLOCK_REALTIME;
45 rb_warn("CLOCK_MONOTONIC not available, "
46 "falling back to CLOCK_REALTIME");
48 rb_warn("clock_gettime() totally broken, " \
49 "falling back to pure Ruby Clogger");
50 rb_raise(rb_eLoadError, "clock_gettime() broken");
53 static void clock_diff(struct timespec *a, const struct timespec *b)
55 a->tv_sec -= b->tv_sec;
56 a->tv_nsec -= b->tv_nsec;
57 if (a->tv_nsec < 0) {
58 --a->tv_sec;
59 a->tv_nsec += 1000000000;
63 /* give GCC hints for better branch prediction
64 * (we layout branches so that ASCII characters are handled faster) */
65 #if defined(__GNUC__) && (__GNUC__ >= 3)
66 # define likely(x) __builtin_expect (!!(x), 1)
67 # define unlikely(x) __builtin_expect (!!(x), 0)
68 #else
69 # define unlikely(x) (x)
70 # define likely(x) (x)
71 #endif
73 enum clogger_opcode {
74 CL_OP_LITERAL = 0,
75 CL_OP_REQUEST,
76 CL_OP_RESPONSE,
77 CL_OP_SPECIAL,
78 CL_OP_EVAL,
79 CL_OP_TIME_LOCAL,
80 CL_OP_TIME_UTC,
81 CL_OP_REQUEST_TIME,
82 CL_OP_TIME,
83 CL_OP_COOKIE
86 enum clogger_special {
87 CL_SP_body_bytes_sent = 0,
88 CL_SP_status,
89 CL_SP_request,
90 CL_SP_request_length,
91 CL_SP_response_length,
92 CL_SP_ip,
93 CL_SP_pid,
94 CL_SP_request_uri,
95 CL_SP_time_iso8601,
96 CL_SP_time_local,
97 CL_SP_time_utc
100 struct clogger {
101 VALUE app;
103 VALUE fmt_ops;
104 VALUE logger;
105 VALUE log_buf;
107 VALUE env;
108 VALUE cookies;
109 VALUE status;
110 VALUE headers;
111 VALUE body;
113 off_t body_bytes_sent;
114 struct timespec ts_start;
116 int fd;
117 int wrap_body;
118 int need_resp;
119 int reentrant; /* tri-state, -1:auto, 1/0 true/false */
122 static ID write_id;
123 static ID ltlt_id;
124 static ID call_id;
125 static ID each_id;
126 static ID close_id;
127 static ID to_i_id;
128 static ID to_s_id;
129 static ID size_id;
130 static ID sq_brace_id;
131 static ID new_id;
132 static ID to_path_id;
133 static ID to_io_id;
134 static ID respond_to_id;
135 static VALUE cClogger;
136 static VALUE mFormat;
137 static VALUE cHeaderHash;
139 /* common hash lookup keys */
140 static VALUE g_HTTP_X_FORWARDED_FOR;
141 static VALUE g_REMOTE_ADDR;
142 static VALUE g_REQUEST_METHOD;
143 static VALUE g_PATH_INFO;
144 static VALUE g_REQUEST_URI;
145 static VALUE g_QUERY_STRING;
146 static VALUE g_HTTP_VERSION;
147 static VALUE g_rack_errors;
148 static VALUE g_rack_input;
149 static VALUE g_rack_multithread;
150 static VALUE g_dash;
151 static VALUE g_space;
152 static VALUE g_question_mark;
153 static VALUE g_rack_request_cookie_hash;
155 #define LOG_BUF_INIT_SIZE 128
157 static void init_buffers(struct clogger *c)
159 c->log_buf = rb_str_buf_new(LOG_BUF_INIT_SIZE);
162 static inline int need_escape(unsigned c)
164 assert(c <= 0xff);
165 return !!(c == '\'' || c == '"' || c <= 0x1f || c >= 0x7f);
168 /* we are encoding-agnostic, clients can send us all sorts of junk */
169 static VALUE byte_xs_str(VALUE from)
171 static const char esc[] = "0123456789ABCDEF";
172 unsigned char *new_ptr;
173 const unsigned char *ptr = (const unsigned char *)RSTRING_PTR(from);
174 long len = RSTRING_LEN(from);
175 long new_len = len;
176 VALUE rv;
178 for (; --len >= 0; ptr++) {
179 unsigned c = *ptr;
181 if (unlikely(need_escape(c)))
182 new_len += 3; /* { '\', 'x', 'X', 'X' } */
185 len = RSTRING_LEN(from);
186 if (new_len == len)
187 return from;
189 rv = rb_str_new(NULL, new_len);
190 new_ptr = (unsigned char *)RSTRING_PTR(rv);
191 ptr = (const unsigned char *)RSTRING_PTR(from);
192 for (; --len >= 0; ptr++) {
193 unsigned c = *ptr;
195 if (unlikely(need_escape(c))) {
196 *new_ptr++ = '\\';
197 *new_ptr++ = 'x';
198 *new_ptr++ = esc[c >> 4];
199 *new_ptr++ = esc[c & 0xf];
200 } else {
201 *new_ptr++ = c;
204 assert(RSTRING_PTR(rv)[RSTRING_LEN(rv)] == '\0');
206 return rv;
209 static VALUE byte_xs(VALUE from)
211 return byte_xs_str(rb_obj_as_string(from));
214 static void clogger_mark(void *ptr)
216 struct clogger *c = ptr;
218 rb_gc_mark(c->app);
219 rb_gc_mark(c->fmt_ops);
220 rb_gc_mark(c->logger);
221 rb_gc_mark(c->log_buf);
222 rb_gc_mark(c->env);
223 rb_gc_mark(c->cookies);
224 rb_gc_mark(c->status);
225 rb_gc_mark(c->headers);
226 rb_gc_mark(c->body);
229 static VALUE clogger_alloc(VALUE klass)
231 struct clogger *c;
233 return Data_Make_Struct(klass, struct clogger, clogger_mark, -1, c);
236 static struct clogger *clogger_get(VALUE self)
238 struct clogger *c;
240 Data_Get_Struct(self, struct clogger, c);
241 assert(c);
242 return c;
245 /* only for writing to regular files, not stupid crap like NFS */
246 static void write_full(int fd, const void *buf, size_t count)
248 ssize_t r;
249 unsigned long ubuf = (unsigned long)buf;
251 while (count > 0) {
252 r = write(fd, (void *)ubuf, count);
254 if ((size_t)r == count) { /* overwhelmingly likely */
255 return;
256 } else if (r > 0) {
257 count -= r;
258 ubuf += r;
259 } else {
260 if (errno == EINTR || errno == EAGAIN)
261 continue; /* poor souls on NFS and like: */
262 if (!errno)
263 errno = ENOSPC;
264 rb_sys_fail("write");
270 * allow us to use write_full() iff we detect a blocking file
271 * descriptor that wouldn't play nicely with Ruby threading/fibers
273 static int raw_fd(VALUE my_fd)
275 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
276 int fd;
277 int flags;
279 if (NIL_P(my_fd))
280 return -1;
281 fd = NUM2INT(my_fd);
283 flags = fcntl(fd, F_GETFL);
284 if (flags < 0)
285 rb_sys_fail("fcntl");
287 if (flags & O_NONBLOCK) {
288 struct stat sb;
290 if (fstat(fd, &sb) < 0)
291 return -1;
293 /* O_NONBLOCK is no-op for regular files: */
294 if (! S_ISREG(sb.st_mode))
295 return -1;
297 return fd;
298 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
299 return -1;
300 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
303 /* :nodoc: */
304 static VALUE clogger_reentrant(VALUE self)
306 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
309 /* :nodoc: */
310 static VALUE clogger_wrap_body(VALUE self)
312 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
315 static void append_status(struct clogger *c)
317 char buf[sizeof("999")];
318 int nr;
319 VALUE status = c->status;
321 if (TYPE(status) != T_FIXNUM) {
322 status = rb_funcall(status, to_i_id, 0);
323 /* no way it's a valid status code (at least not HTTP/1.1) */
324 if (TYPE(status) != T_FIXNUM) {
325 rb_str_buf_append(c->log_buf, g_dash);
326 return;
330 nr = FIX2INT(status);
331 if (nr >= 100 && nr <= 999) {
332 nr = snprintf(buf, sizeof(buf), "%03d", nr);
333 assert(nr == 3);
334 rb_str_buf_cat(c->log_buf, buf, nr);
335 } else {
336 /* raise?, swap for 500? */
337 rb_str_buf_append(c->log_buf, g_dash);
341 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
342 static void append_ip(struct clogger *c)
344 VALUE env = c->env;
345 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
347 if (NIL_P(tmp)) {
348 /* can't be faked on any real server, so no escape */
349 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
350 if (NIL_P(tmp))
351 tmp = g_dash;
352 } else {
353 tmp = byte_xs(tmp);
355 rb_str_buf_append(c->log_buf, tmp);
358 static void append_body_bytes_sent(struct clogger *c)
360 char buf[(sizeof(off_t) * 8) / 3 + 1];
361 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
362 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
364 assert(nr > 0 && nr < (int)sizeof(buf));
365 rb_str_buf_cat(c->log_buf, buf, nr);
368 static void append_ts(struct clogger *c, VALUE op, struct timespec *ts)
370 char buf[sizeof(".000000") + ((sizeof(ts->tv_sec) * 8) / 3)];
371 int nr;
372 char *fmt = RSTRING_PTR(rb_ary_entry(op, 1));
373 int ndiv = NUM2INT(rb_ary_entry(op, 2));
374 int usec = ts->tv_nsec / 1000;
376 nr = snprintf(buf, sizeof(buf), fmt,
377 (int)ts->tv_sec, (int)(usec / ndiv));
378 assert(nr > 0 && nr < (int)sizeof(buf));
379 rb_str_buf_cat(c->log_buf, buf, nr);
382 static void append_request_time_fmt(struct clogger *c, VALUE op)
384 struct timespec now;
386 clock_gettime(hopefully_CLOCK_MONOTONIC, &now);
387 clock_diff(&now, &c->ts_start);
388 append_ts(c, op, &now);
391 static void append_time_fmt(struct clogger *c, VALUE op)
393 struct timespec now;
394 int r = clock_gettime(CLOCK_REALTIME, &now);
396 if (unlikely(r != 0))
397 rb_sys_fail("clock_gettime(CLOCK_REALTIME)");
398 append_ts(c, op, &now);
401 static void append_request_uri(struct clogger *c)
403 VALUE tmp;
405 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
406 if (NIL_P(tmp)) {
407 tmp = rb_hash_aref(c->env, g_PATH_INFO);
408 if (!NIL_P(tmp))
409 rb_str_buf_append(c->log_buf, byte_xs(tmp));
410 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
411 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
412 rb_str_buf_append(c->log_buf, g_question_mark);
413 rb_str_buf_append(c->log_buf, byte_xs(tmp));
415 } else {
416 rb_str_buf_append(c->log_buf, byte_xs(tmp));
420 static void append_request(struct clogger *c)
422 VALUE tmp;
424 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
425 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
426 if (!NIL_P(tmp))
427 rb_str_buf_append(c->log_buf, tmp);
429 rb_str_buf_append(c->log_buf, g_space);
431 append_request_uri(c);
433 /* HTTP_VERSION can be injected by malicious clients */
434 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
435 if (!NIL_P(tmp)) {
436 rb_str_buf_append(c->log_buf, g_space);
437 rb_str_buf_append(c->log_buf, byte_xs(tmp));
441 static void append_request_length(struct clogger *c)
443 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
444 if (NIL_P(tmp)) {
445 rb_str_buf_append(c->log_buf, g_dash);
446 } else {
447 tmp = rb_funcall(tmp, size_id, 0);
448 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
452 static long local_gmtoffset(struct tm *tm)
454 time_t t = time(NULL);
456 tzset();
457 localtime_r(&t, tm);
460 * HAVE_STRUCT_TM_TM_GMTOFF may be defined in Ruby headers
461 * HAVE_ST_TM_GMTOFF is defined ourselves.
463 #if defined(HAVE_STRUCT_TM_TM_GMTOFF) || defined(HAVE_ST_TM_GMTOFF)
464 return tm->tm_gmtoff / 60;
465 #else
466 return -(tm->tm_isdst ? timezone - 3600 : timezone) / 60;
467 #endif
470 static void append_time_iso8601(struct clogger *c)
472 char buf[sizeof("1970-01-01T00:00:00+00:00")];
473 struct tm tm;
474 int nr;
475 long gmtoff = local_gmtoffset(&tm);
477 nr = snprintf(buf, sizeof(buf),
478 "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
479 tm.tm_year + 1900, tm.tm_mon + 1,
480 tm.tm_mday, tm.tm_hour,
481 tm.tm_min, tm.tm_sec,
482 gmtoff < 0 ? '-' : '+',
483 abs(gmtoff / 60), abs(gmtoff % 60));
484 assert(nr == (sizeof(buf) - 1) && "snprintf fail");
485 rb_str_buf_cat(c->log_buf, buf, sizeof(buf) - 1);
488 static const char months[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
489 "Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
491 static void append_time_local(struct clogger *c)
493 char buf[sizeof("01/Jan/1970:00:00:00 +0000")];
494 struct tm tm;
495 int nr;
496 long gmtoff = local_gmtoffset(&tm);
498 nr = snprintf(buf, sizeof(buf),
499 "%02d/%s/%d:%02d:%02d:%02d %c%02d%02d",
500 tm.tm_mday, months + (tm.tm_mon * sizeof("Jan")),
501 tm.tm_year + 1900, tm.tm_hour,
502 tm.tm_min, tm.tm_sec,
503 gmtoff < 0 ? '-' : '+',
504 abs(gmtoff / 60), abs(gmtoff % 60));
505 assert(nr == (sizeof(buf) - 1) && "snprintf fail");
506 rb_str_buf_cat(c->log_buf, buf, sizeof(buf) - 1);
509 static void append_time_utc(struct clogger *c)
511 char buf[sizeof("01/Jan/1970:00:00:00 +0000")];
512 struct tm tm;
513 int nr;
514 time_t t = time(NULL);
516 gmtime_r(&t, &tm);
517 nr = snprintf(buf, sizeof(buf),
518 "%02d/%s/%d:%02d:%02d:%02d +0000",
519 tm.tm_mday, months + (tm.tm_mon * sizeof("Jan")),
520 tm.tm_year + 1900, tm.tm_hour,
521 tm.tm_min, tm.tm_sec);
522 assert(nr == (sizeof(buf) - 1) && "snprintf fail");
523 rb_str_buf_cat(c->log_buf, buf, sizeof(buf) - 1);
526 static void
527 append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt, VALUE buf)
529 char *buf_ptr = RSTRING_PTR(buf);
530 size_t buf_size = RSTRING_LEN(buf) + 1; /* "\0" */
531 size_t nr;
532 struct tm tmp;
533 time_t t = time(NULL);
535 if (op == CL_OP_TIME_LOCAL)
536 localtime_r(&t, &tmp);
537 else if (op == CL_OP_TIME_UTC)
538 gmtime_r(&t, &tmp);
539 else
540 assert(0 && "unknown op");
542 nr = strftime(buf_ptr, buf_size, RSTRING_PTR(fmt), &tmp);
543 assert(nr < buf_size && "time format too small!");
544 rb_str_buf_cat(c->log_buf, buf_ptr, nr);
547 static void append_pid(struct clogger *c)
549 char buf[(sizeof(pid_t) * 8) / 3 + 1];
550 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
552 assert(nr > 0 && nr < (int)sizeof(buf));
553 rb_str_buf_cat(c->log_buf, buf, nr);
556 static void append_eval(struct clogger *c, VALUE str)
558 int state = -1;
559 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
561 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
562 rb_str_buf_append(c->log_buf, rv);
565 static void append_cookie(struct clogger *c, VALUE key)
567 VALUE cookie;
569 if (c->cookies == Qfalse)
570 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
572 if (NIL_P(c->cookies)) {
573 cookie = g_dash;
574 } else {
575 cookie = rb_hash_aref(c->cookies, key);
576 cookie = NIL_P(cookie) ? g_dash : byte_xs(cookie);
578 rb_str_buf_append(c->log_buf, cookie);
581 static void append_request_env(struct clogger *c, VALUE key)
583 VALUE tmp = rb_hash_aref(c->env, key);
585 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
586 rb_str_buf_append(c->log_buf, tmp);
589 static void append_response(struct clogger *c, VALUE key)
591 VALUE v;
593 assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash");
595 v = rb_funcall(c->headers, sq_brace_id, 1, key);
596 v = NIL_P(v) ? g_dash : byte_xs(v);
597 rb_str_buf_append(c->log_buf, v);
600 static void special_var(struct clogger *c, enum clogger_special var)
602 switch (var) {
603 case CL_SP_body_bytes_sent:
604 append_body_bytes_sent(c);
605 break;
606 case CL_SP_status:
607 append_status(c);
608 break;
609 case CL_SP_request:
610 append_request(c);
611 break;
612 case CL_SP_request_length:
613 append_request_length(c);
614 break;
615 case CL_SP_response_length:
616 if (c->body_bytes_sent == 0)
617 rb_str_buf_append(c->log_buf, g_dash);
618 else
619 append_body_bytes_sent(c);
620 break;
621 case CL_SP_ip:
622 append_ip(c);
623 break;
624 case CL_SP_pid:
625 append_pid(c);
626 break;
627 case CL_SP_request_uri:
628 append_request_uri(c);
629 break;
630 case CL_SP_time_iso8601:
631 append_time_iso8601(c);
632 break;
633 case CL_SP_time_local:
634 append_time_local(c);
635 break;
636 case CL_SP_time_utc:
637 append_time_utc(c);
641 static VALUE cwrite(struct clogger *c)
643 const VALUE ops = c->fmt_ops;
644 long i;
645 long len = RARRAY_LEN(ops);
646 VALUE dst = c->log_buf;
648 rb_str_set_len(dst, 0);
650 for (i = 0; i < len; i++) {
651 VALUE op = rb_ary_entry(ops, i);
652 enum clogger_opcode opcode = FIX2INT(rb_ary_entry(op, 0));
653 VALUE op1 = rb_ary_entry(op, 1);
655 switch (opcode) {
656 case CL_OP_LITERAL:
657 rb_str_buf_append(dst, op1);
658 break;
659 case CL_OP_REQUEST:
660 append_request_env(c, op1);
661 break;
662 case CL_OP_RESPONSE:
663 append_response(c, op1);
664 break;
665 case CL_OP_SPECIAL:
666 special_var(c, FIX2INT(op1));
667 break;
668 case CL_OP_EVAL:
669 append_eval(c, op1);
670 break;
671 case CL_OP_TIME_LOCAL:
672 case CL_OP_TIME_UTC: {
673 VALUE arg2 = rb_ary_entry(op, 2);
674 append_time(c, opcode, op1, arg2);
676 break;
677 case CL_OP_REQUEST_TIME:
678 append_request_time_fmt(c, op);
679 break;
680 case CL_OP_TIME:
681 append_time_fmt(c, op);
682 break;
683 case CL_OP_COOKIE:
684 append_cookie(c, op1);
685 break;
689 if (c->fd >= 0) {
690 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
691 } else {
692 VALUE logger = c->logger;
694 if (NIL_P(logger)) {
695 logger = rb_hash_aref(c->env, g_rack_errors);
696 rb_funcall(logger, write_id, 1, dst);
697 } else {
698 rb_funcall(logger, ltlt_id, 1, dst);
702 return Qnil;
705 static VALUE clogger_write(VALUE self)
707 return cwrite(clogger_get(self));
710 static void init_logger(struct clogger *c, VALUE path)
712 ID id;
714 if (!NIL_P(path) && !NIL_P(c->logger))
715 rb_raise(rb_eArgError, ":logger and :path are independent");
716 if (!NIL_P(path)) {
717 VALUE ab = rb_str_new2("ab");
718 id = rb_intern("open");
719 c->logger = rb_funcall(rb_cFile, id, 2, path, ab);
722 id = rb_intern("sync=");
723 if (rb_respond_to(c->logger, id))
724 rb_funcall(c->logger, id, 1, Qtrue);
726 id = rb_intern("fileno");
727 if (rb_respond_to(c->logger, id))
728 c->fd = raw_fd(rb_funcall(c->logger, id, 0));
732 * call-seq:
733 * Clogger.new(app, :logger => $stderr, :format => string) => obj
735 * Creates a new Clogger object that wraps +app+. +:logger+ may
736 * be any object that responds to the "<<" method with a string argument.
737 * Instead of +:logger+, +:path+ may be specified to be a :path of a File
738 * that will be opened in append mode.
740 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
742 struct clogger *c = clogger_get(self);
743 VALUE o = Qnil;
744 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
746 rb_scan_args(argc, argv, "11", &c->app, &o);
747 c->fd = -1;
748 c->logger = Qnil;
749 c->reentrant = -1; /* auto-detect */
751 if (TYPE(o) == T_HASH) {
752 VALUE tmp;
754 tmp = rb_hash_aref(o, ID2SYM(rb_intern("path")));
755 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
756 init_logger(c, tmp);
758 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
759 if (!NIL_P(tmp))
760 fmt = tmp;
762 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
763 switch (TYPE(tmp)) {
764 case T_TRUE:
765 c->reentrant = 1;
766 break;
767 case T_FALSE:
768 c->reentrant = 0;
769 case T_NIL:
770 break;
771 default:
772 rb_raise(rb_eArgError, ":reentrant must be boolean");
776 init_buffers(c);
777 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
779 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
780 1, c->fmt_ops))
781 c->need_resp = 1;
782 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
783 1, c->fmt_ops))
784 c->wrap_body = 1;
786 return self;
789 static VALUE body_iter_i(VALUE str, VALUE self)
791 struct clogger *c = clogger_get(self);
793 str = rb_obj_as_string(str);
794 c->body_bytes_sent += RSTRING_LEN(str);
796 return rb_yield(str);
799 static VALUE body_close(VALUE self)
801 struct clogger *c = clogger_get(self);
803 if (rb_respond_to(c->body, close_id))
804 return rb_funcall(c->body, close_id, 0);
805 return Qnil;
809 * call-seq:
810 * clogger.each { |part| socket.write(part) }
812 * Delegates the body#each call to the underlying +body+ object
813 * while tracking the number of bytes yielded. This will log
814 * the request.
816 static VALUE clogger_each(VALUE self)
818 struct clogger *c = clogger_get(self);
820 rb_need_block();
821 c->body_bytes_sent = 0;
822 rb_iterate(rb_each, c->body, body_iter_i, self);
824 return self;
828 * call-seq:
829 * clogger.close
831 * Delegates the body#close call to the underlying +body+ object.
832 * This is only used when Clogger is wrapping the +body+ of a Rack
833 * response and should be automatically called by the web server.
835 static VALUE clogger_close(VALUE self)
838 return rb_ensure(body_close, self, clogger_write, self);
841 /* :nodoc: */
842 static VALUE clogger_fileno(VALUE self)
844 struct clogger *c = clogger_get(self);
846 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
849 static VALUE ccall(struct clogger *c, VALUE env)
851 VALUE rv;
853 clock_gettime(hopefully_CLOCK_MONOTONIC, &c->ts_start);
854 c->env = env;
855 c->cookies = Qfalse;
856 rv = rb_funcall(c->app, call_id, 1, env);
857 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
858 c->status = rb_ary_entry(rv, 0);
859 c->headers = rb_ary_entry(rv, 1);
860 c->body = rb_ary_entry(rv, 2);
862 rv = rb_ary_dup(rv);
863 if (c->need_resp &&
864 ! rb_obj_is_kind_of(c->headers, cHeaderHash)) {
865 c->headers = rb_funcall(cHeaderHash, new_id, 1,
866 c->headers);
867 rb_ary_store(rv, 1, c->headers);
869 } else {
870 VALUE tmp = rb_inspect(rv);
872 c->status = INT2FIX(500);
873 c->headers = c->body = rb_ary_new();
874 cwrite(c);
875 rb_raise(rb_eTypeError,
876 "app response not a 3 element Array: %s",
877 RSTRING_PTR(tmp));
878 RB_GC_GUARD(tmp);
881 return rv;
885 * call-seq:
886 * clogger.call(env) => [ status, headers, body ]
888 * calls the wrapped Rack application with +env+, returns the
889 * [status, headers, body ] tuplet required by Rack.
891 static VALUE clogger_call(VALUE self, VALUE env)
893 struct clogger *c = clogger_get(self);
894 VALUE rv;
896 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
898 if (c->wrap_body) {
899 /* XXX: we assume the existence of the GVL here: */
900 if (c->reentrant < 0) {
901 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
902 c->reentrant = Qfalse == tmp ? 0 : 1;
905 if (c->reentrant) {
906 self = rb_obj_dup(self);
907 c = clogger_get(self);
910 rv = ccall(c, env);
911 assert(!OBJ_FROZEN(rv) && "frozen response array");
912 rb_ary_store(rv, 2, self);
914 return rv;
917 rv = ccall(c, env);
918 cwrite(c);
920 return rv;
923 static void duplicate_buffers(VALUE ops)
925 long i;
926 long len = RARRAY_LEN(ops);
928 for (i = 0; i < len; i++) {
929 VALUE op = rb_ary_entry(ops, i);
930 enum clogger_opcode opcode = FIX2INT(rb_ary_entry(op, 0));
932 if (opcode == CL_OP_TIME_LOCAL || opcode == CL_OP_TIME_UTC) {
933 VALUE buf = rb_ary_entry(op, 2);
934 Check_Type(buf, T_STRING);
935 buf = rb_str_dup(buf);
936 rb_str_modify(buf); /* trigger copy-on-write */
937 rb_ary_store(op, 2, buf);
942 /* :nodoc: */
943 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
945 struct clogger *a = clogger_get(orig);
946 struct clogger *b = clogger_get(clone);
948 memcpy(b, a, sizeof(struct clogger));
949 init_buffers(b);
950 duplicate_buffers(b->fmt_ops);
952 return clone;
955 #define CONST_GLOBAL_STR2(var, val) do { \
956 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
957 rb_global_variable(&g_##var); \
958 } while (0)
960 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
963 * call-seq:
964 * clogger.respond_to?(:to_path) => true or false
965 * clogger.respond_to?(:close) => true
967 * used to delegate +:to_path+ checks for Rack webservers that optimize
968 * static file serving
970 static VALUE respond_to(VALUE self, VALUE method)
972 struct clogger *c = clogger_get(self);
973 ID id = rb_to_id(method);
975 if (close_id == id)
976 return Qtrue;
977 return rb_respond_to(c->body, id);
981 * call-seq:
982 * clogger.to_path
984 * used to proxy +:to_path+ method calls to the wrapped response body.
986 static VALUE to_path(VALUE self)
988 struct clogger *c = clogger_get(self);
989 struct stat sb;
990 int rv;
991 VALUE path = rb_funcall(c->body, to_path_id, 0);
993 /* try to avoid an extra path lookup */
994 if (rb_respond_to(c->body, to_io_id)) {
995 rv = fstat(my_fileno(c->body), &sb);
996 } else {
997 const char *cpath = StringValueCStr(path);
998 unsigned devfd;
1000 * Rainbows! can use "/dev/fd/%u" in to_path output to avoid
1001 * extra open() syscalls, too.
1003 if (sscanf(cpath, "/dev/fd/%u", &devfd) == 1)
1004 rv = fstat((int)devfd, &sb);
1005 else
1006 rv = stat(cpath, &sb);
1010 * calling this method implies the web server will bypass
1011 * the each method where body_bytes_sent is calculated,
1012 * so we stat and set that value here.
1014 c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
1015 return path;
1019 * call-seq:
1020 * clogger.to_io
1022 * used to proxy +:to_io+ method calls to the wrapped response body.
1024 static VALUE to_io(VALUE self)
1026 struct clogger *c = clogger_get(self);
1027 struct stat sb;
1028 VALUE io = rb_convert_type(c->body, T_FILE, "IO", "to_io");
1030 if (fstat(my_fileno(io), &sb) == 0)
1031 c->body_bytes_sent = sb.st_size;
1033 return io;
1036 /* :nodoc: */
1037 static VALUE body(VALUE self)
1039 return clogger_get(self)->body;
1042 void Init_clogger_ext(void)
1044 VALUE tmp;
1046 check_clock();
1048 write_id = rb_intern("write");
1049 ltlt_id = rb_intern("<<");
1050 call_id = rb_intern("call");
1051 each_id = rb_intern("each");
1052 close_id = rb_intern("close");
1053 to_i_id = rb_intern("to_i");
1054 to_s_id = rb_intern("to_s");
1055 size_id = rb_intern("size");
1056 sq_brace_id = rb_intern("[]");
1057 new_id = rb_intern("new");
1058 to_path_id = rb_intern("to_path");
1059 to_io_id = rb_intern("to_io");
1060 respond_to_id = rb_intern("respond_to?");
1061 cClogger = rb_define_class("Clogger", rb_cObject);
1062 mFormat = rb_define_module_under(cClogger, "Format");
1063 rb_define_alloc_func(cClogger, clogger_alloc);
1064 rb_define_method(cClogger, "initialize", clogger_init, -1);
1065 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
1066 rb_define_method(cClogger, "call", clogger_call, 1);
1067 rb_define_method(cClogger, "each", clogger_each, 0);
1068 rb_define_method(cClogger, "close", clogger_close, 0);
1069 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
1070 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
1071 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
1072 rb_define_method(cClogger, "to_path", to_path, 0);
1073 rb_define_method(cClogger, "to_io", to_io, 0);
1074 rb_define_method(cClogger, "respond_to?", respond_to, 1);
1075 rb_define_method(cClogger, "body", body, 0);
1076 CONST_GLOBAL_STR(REMOTE_ADDR);
1077 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
1078 CONST_GLOBAL_STR(REQUEST_METHOD);
1079 CONST_GLOBAL_STR(PATH_INFO);
1080 CONST_GLOBAL_STR(QUERY_STRING);
1081 CONST_GLOBAL_STR(REQUEST_URI);
1082 CONST_GLOBAL_STR(HTTP_VERSION);
1083 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
1084 CONST_GLOBAL_STR2(rack_input, "rack.input");
1085 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
1086 CONST_GLOBAL_STR2(dash, "-");
1087 CONST_GLOBAL_STR2(space, " ");
1088 CONST_GLOBAL_STR2(question_mark, "?");
1089 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
1091 tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
1092 tmp = rb_const_get(tmp, rb_intern("Utils"));
1093 cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));