clogger 0.5.0 - body.to_path forwarding
[clogger.git] / lib / clogger.rb
blob1d4e012ff466000d837cd4786684b5fe7745a5c1
1 # -*- encoding: binary -*-
2 require 'rack'
4 # See the README for usage instructions
5 class Clogger
7   # the version of Clogger, currently 0.5.0
8   VERSION = '0.5.0'
10   # :stopdoc:
11   OP_LITERAL = 0
12   OP_REQUEST = 1
13   OP_RESPONSE = 2
14   OP_SPECIAL = 3
15   OP_EVAL = 4
16   OP_TIME_LOCAL = 5
17   OP_TIME_UTC = 6
18   OP_REQUEST_TIME = 7
19   OP_TIME = 8
20   OP_COOKIE = 9
22   # support nginx variables that are less customizable than our own
23   ALIASES = {
24     '$request_time' => '$request_time{3}',
25     '$time_local' => '$time_local{%d/%b/%Y:%H:%M:%S %z}',
26     '$msec' => '$time{3}',
27     '$usec' => '$time{6}',
28     '$http_content_length' => '$content_length',
29     '$http_content_type' => '$content_type',
30   }
32   SPECIAL_VARS = {
33     :body_bytes_sent => 0,
34     :status => 1,
35     :request => 2, # REQUEST_METHOD PATH_INFO?QUERY_STRING HTTP_VERSION
36     :request_length => 3, # env['rack.input'].size
37     :response_length => 4, # like body_bytes_sent, except "-" instead of "0"
38     :ip => 5, # HTTP_X_FORWARDED_FOR || REMOTE_ADDR || -
39     :pid => 6, # getpid()
40     :request_uri => 7
41   }
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
49     # to_path is defined in Clogger::Pure or the C extension
50   end
52 private
54   CGI_ENV = Regexp.new('\A\$(' <<
55       %w(request_method content_length content_type
56          remote_addr remote_ident remote_user
57          path_info query_string script_name
58          server_name server_port
59          auth_type gateway_interface server_software path_translated
60          ).join('|') << ')\z').freeze
62   SCAN = /([^$]*)(\$+(?:env\{\w+(?:\.[\w\.]+)?\}|
63                         e\{[^\}]+\}|
64                         (?:request_)?time\{\d+\}|
65                         time_(?:utc|local)\{[^\}]+\}|
66                         \w*))?([^$]*)/x
68   def compile_format(str, opt = {})
69     rv = []
70     opt ||= {}
71     str.scan(SCAN).each do |pre,tok,post|
72       rv << [ OP_LITERAL, pre ] if pre && pre != ""
74       unless tok.nil?
75         if tok.sub!(/\A(\$+)\$/, '$')
76           rv << [ OP_LITERAL, $1.dup ]
77         end
79         compat = ALIASES[tok] and tok = compat
81         case tok
82         when /\A(\$*)\z/
83           rv << [ OP_LITERAL, $1.dup ]
84         when /\A\$env\{(\w+(?:\.[\w\.]+))\}\z/
85           rv << [ OP_REQUEST, $1.freeze ]
86         when /\A\$e\{([^\}]+)\}\z/
87           rv << [ OP_EVAL, $1.dup ]
88         when /\A\$cookie_(\w+)\z/
89           rv << [ OP_COOKIE, $1.dup.freeze ]
90         when CGI_ENV, /\A\$(http_\w+)\z/
91           rv << [ OP_REQUEST, $1.upcase.freeze ]
92         when /\A\$sent_http_(\w+)\z/
93           rv << [ OP_RESPONSE, $1.downcase.tr('_','-').freeze ]
94         when /\A\$time_local\{([^\}]+)\}\z/
95           rv << [ OP_TIME_LOCAL, $1.dup ]
96         when /\A\$time_utc\{([^\}]+)\}\z/
97           rv << [ OP_TIME_UTC, $1.dup ]
98         when /\A\$time\{(\d+)\}\z/
99           rv << [ OP_TIME, *usec_conv_pair(tok, $1.to_i) ]
100         when /\A\$request_time\{(\d+)\}\z/
101           rv << [ OP_REQUEST_TIME, *usec_conv_pair(tok, $1.to_i) ]
102         else
103           tok_sym = tok[1..-1].to_sym
104           if special_code = SPECIAL_VARS[tok_sym]
105             rv << [ OP_SPECIAL, special_code ]
106           else
107             raise ArgumentError, "unable to make sense of token: #{tok}"
108           end
109         end
110       end
112       rv << [ OP_LITERAL, post ] if post && post != ""
113     end
115     # auto-append a newline
116     last = rv.last or return rv
117     op = last.first
118     ors = opt[:ORS] || "\n"
119     if (op == OP_LITERAL && /#{ors}\z/ !~ last.last) || op != OP_LITERAL
120       rv << [ OP_LITERAL, ors ] if ors.size > 0
121     end
123     rv
124   end
126   def usec_conv_pair(tok, prec)
127     if prec == 0
128       [ "%d", 1 ] # stupid...
129     elsif prec > 6
130       raise ArgumentError, "#{tok}: too high precision: #{prec} (max=6)"
131     else
132       [ "%d.%0#{prec}d", 10 ** (6 - prec) ]
133     end
134   end
136   def need_response_headers?(fmt_ops)
137     fmt_ops.any? { |op| OP_RESPONSE == op[0] }
138   end
140   def need_wrap_body?(fmt_ops)
141     fmt_ops.any? do |op|
142       (OP_REQUEST_TIME == op[0]) || (OP_SPECIAL == op[0] &&
143         (SPECIAL_VARS[:body_bytes_sent] == op[1] ||
144          SPECIAL_VARS[:response_length] == op[1]))
145     end
146   end
148   # :startdoc:
151 require 'clogger/format'
153 begin
154   raise LoadError if ENV['CLOGGER_PURE'].to_i != 0
155   require 'clogger_ext'
156 rescue LoadError
157   require 'clogger/pure'