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