clogger 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.4.0.GIT
4
+ DEF_VER=v0.5.0.GIT
5
5
 
6
6
  LF='
7
7
  '
@@ -22,11 +22,24 @@ clean:
22
22
  -$(MAKE) -C ext/clogger_ext clean
23
23
  $(RM) ext/clogger_ext/Makefile lib/clogger_ext.$(DLEXT)
24
24
 
25
- test-ext: ext/clogger_ext/clogger.$(DLEXT)
26
- $(RUBY) -Iext/clogger_ext:lib test/test_clogger.rb
25
+ test_unit := $(wildcard test/test_*.rb)
26
+ test-unit: $(test_unit)
27
+
28
+ ifeq ($(CLOGGER_PURE),)
29
+ $(test_unit): export RUBYLIB := ext/clogger_ext:lib
30
+ $(test_unit): ext/clogger_ext/clogger.$(DLEXT)
31
+ else
32
+ $(test_unit): export RUBYLIB := lib
33
+ endif
34
+
35
+ $(test_unit):
36
+ $(RUBY) $@
37
+
38
+ test-ext:
39
+ CLOGGER_PURE=0 $(MAKE) test-unit
27
40
 
28
41
  test-pure:
29
- CLOGGER_PURE=t $(RUBY) -Ilib test/test_clogger.rb
42
+ CLOGGER_PURE=1 $(MAKE) test-unit
30
43
 
31
44
  test: test-ext test-pure
32
45
 
@@ -46,7 +59,7 @@ NEWS: GIT-VERSION-FILE .manifest
46
59
  $(RAKE) -s news_rdoc > $@+
47
60
  mv $@+ $@
48
61
 
49
- SINCE = 0.3.2
62
+ SINCE = 0.4.0
50
63
  ChangeLog: log_range = $(shell test -n "$(SINCE)" && echo v$(SINCE)..)
51
64
  ChangeLog: GIT-VERSION-FILE
52
65
  @echo "ChangeLog from $(GIT_URL) ($(SINCE)..$(GIT_VERSION))" > $@+
@@ -60,7 +73,7 @@ atom = <link rel="alternate" title="Atom feed" href="$(1)" \
60
73
  type="application/atom+xml"/>
61
74
 
62
75
  doc: .document NEWS ChangeLog
63
- rdoc -Na -t "$(shell sed -ne '1s/^= //p' README)"
76
+ rdoc -a -t "$(shell sed -ne '1s/^= //p' README)"
64
77
  install -m644 COPYING doc/COPYING
65
78
  install -m644 $(shell grep '^[A-Z]' .document) doc/
66
79
  $(RUBY) -i -p -e \
@@ -145,3 +158,4 @@ gem install-gem: GIT-VERSION-FILE
145
158
  endif
146
159
 
147
160
  .PHONY: .FORCE-GIT-VERSION-FILE test doc manifest
