clogger.git  about / heads / tags
configurable request logging for Rack
blob 11c03f432af53338c22224581b782dfc0f839040 3232 bytes (raw)
$ git show v0.0.1:lib/clogger/pure.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
 
# -*- encoding: binary -*-
# :stopdoc:
#
# Not at all optimized for performance, this was written based on
# the original C extension code so it's not very Ruby-ish...
class Clogger

  def initialize(app, opts = {})
    @app = app
    @logger = opts[:logger]
    @fmt_ops = compile_format(opts[:format] || Format::Common)
    @wrap_body = need_wrap_body?(@fmt_ops)
    @reentrant = nil
    @body_bytes_sent = 0
  end

  def call(env)
    @start = Time.now
    status, headers, body = @app.call(env)
    if wrap_body?
      @reentrant = env['rack.multithread']
      @env, @status, @headers, @body = env, status, headers, body
      return [ status, headers, reentrant? ? self.dup : self ]
    end
    log(env, status, headers)
    [ status, headers, body ]
  end

  def each
    @body_bytes_sent = 0
    @body.each do |part|
      @body_bytes_sent += part.size
      yield part
    end
    ensure
      log(@env, @status, @headers)
  end

  def close
    @body.close
  end

  def reentrant?
    @reentrant
  end

  def wrap_body?
    @wrap_body
  end

  def fileno
    @logger.fileno rescue nil
  end

private

  def byte_xs(s)
    s = s.dup
    s.force_encoding(Encoding::BINARY) if defined?(Encoding::BINARY)
    s.gsub!(/(['"\x00-\x1f])/) { |x| "\\x#{$1.unpack('H2').first}" }
    s
  end

  SPECIAL_RMAP = SPECIAL_VARS.inject([]) { |ary, (k,v)| ary[v] = k; ary }

  def special_var(special_nr, env, status, headers)
    case SPECIAL_RMAP[special_nr]
    when :body_bytes_sent
      @body_bytes_sent.to_s
    when :status
      status = status.to_i
      status >= 100 && status <= 999 ? ('%03d' % status) : '-'
    when :request
      qs = env['QUERY_STRING']
      qs.empty? or qs = "?#{byte_xs(qs)}"
      "#{env['REQUEST_METHOD']} " \
        "#{byte_xs(env['PATH_INFO'])}#{qs} " \
        "#{byte_xs(env['HTTP_VERSION'])}"
    when :request_length
      env['rack.input'].size.to_s
    when :response_length
      @body_bytes_sent == 0 ? '-' : @body_bytes_sent.to_s
    when :ip
      xff = env['HTTP_X_FORWARDED_FOR'] and return byte_xs(xff)
      env['REMOTE_ADDR'] || '-'
    when :pid
      $$.to_s
    else
      raise "EDOOFUS #{special_nr}"
    end
  end

  def time_format(sec, usec, format, div)
    format % [ sec, usec / div ]
  end

  def log(env, status, headers)
    (@logger || env['rack.errors']) << @fmt_ops.map { |op|
      case op[0]
      when OP_LITERAL; op[1]
      when OP_REQUEST; byte_xs(env[op[1]] || "-")
      when OP_RESPONSE; byte_xs(get_sent_header(headers, op[1]))
      when OP_SPECIAL; special_var(op[1], env, status, headers)
      when OP_EVAL; eval(op[1]).to_s rescue "-"
      when OP_TIME_LOCAL; Time.now.strftime(op[1])
      when OP_TIME_UTC; Time.now.utc.strftime(op[1])
      when OP_REQUEST_TIME
        t = Time.now - @start
        time_format(t.to_i, (t - t.to_i) * 1000000, op[1], op[2])
      when OP_TIME
        t = Time.now
        time_format(t.sec, t.usec, op[1], op[2])
      when OP_COOKIE
        (env['rack.request.cookie_hash'][op[1]] rescue "-") || "-"
      else
        raise "EDOOFUS #{op.inspect}"
      end
    }.join('')
  end

  def get_sent_header(headers, match)
    headers.each { |key, value| match == key.downcase and return value }
    "-"
  end

end

git clone https://yhbt.net/clogger.git