remove Clogger::ToPath proxy class
[clogger.git] / lib / clogger.rb
bloba64ca092a8187b981b71b4c52a88110e5d5a9267
1 # -*- encoding: binary -*-
2 require 'rack'
4 # See the README for usage instructions
5 class Clogger
7   # the version of Clogger, currently 0.6.0
8   VERSION = '0.6.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 private
45   CGI_ENV = Regexp.new('\A\$(' <<
46       %w(request_method content_length content_type
47          remote_addr remote_ident remote_user
48          path_info query_string script_name
49          server_name server_port
50          auth_type gateway_interface server_software path_translated
51          ).join('|') << ')\z')
53   SCAN = /([^$]*)(\$+(?:env\{\w+(?:\.[\w\.]+)?\}|
54                         e\{[^\}]+\}|
55                         (?:request_)?time\{\d+\}|
56                         time_(?:utc|local)\{[^\}]+\}|
57                         \w*))?([^$]*)/x
59   def compile_format(str, opt = {})
60     rv = []
61     opt ||= {}
62     str.scan(SCAN).each do |pre,tok,post|
63       rv << [ OP_LITERAL, pre ] if pre && pre != ""
65       unless tok.nil?
66         if tok.sub!(/\A(\$+)\$/, '$')
67           rv << [ OP_LITERAL, $1 ]
68         end
70         compat = ALIASES[tok] and tok = compat
72         case tok
73         when /\A(\$*)\z/
74           rv << [ OP_LITERAL, $1 ]
75         when /\A\$env\{(\w+(?:\.[\w\.]+))\}\z/
76           rv << [ OP_REQUEST, $1 ]
77         when /\A\$e\{([^\}]+)\}\z/
78           rv << [ OP_EVAL, $1 ]
79         when /\A\$cookie_(\w+)\z/
80           rv << [ OP_COOKIE, $1 ]
81         when CGI_ENV, /\A\$(http_\w+)\z/
82           rv << [ OP_REQUEST, $1.upcase ]
83         when /\A\$sent_http_(\w+)\z/
84           rv << [ OP_RESPONSE, $1.downcase.tr('_','-') ]
85         when /\A\$time_local\{([^\}]+)\}\z/
86           rv << [ OP_TIME_LOCAL, $1 ]
87         when /\A\$time_utc\{([^\}]+)\}\z/
88           rv << [ OP_TIME_UTC, $1 ]
89         when /\A\$time\{(\d+)\}\z/
90           rv << [ OP_TIME, *usec_conv_pair(tok, $1.to_i) ]
91         when /\A\$request_time\{(\d+)\}\z/
92           rv << [ OP_REQUEST_TIME, *usec_conv_pair(tok, $1.to_i) ]
93         else
94           tok_sym = tok[1..-1].to_sym
95           if special_code = SPECIAL_VARS[tok_sym]
96             rv << [ OP_SPECIAL, special_code ]
97           else
98             raise ArgumentError, "unable to make sense of token: #{tok}"
99           end
100         end
101       end
103       rv << [ OP_LITERAL, post ] if post && post != ""
104     end
106     # auto-append a newline
107     last = rv.last or return rv
108     op = last.first
109     ors = opt[:ORS] || "\n"
110     if (op == OP_LITERAL && /#{ors}\z/ !~ last.last) || op != OP_LITERAL
111       rv << [ OP_LITERAL, ors ] if ors.size > 0
112     end
114     rv
115   end
117   def usec_conv_pair(tok, prec)
118     if prec == 0
119       [ "%d", 1 ] # stupid...
120     elsif prec > 6
121       raise ArgumentError, "#{tok}: too high precision: #{prec} (max=6)"
122     else
123       [ "%d.%0#{prec}d", 10 ** (6 - prec) ]
124     end
125   end
127   def need_response_headers?(fmt_ops)
128     fmt_ops.any? { |op| OP_RESPONSE == op[0] }
129   end
131   def need_wrap_body?(fmt_ops)
132     fmt_ops.any? do |op|
133       (OP_REQUEST_TIME == op[0]) || (OP_SPECIAL == op[0] &&
134         (SPECIAL_VARS[:body_bytes_sent] == op[1] ||
135          SPECIAL_VARS[:response_length] == op[1]))
136     end
137   end
139   # :startdoc:
142 require 'clogger/format'
144 begin
145   raise LoadError if ENV['CLOGGER_PURE'].to_i != 0
146   require 'clogger_ext'
147 rescue LoadError
148   require 'clogger/pure'