1 # -*- encoding: binary -*-
4 # See the README for usage instructions
19 # support nginx variables that are less customizable than our own
21 '$request_time' => '$request_time{3}',
22 '$msec' => '$time{3}',
23 '$usec' => '$time{6}',
24 '$http_content_length' => '$content_length',
25 '$http_content_type' => '$content_type',
29 :body_bytes_sent => 0,
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 || -
44 CGI_ENV = Regexp.new('\A\$(' <<
45 %w(request_method content_length content_type
46 remote_addr remote_ident remote_user
47 path_info query_string script_name
48 server_name server_port
49 auth_type gateway_interface server_software path_translated
52 SCAN = /([^$]*)(\$+(?:env\{\w+(?:\.[\w\.]+)?\}|
54 (?:request_)?time\{\d+(?:,\d+)?\}|
55 time_(?:utc|local)\{[^\}]+\}|
58 def compile_format(str, opt = {})
59 str = Clogger::Format.const_get(str) if Symbol === str
60 longest_day = Time.at(26265600) # "Saturday, November 01, 1970 00:00:00"
63 str.scan(SCAN).each do |pre,tok,post|
64 rv << [ OP_LITERAL, pre ] if pre && pre != ""
67 if tok.sub!(/\A(\$+)\$/, '$')
68 rv << [ OP_LITERAL, $1 ]
71 compat = ALIASES[tok] and tok = compat
75 rv << [ OP_LITERAL, $1 ]
76 when /\A\$env\{(\w+(?:\.[\w\.]+))\}\z/
77 rv << [ OP_REQUEST, $1 ]
78 when /\A\$e\{([^\}]+)\}\z/
80 when /\A\$cookie_(\w+)\z/
81 rv << [ OP_COOKIE, $1 ]
82 when CGI_ENV, /\A\$(http_\w+)\z/
83 rv << [ OP_REQUEST, $1.upcase ]
84 when /\A\$sent_http_(\w+)\z/
85 rv << [ OP_RESPONSE, $1.downcase.tr('_','-') ]
86 when /\A\$time_local\{([^\}]+)\}\z/
88 rv << [ OP_TIME_LOCAL, fmt, longest_day.strftime(fmt) ]
89 when /\A\$time_utc\{([^\}]+)\}\z/
91 rv << [ OP_TIME_UTC, fmt, longest_day.strftime(fmt) ]
92 when /\A\$time\{(\d+)\}\z/
93 rv << [ OP_TIME, *usec_conv_pair(tok, $1.to_i) ]
94 when /\A\$request_time\{(\d+)\}\z/
95 rv << [ OP_REQUEST_TIME, *usec_conv_pair(tok, $1.to_i), 0 ]
96 when /\A\$request_time\{(\d+),(\d+)\}\z/
99 if ipow > 9 # nanosecond precision is the highest POSIX goes
100 raise ArgumentError, "#{tok}: too big: #{ipow} (max=9)"
102 rv << [ OP_REQUEST_TIME, *usec_conv_pair(tok, prec), ipow ]
104 tok_sym = tok[1..-1].to_sym
105 if special_code = SPECIAL_VARS[tok_sym]
106 rv << [ OP_SPECIAL, special_code ]
108 raise ArgumentError, "unable to make sense of token: #{tok}"
113 rv << [ OP_LITERAL, post ] if post && post != ""
116 # auto-append a newline
117 last = rv.last or return rv
119 ors = opt[:ORS] || "\n"
120 if (op == OP_LITERAL && /#{ors}\z/ !~ last.last) || op != OP_LITERAL
121 rv << [ OP_LITERAL, ors ] if ors.size > 0
127 def usec_conv_pair(tok, prec)
129 [ "%d", 1 ] # stupid...
131 raise ArgumentError, "#{tok}: too high precision: #{prec} (max=6)"
133 [ "%d.%0#{prec}d", 10 ** (6 - prec) ]
137 def need_response_headers?(fmt_ops)
138 fmt_ops.any? { |op| OP_RESPONSE == op[0] }
141 def need_wrap_body?(fmt_ops)
143 (OP_REQUEST_TIME == op[0]) || (OP_SPECIAL == op[0] &&
144 (SPECIAL_VARS[:body_bytes_sent] == op[1] ||
145 SPECIAL_VARS[:response_length] == op[1]))
150 def method_missing(*args, &block)
151 body.__send__(*args, &block)
156 require 'clogger/format'
159 raise LoadError if ENV['CLOGGER_PURE'].to_i != 0
160 require 'clogger_ext'
162 require 'clogger/pure'