use std::collections::HashMap;
use std::convert::Infallible;
use std::fmt;
use std::io::{self, Read};
use std::str::{self, FromStr};
use std::time::{Duration, SystemTime};
use log::Level;
pub fn parse<R>(reader: R) -> Parser<R>
where
R: Read,
{
Parser {
parsed: 0,
reader,
buf: Vec::with_capacity(4096),
needs_read: true,
hit_eof: false,
}
}
#[derive(Debug)]
pub struct Parser<R> {
reader: R,
parsed: usize,
buf: Vec<u8>,
needs_read: bool,
hit_eof: bool,
}
impl<R: Read> Parser<R> {
fn fill_buf(&mut self) -> io::Result<()> {
self.remove_spaces();
drop(self.buf.drain(..self.parsed));
self.parsed = 0;
if self.buf.len() == self.buf.capacity() {
self.buf.reserve(self.buf.capacity());
}
let original_len = self.buf.len();
self.buf.resize(self.buf.capacity(), 0);
match self.reader.read(&mut self.buf[original_len..]) {
Ok(n) => {
self.buf.truncate(original_len + n);
if n == 0 {
self.hit_eof = true;
}
Ok(())
}
Err(err) => {
self.buf.truncate(original_len);
Err(err)
}
}
}
fn remove_spaces(&mut self) {
let input = &self.buf[self.parsed..];
let input_left = eat_space(input);
self.parsed += input.len() - input_left.len();
}
fn parse_line(&mut self) -> Result<Option<Record>, ParseError> {
let mut record = Record::empty();
let mut record_is_empty = true;
self.remove_spaces();
let mut input = &self.buf[self.parsed..];
loop {
input = eat_space(input);
if input.is_empty() || input[0] == b'\n' {
self.parsed = (self.buf.len() - input.len()) + if input.is_empty() { 0 } else { 1 };
return Ok((!record_is_empty).then(|| record));
}
let (i, key) = parse_key(input).map_err(|err| self.create_line_error(err))?;
if i.is_empty() {
return Ok(None);
}
input = i;
let (i, value) = parse_value(input);
if i.is_empty() && !self.hit_eof {
return Ok(None);
}
input = i;
match key {
"ts" => {
let timestamp =
parse_timestamp(value).map_err(|err| self.create_line_error(err))?;
record.timestamp = Some(timestamp);
}
"lvl" => {
let level =
parse_log_level(value).map_err(|err| self.create_line_error(err))?;
record.level = level;
}
"msg" => {
let msg = parse_string(value).map_err(|err| self.create_line_error(err))?;
record.msg = msg.to_owned();
}
"target" => {
let target = parse_string(value).map_err(|err| self.create_line_error(err))?;
record.target = target.to_owned();
}
"module" => {
let module = parse_string(value).map_err(|err| self.create_line_error(err))?;
if !module.is_empty() {
record.module = Some(module.to_owned());
}
}
"file" => {
let (file, line) =
parse_file(value).map_err(|err| self.create_line_error(err))?;
record.file = Some((file.to_owned(), line));
}
_ => {
let value = parse_string(value).map_err(|err| self.create_line_error(err))?;
let _ = record
.key_values
.insert(key.to_owned(), value.parse().unwrap());
}
}
record_is_empty = false;
}
}
fn create_line_error(&self, kind: ParseErrorKind) -> ParseError {
let line = single_line(&self.buf[self.parsed..])
.to_owned()
.into_boxed_slice();
ParseError {
line: Some(line),
kind,
}
}
}
impl<R: Read> Iterator for Parser<R> {
type Item = Result<Record, ParseError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.needs_read {
match self.fill_buf() {
Ok(()) => { }
Err(err) => {
return Some(Err(ParseError {
line: None,
kind: ParseErrorKind::Io(err),
}));
}
}
}
match self.parse_line() {
Ok(Some(record)) => return Some(Ok(record)),
Ok(None) if self.hit_eof => return None,
Ok(None) => {
self.needs_read = true;
continue;
}
Err(err) => {
if let Some(line) = err.line.as_ref() {
self.parsed += line.len();
if let Some(b'\n') = self.buf.get(self.parsed) {
self.parsed += 1
}
}
return Some(Err(err));
}
}
}
}
}
type ParseResult<'a, T> = Result<(&'a [u8], T), ParseErrorKind>;
#[non_exhaustive]
pub struct ParseError {
pub line: Option<Box<[u8]>>,
pub kind: ParseErrorKind,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(line) = self.line.as_ref() {
write!(
f,
"error parsing log message: {}, in line `{:?}`",
self.kind,
str::from_utf8(line)
.as_ref()
.map_or_else(|line| line as &dyn fmt::Debug, |_| line as &dyn fmt::Debug)
)
} else {
write!(f, "error reading: {}", self.kind)
}
}
}
impl fmt::Debug for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(Debug)]
pub enum ParseErrorKind {
KeyInvalidUt8,
InvalidTimestamp,
InvalidLevel,
InvalidFile,
InvalidValue,
Io(io::Error),
}
#[doc(hidden)]
impl PartialEq for ParseErrorKind {
fn eq(&self, other: &Self) -> bool {
use ParseErrorKind::*;
match (&self, &other) {
(KeyInvalidUt8, KeyInvalidUt8)
| (InvalidTimestamp, InvalidTimestamp)
| (InvalidLevel, InvalidLevel)
| (InvalidFile, InvalidFile)
| (InvalidValue, InvalidValue) => true,
(Io(s_err), Io(o_err)) => match (s_err.raw_os_error(), o_err.raw_os_error()) {
(Some(s), Some(o)) => s == o,
_ => false,
},
_ => false,
}
}
}
impl fmt::Display for ParseErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ParseErrorKind::*;
let msg = match self {
KeyInvalidUt8 => "invalid UTF-8 in key",
InvalidTimestamp => "invalid timestamp",
InvalidLevel => "invalid level",
InvalidFile => "invalid file",
InvalidValue => "invalid UTF-8 in value",
Io(err) => return err.fmt(f),
};
f.write_str(msg)
}
}
fn single_line<'a>(input: &'a [u8]) -> &'a [u8] {
let mut i = 0;
let mut quote_count = 0;
for b in input.iter().copied() {
match b {
b'"' => quote_count += 1,
b'\n' if quote_count % 2 == 0 => break,
_ => {}
}
i += 1;
}
&input[..i]
}
fn eat_space<'a>(input: &'a [u8]) -> &'a [u8] {
let mut i = 0;
for b in input.iter().copied() {
if b != b' ' && b != b'\t' {
break;
}
i += 1;
}
&input[i..]
}
fn eat_space_end<'a>(input: &'a [u8]) -> &'a [u8] {
let mut i = 0;
for b in input.iter().rev().copied() {
if b != b' ' && b != b'\t' {
break;
}
i += 1;
}
&input[..input.len() - i]
}
fn eat_space_both<'a>(input: &'a [u8]) -> &'a [u8] {
eat_space(eat_space_end(input))
}
fn parse_key<'a>(input: &'a [u8]) -> ParseResult<'a, &'a str> {
let mut i = 0;
for b in input.iter().copied() {
if b == b'=' {
break;
}
i += 1;
}
let (mut key_bytes, mut input) = input.split_at(i);
if !input.is_empty() {
input = &input[1..];
}
key_bytes = eat_space_both(key_bytes);
if let (Some(b'"'), Some(b'"')) = (key_bytes.first(), key_bytes.last()) {
key_bytes = eat_space_both(&key_bytes[1..key_bytes.len() - 1]);
}
match str::from_utf8(key_bytes) {
Ok(key) => Ok((input, key)),
Err(_) => Err(ParseErrorKind::KeyInvalidUt8),
}
}
fn parse_timestamp<'a>(value: &'a [u8]) -> Result<SystemTime, ParseErrorKind> {
if value.len() != 27
|| value[4] != b'-'
|| value[7] != b'-'
|| value[10] != b'T'
|| value[13] != b':'
|| value[16] != b':'
|| value[19] != b'.'
|| value[26] != b'Z'
{
return Err(ParseErrorKind::InvalidTimestamp);
}
let value = match str::from_utf8(value) {
Ok(value) => value,
Err(_) => return Err(ParseErrorKind::InvalidTimestamp),
};
#[rustfmt::skip]
let year: i32 = value[0..4].parse().map_err(|_| ParseErrorKind::InvalidTimestamp)?;
#[rustfmt::skip]
let month: i32 = value[5..7].parse().map_err(|_| ParseErrorKind::InvalidTimestamp)?;
#[rustfmt::skip]
let day: i32 = value[8..10].parse().map_err(|_| ParseErrorKind::InvalidTimestamp)?;
#[rustfmt::skip]
let hour: i32 = value[11..13].parse().map_err(|_| ParseErrorKind::InvalidTimestamp)?;
#[rustfmt::skip]
let min: i32 = value[14..16].parse().map_err(|_| ParseErrorKind::InvalidTimestamp)?;
#[rustfmt::skip]
let sec: i32 = value[17..19].parse().map_err(|_| ParseErrorKind::InvalidTimestamp)?;
#[rustfmt::skip]
let nanos: u32 = value[20..26].parse().map_err(|_| ParseErrorKind::InvalidTimestamp)?;
let mut tm = libc::tm {
tm_sec: sec,
tm_min: min,
tm_hour: hour,
tm_mday: day,
tm_mon: month - 1,
tm_year: year - 1900,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: std::ptr::null_mut(),
};
let time_offset = unsafe { libc::timegm(&mut tm) };
Ok(SystemTime::UNIX_EPOCH + Duration::new(time_offset as u64, nanos))
}
fn parse_log_level<'a>(value: &'a [u8]) -> Result<Level, ParseErrorKind> {
match str::from_utf8(value) {
Ok(value) => match value.parse() {
Ok(level) => Ok(level),
Err(_) => Err(ParseErrorKind::InvalidLevel),
},
Err(_) => Err(ParseErrorKind::InvalidLevel),
}
}
fn parse_string<'a>(value: &'a [u8]) -> Result<&'a str, ParseErrorKind> {
match str::from_utf8(value) {
Ok(value) => Ok(value),
Err(_) => Err(ParseErrorKind::InvalidValue),
}
}
fn parse_file<'a>(value: &'a [u8]) -> Result<(&'a str, u32), ParseErrorKind> {
match str::from_utf8(value) {
Ok(value) => {
if let Some((file, column)) = value.rsplit_once(':') {
match column.parse() {
Ok(column) => Ok((file, column)),
Err(_) => Err(ParseErrorKind::InvalidFile),
}
} else {
Err(ParseErrorKind::InvalidFile)
}
}
Err(_) => Err(ParseErrorKind::InvalidFile),
}
}
fn parse_value<'a>(input: &'a [u8]) -> (&'a [u8], &'a [u8]) {
let input = eat_space(input);
if input.first().copied() == Some(b'"') {
parse_quoted_value(input)
} else {
parse_naked_value(input)
}
}
fn parse_quoted_value<'a>(input: &'a [u8]) -> (&'a [u8], &'a [u8]) {
debug_assert!(input[0] == b'"');
let mut i = 1;
let mut quote_count = 1;
let bytes = input.iter().skip(1).copied();
for b in bytes {
match b {
b'"' => quote_count += 1,
b'=' if quote_count % 2 == 0 => break,
_ => {}
}
i += 1;
}
let input_value = &input[1..i];
for b in input_value.iter().rev().copied() {
i -= 1;
if b == b'"' {
break;
}
}
let value = &input[1..i];
let input = if i == input.len() {
&[]
} else {
&input[i + 1..]
};
(input, value)
}
fn parse_naked_value<'a>(input: &'a [u8]) -> (&'a [u8], &'a [u8]) {
let mut i = 0;
for b in input.iter().copied() {
if b == b' ' || b == b'\n' {
break;
}
i += 1;
}
let value = &input[..i];
let input = &input[i..];
(input, value)
}
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub struct Record {
pub timestamp: Option<SystemTime>,
pub level: Level,
pub msg: String,
pub target: String,
pub module: Option<String>,
pub file: Option<(String, u32)>,
pub key_values: HashMap<String, Value>,
}
#[derive(Debug, PartialEq)]
pub enum Value {
Bool(bool),
Int(i64),
Float(f64),
String(String),
}
impl FromStr for Value {
type Err = Infallible;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if let Ok(b) = value.parse() {
Ok(Value::Bool(b))
} else if let Ok(i) = value.parse() {
Ok(Value::Int(i))
} else if let Ok(f) = value.parse() {
Ok(Value::Float(f))
} else {
Ok(Value::String(value.to_owned()))
}
}
}
impl Record {
#[doc(hidden)]
pub fn empty() -> Record {
Record {
timestamp: None,
level: Level::Info,
msg: String::new(),
target: String::new(),
module: None,
file: None,
key_values: HashMap::new(),
}
}
}