161
+ .PHONY: test test-ext test-pure $(test_unit)
data/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ \Clogger is copyrighted Free Software by all contributors, see logs in
2
+ revision control for names and email addresses of all of them.
3
+
4
+ You can redistribute it and/or modify it under the terms of the GNU Lesser
5
+ General Public License as published by the Free Software Foundation,
6
+ version 2.1 or later {LGPLv2.1}[http://www.gnu.org/licenses/lgpl-2.1.txt]
7
+ (see link:COPYING).
8
+
9
+ \Clogger is distributed in the hope that it will be useful, but WITHOUT
10
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12
+ License for more details.
13
+
14
+ You should have received a copy of the GNU Lesser General Public License
15
+ along with this library; if not, write to the Free Software
16
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
data/README CHANGED
@@ -1,8 +1,8 @@
1
- = Clogger - configurable request logging for Rack
1
+ = \Clogger - configurable request logging for Rack
2
2
 
3
3
  == DESCRIPTION
4
4
 
5
- Clogger is Rack middleware for logging HTTP requests. The log format
5
+ \Clogger is Rack middleware for logging HTTP requests. The log format
6
6
  is customizable so you can specify exactly which fields to log.
7
7
 
8
8
  == FEATURES
@@ -21,13 +21,18 @@ is customizable so you can specify exactly which fields to log.
21
21
  all bytes in the range of \x00-\x1F
22
22
 
23
23
  * multi-instance capable and (optionally) reentrant. You can use
24
- Clogger in a multi-threaded server, and even multiple Cloggers logging
24
+ \Clogger in a multi-threaded server, and even multiple Cloggers logging
25
25
  to different locations and different formats in the same process.
26
26
 
27
+ * Pure Ruby version for non-MRI versions of Ruby (or via CLOGGER_PURE=1
28
+ in the environment). The optional C extension is loaded by default
29
+ and supported under MRI 1.8.7, 1.9.1, and 1.9.2.
30
+
27
31
  == SYNOPSIS
28
32
 
29
- Clogger may be loaded as Rack middleware in your config.ru:
33
+ \Clogger may be loaded as Rack middleware in your config.ru:
30
34
 
35
+ # ENV['CLOGGER_PURE'] = '1' # uncomment to disable C extension
31
36
  require "clogger"
32
37
  use Clogger,
33
38
  :format => Clogger::Format::Combined,
@@ -81,7 +86,7 @@ somewhere inside the "Rails::Initializer.run do |config|" block:
81
86
 
82
87
  == REQUIREMENTS
83
88
 
84
- * Ruby, Rack
89
+ * {Ruby}[http://ruby-lang.org/], {Rack}[http://rack.rubyforge.org/]
85
90
 
86
91
  == DEVELOPMENT
87
92
 
@@ -126,20 +131,3 @@ There is an optional C extension that should be compatible with MRI
126
131
  other Ruby implementations, but be sure to let us know if that's not the
127
132
  case. No pre-built binaries are currently distributed, let us know if
128
133
  you're interested in helping with the release/support effort.
129
-
130
- == LICENSE
131
-
132
- Copyright (C) 2009 Eric Wong and contributors.
133
-
134
- Clogger is free software; you can redistribute it and/or modify it under
135
- the terms of the GNU Lesser General Public License as published by the
136
- Free Software Foundation, version 3.0.
137
-
138
- Clogger is distributed in the hope that it will be useful, but WITHOUT ANY
139
- WARRANTY; without even the implied warranty of MERCHANTABILITY or
140
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
141
- License in the COPYING file for more details.
142
-
143
- You should have received a copy of the GNU Lesser General Public License
144
- along with Clogger; if not, write to the Free Software Foundation, Inc.,
145
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
@@ -35,11 +35,11 @@ is customizable so you can specify exactly which fields to log.
35
35
  s.require_paths = %w(lib ext)
36
36
  s.rubyforge_project = %q{clogger}
37
37
  s.summary = %q{configurable request logging for Rack}
38
- s.test_files = %w(test/test_clogger.rb)
38
+ s.test_files = %w(test/test_clogger.rb test/test_clogger_to_path.rb)
39
39
 
40
40
  # HeaderHash wasn't case-insensitive in old versions
41
41
  s.add_dependency(%q<rack>, ["> 0.9"])
42
42
  s.extensions = %w(ext/clogger_ext/extconf.rb)
43
43
 
44
- # s.license = "LGPLv3" # disabled for compatibility with older RubyGems
44
+ # s.license = "LGPLv2.1+" # disabled for compatibility with older RubyGems
45
45
  end
@@ -1,8 +1,14 @@
1
1
  #define _BSD_SOURCE
2
2
  #include <ruby.h>
3
+ #ifdef HAVE_RUBY_IO_H
4
+ # include <ruby/io.h>
5
+ #else
6
+ # include <rubyio.h>
7
+ #endif
3
8
  #include <assert.h>
4
9
  #include <unistd.h>
5
10
  #include <sys/types.h>
11
+ #include <sys/stat.h>
6
12
  #include <sys/time.h>
7
13
  #include <time.h>
8
14
  #include <errno.h>
@@ -89,7 +95,10 @@ static ID to_s_id;
89
95
  static ID size_id;
90
96
  static ID sq_brace_id;
91
97
  static ID new_id;
98
+ static ID to_path_id;
99
+ static ID to_io_id;
92
100
  static VALUE cClogger;
101
+ static VALUE cToPath;
93
102
  static VALUE mFormat;
94
103
  static VALUE cHeaderHash;
95
104
 
@@ -237,21 +246,31 @@ static void write_full(int fd, const void *buf, size_t count)
237
246
  * allow us to use write_full() iff we detect a blocking file
238
247
  * descriptor that wouldn't play nicely with Ruby threading/fibers
239
248
  */
240
- static int raw_fd(VALUE my_fileno)
249
+ static int raw_fd(VALUE my_fd)
241
250
  {
242
251
  #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
243
252
  int fd;
244
253
  int flags;
245
254
 
246
- if (NIL_P(my_fileno))
255
+ if (NIL_P(my_fd))
247
256
  return -1;
248
- fd = NUM2INT(my_fileno);
257
+ fd = NUM2INT(my_fd);
249
258
 
250
259
  flags = fcntl(fd, F_GETFL);
251
260
  if (flags < 0)
252
261
  rb_sys_fail("fcntl");
253
262
 
254
- return (flags & O_NONBLOCK) ? -1 : fd;
263
+ if (flags & O_NONBLOCK) {
264
+ struct stat sb;
265
+
266
+ if (fstat(fd, &sb) < 0)
267
+ return -1;
268
+
269
+ /* O_NONBLOCK is no-op for regular files: */
270
+ if (! S_ISREG(sb.st_mode))
271
+ return -1;
272
+ }
273
+ return fd;
255
274
  #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
256
275
  return -1;
257
276
  #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
@@ -638,12 +657,11 @@ static VALUE body_iter_i(VALUE str, VALUE memop)
638
657
  return rb_yield(str);
639
658
  }
640
659
 
641
- static VALUE wrap_each(struct clogger *c)
660
+ static VALUE body_close(struct clogger *c)
642
661
  {
643
- c->body_bytes_sent = 0;
644
- rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
645
-
646
- return c->body;
662
+ if (rb_respond_to(c->body, close_id))
663
+ return rb_funcall(c->body, close_id, 0);
664
+ return Qnil;
647
665
  }
648
666
 
649
667
  /**
@@ -659,8 +677,10 @@ static VALUE clogger_each(VALUE self)
659
677
  struct clogger *c = clogger_get(self);
660
678
 
661
679
  rb_need_block();
680
+ c->body_bytes_sent = 0;
681
+ rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
662
682
 
663
- return rb_ensure(wrap_each, (VALUE)c, cwrite, (VALUE)c);
683
+ return self;
664
684
  }
665
685
 
666
686
  /**
@@ -675,9 +695,7 @@ static VALUE clogger_close(VALUE self)
675
695
  {
676
696
  struct clogger *c = clogger_get(self);
677
697
 
678
- if (rb_respond_to(c->body, close_id))
679
- return rb_funcall(c->body, close_id, 0);
680
- return Qnil;
698
+ return rb_ensure(body_close, (VALUE)c, cwrite, (VALUE)c);
681
699
  }
682
700
 
683
701
  /* :nodoc: */
@@ -749,6 +767,9 @@ static VALUE clogger_call(VALUE self, VALUE env)
749
767
 
750
768
  rv = ccall(c, env);
751
769
  assert(!OBJ_FROZEN(rv) && "frozen response array");
770
+
771
+ if (rb_respond_to(c->body, to_path_id))
772
+ self = rb_funcall(cToPath, new_id, 1, self);
752
773
  rb_ary_store(rv, 2, self);
753
774
 
754
775
  return rv;
@@ -779,6 +800,39 @@ static VALUE clogger_init_copy(VALUE clone, VALUE orig)
779
800
 
780
801
  #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
781
802
 
803
+ static VALUE to_path(VALUE self)
804
+ {
805
+ struct clogger *c = clogger_get(RSTRUCT_PTR(self)[0]);
806
+ VALUE path = rb_funcall(c->body, to_path_id, 0);
807
+ struct stat sb;
808
+ int rv;
809
+ unsigned devfd;
810
+ const char *cpath;
811
+
812
+ Check_Type(path, T_STRING);
813
+ cpath = RSTRING_PTR(path);
814
+
815
+ /* try to avoid an extra path lookup */
816
+ if (rb_respond_to(c->body, to_io_id))
817
+ rv = fstat(my_fileno(c->body), &sb);
818
+ /*
819
+ * Rainbows! can use "/dev/fd/%u" in to_path output to avoid
820
+ * extra open() syscalls, too.
821
+ */
822
+ else if (sscanf(cpath, "/dev/fd/%u", &devfd) == 1)
823
+ rv = fstat((int)devfd, &sb);
824
+ else
825
+ rv = stat(cpath, &sb);
826
+
827
+ /*
828
+ * calling this method implies the web server will bypass
829
+ * the each method where body_bytes_sent is calculated,
830
+ * so we stat and set that value here.
831
+ */
832
+ c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
833
+ return path;
834
+ }
835
+
782
836
  void Init_clogger_ext(void)
783
837
  {
784
838
  VALUE tmp;
@@ -792,6 +846,8 @@ void Init_clogger_ext(void)
792
846
  size_id = rb_intern("size");
793
847
  sq_brace_id = rb_intern("[]");
794
848
  new_id = rb_intern("new");
849
+ to_path_id = rb_intern("to_path");
850
+ to_io_id = rb_intern("to_io");
795
851
  cClogger = rb_define_class("Clogger", rb_cObject);
796
852
  mFormat = rb_define_module_under(cClogger, "Format");
797
853
  rb_define_alloc_func(cClogger, clogger_alloc);
@@ -821,4 +877,6 @@ void Init_clogger_ext(void)
821
877
  tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
822
878
  tmp = rb_const_get(tmp, rb_intern("Utils"));
823
879
  cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));
880
+ cToPath = rb_const_get(cClogger, rb_intern("ToPath"));
881
+ rb_define_method(cToPath, "to_path", to_path, 0);
824
882
  }
