clogger 0.8.0 - delegating away!
[clogger.git] / lib / clogger.rb
blob5542910036d9eda94da533c17a5243ee198c2780
1 # -*- encoding: binary -*-
2 require 'rack'
4 # See the README for usage instructions
5 class Clogger
7   # the version of Clogger, currently 0.8.0
8   VERSION = '0.8.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     '$time_utc' => '$time_utc{%d/%b/%Y:%H:%M:%S %z}',
27     '$msec' => '$time{3}',
28     '$usec' => '$time{6}',
29     '$http_content_length' => '$content_length',
30     '$http_content_type' => '$content_type',
31   }
33   SPECIAL_VARS = {
34     :body_bytes_sent => 0,
35     :status => 1,
36     :request => 2, # REQUEST_METHOD PATH_INFO?QUERY_STRING HTTP_VERSION
37     :request_length => 3, # env['rack.input'].size
38     :response_length => 4, # like body_bytes_sent, except "-" instead of "0"
39     :ip => 5, # HTTP_X_FORWARDED_FOR || REMOTE_ADDR || -
40     :pid => 6, # getpid()
41     :request_uri => 7
42   }
44 private
46   CGI_ENV = Regexp.new('\A\$(' <<
47       %w(request_method content_length content_type
48          remote_addr remote_ident remote_user
49          path_info query_string script_name
50          server_name server_port
51          auth_type gateway_interface server_software path_translated
52          ).join('|') << ')\z')
54   SCAN = /([^$]*)(\$+(?:env\{\w+(?:\.[\w\.]+)?\}|
55                         e\{[^\}]+\}|
56                         (?:request_)?time\{\d+\}|
57                         time_(?:utc|local)\{[^\}]+\}|
58                         \w*))?([^$]*)/x
60   def compile_format(str, opt = {})
61     longest_day = Time.at(26265600) # "Saturday, November 01, 1970 00:00:00"
62     rv = []
63     opt ||= {}
64     str.scan(SCAN).each do |pre,tok,post|
65       rv << [ OP_LITERAL, pre ] if pre && pre != ""
67       unless tok.nil?
68         if tok.sub!(/\A(\$+)\$/, '$')
69           rv << [ OP_LITERAL, $1 ]
70         end
72         compat = ALIASES[tok] and tok = compat
74         case tok
75         when /\A(\$*)\z/
76           rv << [ OP_LITERAL, $1 ]
77         when /\A\$env\{(\w+(?:\.[\w\.]+))\}\z/
78           rv << [ OP_REQUEST, $1 ]
79         when /\A\$e\{([^\}]+)\}\z/
80           rv << [ OP_EVAL, $1 ]
81         when /\A\$cookie_(\w+)\z/
82           rv << [ OP_COOKIE, $1 ]
83         when CGI_ENV, /\A\$(http_\w+)\z/
84           rv << [ OP_REQUEST, $1.upcase ]
85         when /\A\$sent_http_(\w+)\z/
86           rv << [ OP_RESPONSE, $1.downcase.tr('_','-') ]
87         when /\A\$time_local\{([^\}]+)\}\z/
88           fmt = $1
89           rv << [ OP_TIME_LOCAL, fmt, longest_day.strftime(fmt) ]
90         when /\A\$time_utc\{([^\}]+)\}\z/
91           fmt = $1
92           rv << [ OP_TIME_UTC, fmt, longest_day.strftime(fmt) ]
93         when /\A\$time\{(\d+)\}\z/
94           rv << [ OP_TIME, *usec_conv_pair(tok, $1.to_i) ]
95         when /\A\$request_time\{(\d+)\}\z/
96           rv << [ OP_REQUEST_TIME, *usec_conv_pair(tok, $1.to_i) ]
97         else
98           tok_sym = tok[1..-1].to_sym
99           if special_code = SPECIAL_VARS[tok_sym]
100             rv << [ OP_SPECIAL, special_code ]
101           else
102             raise ArgumentError, "unable to make sense of token: #{tok}"
103           end
104         end
105       end
107       rv << [ OP_LITERAL, post ] if post && post != ""
108     end
110     # auto-append a newline
111     last = rv.last or return rv
112     op = last.first
113     ors = opt[:ORS] || "\n"
114     if (op == OP_LITERAL && /#{ors}\z/ !~ last.last) || op != OP_LITERAL
115       rv << [ OP_LITERAL, ors ] if ors.size > 0
116     end
118     rv
119   end
121   def usec_conv_pair(tok, prec)
122     if prec == 0
123       [ "%d", 1 ] # stupid...
124     elsif prec > 6
125       raise ArgumentError, "#{tok}: too high precision: #{prec} (max=6)"
126     else
127       [ "%d.%0#{prec}d", 10 ** (6 - prec) ]
128     end
129   end
131   def need_response_headers?(fmt_ops)
132     fmt_ops.any? { |op| OP_RESPONSE == op[0] }
133   end
135   def need_wrap_body?(fmt_ops)
136     fmt_ops.any? do |op|
137       (OP_REQUEST_TIME == op[0]) || (OP_SPECIAL == op[0] &&
138         (SPECIAL_VARS[:body_bytes_sent] == op[1] ||
139          SPECIAL_VARS[:response_length] == op[1]))
140     end
141   end
143 private
144   def method_missing(*args, &block)
145     body.__send__(*args, &block)
146   end
147   # :startdoc:
150 require 'clogger/format'
152 begin
153   raise LoadError if ENV['CLOGGER_PURE'].to_i != 0
154   require 'clogger_ext'
155 rescue LoadError
156   require 'clogger/pure'