From 2a45b7ebc02873a5adb93d05b30ac745dab64ce7 Mon Sep 17 00:00:00 2001 From: Chris Lowder Date: Fri, 21 Apr 2023 17:11:17 +0100 Subject: [PATCH] Tolerate garbage preceding logfmt Accommodates Rails's default tagged logger. Nom doesn't support just dropping the garbage[^1], it's accumulated in a newly allocated vector. [^1]: https://github.com/rust-bakery/nom/issues/1594 --- src/parser.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index ced4c6d..c6a59d5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use nom::{ branch::alt, bytes::complete::{escaped_transform, tag, take_while1}, - character::complete::{none_of, one_of, space0}, - combinator::{eof, opt}, - multi::fold_many1, + character::complete::{anychar, none_of, one_of, space0}, + combinator::{eof, opt, peek}, + multi::{fold_many1, many_till}, sequence::{delimited, terminated, tuple}, IResult, }; @@ -49,7 +49,9 @@ fn pairs(input: &str) -> IResult<&str, HashMap>> { } pub fn parse(message: &str) -> Option>> { - pairs(message).map(|(_, result)| result).ok() + tuple((many_till(anychar, peek(pair)), pairs))(message) + .map(|(_rest, (_garbage, result))| result) + .ok() } #[cfg(test)] @@ -98,6 +100,27 @@ mod tests { ); } + #[test] + fn test_lograge_lines_with_rails_tagged_prefix() { + assert_eq!( + Some(HashMap::from([ + pair("at", Some("info")), + pair("method", Some("POST")), + pair("path", Some("/foo/bar")), + pair("host", Some("example.com")), + pair("request_id", Some("f116113c-b8ed-41ea-bbf3-a031313dd936")), + pair("fwd", Some("0.0.0.0")), + pair("dyno", Some("web.1")), + pair("connect", Some("0ms")), + pair("service", Some("25ms")), + pair("status", Some("204")), + pair("bytes", Some("490")), + pair("protocol", Some("http")), + ])), + parse("I, [2022-08-05T15:55:06.335844 #56] INFO -- : [242dc622-3727-4e5e-ac6e-fcf121a1a532] at=info method=POST path=\"/foo/bar\" host=example.com request_id=f116113c-b8ed-41ea-bbf3-a031313dd936 fwd=\"0.0.0.0\" dyno=web.1 connect=0ms service=25ms status=204 bytes=490 protocol=http") + ); + } + #[test] fn test_edge_cases() { // leading whitespace is discarded