module HAProxyLogParser

grammar Line
  rule line
    syslog_portion:([^\[]+ '[' integer ']: ')
    client_ip:ip_address ':' client_port:integer ' '
    '[' accept_date '] '
    suffix:(normal_suffix / error_suffix)
    "\n"?
  end

  rule normal_suffix
    frontend_name:proxy_name transport_mode:'~'? ' '
    backend_name:proxy_name '/' server_name ' '
    tq:integer '/' tw:integer '/' tc:integer '/' tr:integer '/' tt:integer ' '
    status_code:integer ' '
    bytes_read:integer ' '
    captured_request_cookie:([^ ]+) ' '
    captured_response_cookie:([^ ]+) ' '
    termination_state ' '
    actconn:integer '/' feconn:integer '/' beconn:integer '/'
      srv_conn:integer '/' retries:integer ' '
    srv_queue:integer '/' backend_queue:integer ' '
    captured_request_headers:('{' headers:captured_headers '} ')?
    captured_response_headers:('{' headers:captured_headers '} ')?
    '"' http_request:[^"]+ '"'
  end

  rule error_suffix
    frontend_name:proxy_name '/' bind_name ': '
    message:[^\n]+
  end

  rule integer
    ('-' / '+')? [0-9]+
  end

  rule time
    ([0-9] 2..2 ':') 2..2 ([0-9] 2..2)
  end

  rule ip_address
    # Asserting port is a little hacky but required because of the way
    # HAProxy writes IPv6 addresses in the logs. It uses a valid RFC5952
    # method of IP:PORT, but this complicates things by having valid IP:PORT
    # combos that are themselves valid IPv6 addresses.
    # e.g. 1::2:123
    ip4_address / ip6_address_assert_port
  end

  rule ip4_address
    dec_octet '.' dec_octet '.' dec_octet '.' dec_octet
  end

  rule colon_port
    ':' integer ' '
  end

  rule ip6_address_assert_port
    # Taken from https://tools.ietf.org/html/rfc3986#section-3.2.2
    # Performs look-ahead assertion on the port, assuming format IP:PORT
                                    ( h16 ':' ) 6..6 ls32 &colon_port
    /                          '::' ( h16 ':' ) 5..5 ls32 &colon_port
    / ( h16                 )? '::' ( h16 ':' ) 4..4 ls32 &colon_port
    / ( h16 ( ':' h16 ) ..1 )? '::' ( h16 ':' ) 3..3 ls32 &colon_port
    / ( h16 ( ':' h16 ) ..2 )? '::' ( h16 ':' ) 2..2 ls32 &colon_port
    / ( h16 ( ':' h16 ) ..3 )? '::'   h16 ':'        ls32 &colon_port
    / ( h16 ( ':' h16 ) ..4 )? '::'                  ls32 &colon_port
    / ( h16 ( ':' h16 ) ..5 )? '::'                  h16  &colon_port
    / ( h16 ( ':' h16 ) ..6 )? '::'                       &colon_port
  end

  rule ls32
    # least-significant 32 bits of address
    ( h16 ':' h16 ) / ip4_address
  end

  rule h16
    # 16 bits of address represented in hexadecimal
    hex_digit 1..4
  end

  rule hex_digit
    [a-f0-9A-F]
  end

  rule dec_octet
    '25' [0-5]
    /  '2' [0-4] dec_digit
    /  '1' dec_digit 2..2
    /  dec_digit 1..2
  end

  rule dec_digit
    [0-9]
  end

  rule accept_date
    [0-9] 2..2 '/' [A-Z] [a-z] 2..2 '/' [0-9]+ ':' time '.' ([0-9] 3..3)
  end

  rule proxy_name
    [-_A-Za-z0-9.:]+
  end

  rule server_name
    proxy_name / '<NOSRV>' / '<STATS>'
  end

  rule termination_state
    [-A-Za-z] 4..4
  end

  rule captured_headers
    [^}]*
  end

  rule bind_name
    [-_A-Za-z0-9.]+
  end
end

end