今更感が強いですが実際ログをLTSVにすると扱いやすい場面に最近遭遇したので。
ltsv_formatter.rb
require 'logger'
class Logger
class FreeFormatter < Formatter
Format = "%{label}, [%{time}#%{pid}] %<severity>5s -- %{progname}: %{message}\n"
attr_accessor :format
def initialize(format=self.class::Format)
@format = format || Format
super()
end
def call(severity, time, progname, msg)
@format % {
:label => severity[0..0],
:time => format_datetime(time),
:pid => $$,
:severity => severity,
:progname => progname,
:message => msg2str(msg)
}
end
end
class LTSVFormatter < FreeFormatter
Format = %w[severity time progname pid message].map {|field|
"#{field}:%{#{field}}"
}.join("\t") + "\n"
private
def format_datetime(time)
if @datetime_format.nil?
time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d" % time.usec
else
super
end
end
end
end
if __FILE__ == $0
logger = Logger.new($stderr)
logger.formatter = Logger::FreeFormatter.new
logger.debug 'hello'
logger.info 'world'
logger.formatter = Logger::LTSVFormatter.new
logger.warn 'hi'
logger.error 'again'
end
ltsv_logger.ru
class LTSVLogger < Rack::CommonLogger
FORMAT = %w[host user time method path query version status size].map {|field| "#{field}:%{#{field}}"}.join("\t") + "\truntime:%<runtime>0.4f\n"
def log(env, status, header, began_at)
now = Time.now
length = extract_content_length(header)
logger = @logger || env['rack.errors']
logger.write FORMAT % {
host: env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
user: env["REMOTE_USER"] || "-",
time: now.strftime("%d/%b/%Y %H:%M:%S"),
method: env["REQUEST_METHOD"],
path: env["PATH_INFO"],
query: env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
version: env["HTTP_VERSION"],
status: status.to_s[0..3],
size: length,
runtime: now - began_at }
end
end
use LTSVLogger
run lambda {|env|
[200, {'Content-Type' => 'text/plain'}, ['Hello, LTSV']]
}