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