@@ -11,6 +11,12 @@
11
11
  #ifndef RARRAY_LEN
12
12
  # define RARRAY_LEN(s) (RARRAY(s)->len)
13
13
  #endif
14
+ #ifndef RSTRUCT_PTR
15
+ # define RSTRUCT_PTR(s) (RSTRUCT(s)->ptr)
16
+ #endif
17
+ #ifndef RSTRUCT_LEN
18
+ # define RSTRUCT_LEN(s) (RSTRUCT(s)->len)
19
+ #endif
14
20
 
15
21
  #ifndef HAVE_RB_STR_SET_LEN
16
22
  /* this is taken from Ruby 1.8.7, 1.8.6 may not have it */
@@ -21,3 +27,34 @@ static void rb_18_str_set_len(VALUE str, long len)
21
27
  }
22
28
  #define rb_str_set_len(str,len) rb_18_str_set_len(str,len)
23
29
  #endif
30
+
31
+ #if ! HAVE_RB_IO_T
32
+ # define rb_io_t OpenFile
33
+ #endif
34
+
35
+ #ifdef GetReadFile
36
+ # define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
37
+ #else
38
+ # if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
39
+ # define FPTR_TO_FD(fptr) fileno(fptr->f)
40
+ # else
41
+ # define FPTR_TO_FD(fptr) fptr->fd
42
+ # endif
43
+ #endif
44
+
45
+ static int my_fileno(VALUE io)
46
+ {
47
+ rb_io_t *fptr;
48
+
49
+ for (;;) {
50
+ switch (TYPE(io)) {
51
+ case T_FILE: {
52
+ GetOpenFile(io, fptr);
53
+ return FPTR_TO_FD(fptr);
54
+ }
55
+ default:
56
+ io = rb_convert_type(io, T_FILE, "IO", "to_io");
57
+ /* retry */
58
+ }
59
+ }
60
+ }
@@ -1,9 +1,13 @@
1
1
  # -*- encoding: binary -*-
