allow using custom date format

This commit is contained in:
Robin Appelman 2025-02-20 23:26:17 +01:00
commit 1f8cbbf2b8
3 changed files with 44 additions and 1 deletions

View file

@ -18,6 +18,19 @@ logsmash ./logfile.log
Logsmash supports both loading plain log files, compressed log files (`.gz`, `.bz2`, `.xz` or `.zst`), or archives
containing log files (`.zip` or (compressed) `.tar`).
### Date formats
Since not all log files use the same date format, logsmash tries to parse each data with a number of different log
formats.
If the log file you're opening is using an unsupported log format, you can specify a custom date format with the
`--date-format` option.
The data format can either be in [the strftime format](https://man7.org/linux/man-pages/man3/strftime.3.html) or
in [the time crate format (version 2)](https://time-rs.github.io/book/api/format-description.html).
For example: `[day].[month].[year] - [hour]:[minute]:[second]`.
## Log sources
Logsmash is built around matching log line to their source, either a call to a logging function or an exception being

View file

@ -6,8 +6,10 @@ use serde_json::Value;
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::OnceLock;
use time::format_description::well_known::iso8601::{Config, EncodedConfig, TimePrecision};
use time::format_description::well_known::Iso8601;
use time::format_description::OwnedFormatItem;
use time::OffsetDateTime;
use tinystr::TinyAsciiStr;
@ -17,6 +19,9 @@ pub const TIME_FORMAT: EncodedConfig = Config::DEFAULT
})
.encode();
// ugly global because passing state to serde is hard
pub static CUSTOM_TIME_FORMAT: OnceLock<Option<OwnedFormatItem>> = OnceLock::new();
#[derive(Deserialize, Clone)]
pub struct LogLine<'a> {
#[serde(default)]
@ -38,6 +43,7 @@ pub struct LogLine<'a> {
}
mod date {
use crate::logline::CUSTOM_TIME_FORMAT;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use time::format_description::well_known::Iso8601;
@ -70,6 +76,12 @@ mod date {
) -> Result<OffsetDateTime, D::Error> {
let str = <&str>::deserialize(deserializer)?;
if let Some(Some(format)) = CUSTOM_TIME_FORMAT.get() {
if let Some(date) = try_format(str, format) {
return Ok(date);
}
}
if let Some(date) = try_format(str, &Iso8601::DATE_TIME_OFFSET) {
return Ok(date);
}

View file

@ -1,7 +1,7 @@
use crate::app::{App, LogMatch};
use crate::error::LogError;
use crate::logfile::LogFile;
use crate::logline::{Exception, FullException, FullLogLine, LogLine};
use crate::logline::{Exception, FullException, FullLogLine, LogLine, CUSTOM_TIME_FORMAT};
use crate::matcher::{MatchResult, Matcher};
use crate::ui::run_ui;
use base64::prelude::*;
@ -26,6 +26,7 @@ mod ui;
#[cfg(not(target_os = "windows"))]
use tikv_jemallocator::Jemalloc;
use time::format_description::{parse_owned, parse_strftime_owned};
#[cfg(not(target_os = "windows"))]
#[global_allocator]
@ -37,11 +38,28 @@ struct Args {
/// Collect data and exit, intended for profiling
#[arg(long)]
profile: bool,
/// Date format to use when parsing log lines
#[arg(long)]
date_format: Option<String>,
}
fn main() -> MainResult {
let args = Args::parse();
if let Some(date_format) = args.date_format.as_deref() {
let date_format = if date_format.contains('%') {
parse_strftime_owned(date_format)
.inspect_err(|_| eprintln!("Invalid strftime format: {date_format}"))?
} else {
parse_owned::<2>(date_format)
.inspect_err(|_| eprintln!("Invalid date format: {date_format}"))?
};
CUSTOM_TIME_FORMAT
.set(Some(date_format))
.expect("Set only once");
}
let file = File::open(&args.file)?;
let file = BufReader::new(file);
let log_file = LogFile::open(&args.file, file).map_err(|err| LogError::Read {