2
- autoload :Rack, 'rack'
2
+ require 'rack'
3
3
 
4
+ # See the README for usage instructions
4
5
  class Clogger
5
- VERSION = '0.4.0'
6
6
 
7
+ # the version of Clogger, currently 0.5.0
8
+ VERSION = '0.5.0'
9
+
10
+ # :stopdoc:
7
11
  OP_LITERAL = 0
8
12
  OP_REQUEST = 1
9
13
  OP_RESPONSE = 2
@@ -36,6 +40,15 @@ class Clogger
36
40
  :request_uri => 7
37
41
  }
38
42
 
43
+ # proxy class to avoid clobbering the +to_path+ optimization when
44
+ # using static files
45
+ class ToPath < Struct.new(:clogger)
46
+ def each(&block); clogger.each(&block); end
47
+ def close; clogger.close; end
48
+
49
+ # to_path is defined in Clogger::Pure or the C extension
50
+ end
51
+
39
52
  private
40
53
 
41
54
  CGI_ENV = Regexp.new('\A\$(' <<
@@ -132,12 +145,13 @@ private
132
145
  end
133
146
  end
134
147
 
148
+ # :startdoc:
135
149
  end
136
150
 
137
151
  require 'clogger/format'
138
152
 
139
153
  begin
140
- raise LoadError if ENV['CLOGGER_PURE']
154
+ raise LoadError if ENV['CLOGGER_PURE'].to_i != 0
141
155
  require 'clogger_ext'
142
156
  rescue LoadError
143
157
  require 'clogger/pure'
@@ -5,6 +5,9 @@
5
5
  # the original C extension code so it's not very Ruby-ish...
6
6
  class Clogger
7
7
 
8
+ attr_accessor :env, :status, :headers, :body
9
+ attr_writer :body_bytes_sent
10
+
8
11
  def initialize(app, opts = {})
9
12
  # trigger autoload to avoid thread-safety issues later on
10
13
  Rack::Utils::HeaderHash.new({})
@@ -30,8 +33,13 @@ class Clogger
30
33
  headers = Rack::Utils::HeaderHash.new(headers) if @need_resp
31
34
  if @wrap_body
32
35
  @reentrant = env['rack.multithread'] if @reentrant.nil?
33
- @env, @status, @headers, @body = env, status, headers, body
34
- return [ status, headers, @reentrant ? self.dup : self ]
36
+ wbody = @reentrant ? self.dup : self
37
+ wbody.env = env
38
+ wbody.status = status
39
+ wbody.headers = headers
40
+ wbody.body = body
41
+ wbody = Clogger::ToPath.new(wbody) if body.respond_to?(:to_path)
42
+ return [ status, headers, wbody ]
35
43
  end
36
44
  log(env, status, headers)
37
45
  [ status, headers, body ]
@@ -43,12 +51,13 @@ class Clogger
43
51
  @body_bytes_sent += Rack::Utils.bytesize(part)
44
52
  yield part
45
53
  end
46
- ensure
47
- log(@env, @status, @headers)
54
+ self
48
55
  end
49
56
 
50
57
  def close
51
58
  @body.close if @body.respond_to?(:close)
59
+ ensure
60
+ log(@env, @status, @headers)
52
61
  end
53
62
 
54
63
  def reentrant?
@@ -138,4 +147,16 @@ private
138
147
  }.join('')
139
148
  end
140
149
 
150
+ class ToPath
151
+ def to_path
152
+ rv = (body = clogger.body).to_path
153
+
154
+ # try to avoid unnecessary path lookups with to_io.stat instead of
155
+ # File.stat
156
+ clogger.body_bytes_sent =
157
+ (body.respond_to?(:to_io) ? body.to_io.stat : File.stat(rv)).size
158
+ rv
159
+ end
160
+ end
161
+
141
162
  end