diff --git a/src/log_analyzer_cli/parsers/syslog.py b/src/log_analyzer_cli/parsers/syslog.py index 99891f3..2641fc2 100644 --- a/src/log_analyzer_cli/parsers/syslog.py +++ b/src/log_analyzer_cli/parsers/syslog.py @@ -118,17 +118,22 @@ def _parse_timestamp(self, ts_str: str) -> Optional[datetime]: formats = [ "%b %d %H:%M:%S", "%Y-%m-%d %H:%M:%S", + "%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S%z", ] + ts_str_normalized = ts_str + if ts_str_normalized.endswith("Z"): + ts_str_normalized = ts_str_normalized[:-1] + "+00:00" + for fmt in formats: try: if fmt == "%b %d %H:%M:%S": dt = datetime.strptime(ts_str, fmt) dt = dt.replace(year=year) return dt - return datetime.strptime(ts_str, fmt) + return datetime.strptime(ts_str_normalized, fmt) except ValueError: continue return None diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 0248a28..f03ef88 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -50,6 +50,34 @@ def test_parse_syslog_with_level(self): assert entry.level == "ERROR" assert "Database connection failed" in entry.message + def test_parse_iso8601_with_microseconds_and_z(self): + """ISO-8601 timestamps with fractional seconds and a Z suffix should parse.""" + parser = SyslogParser() + line = "2025-03-20T10:15:32.123Z host process[1234]: something happened" + entry = parser.parse(line) + + assert entry is not None + assert entry.timestamp is not None + assert entry.timestamp.year == 2025 + assert entry.timestamp.month == 3 + assert entry.timestamp.day == 20 + assert entry.timestamp.hour == 10 + assert entry.timestamp.minute == 15 + assert entry.timestamp.second == 32 + assert entry.timestamp.microsecond == 123000 + assert entry.timestamp.tzinfo is not None + + def test_parse_iso8601_with_microseconds_and_offset(self): + """ISO-8601 timestamps with fractional seconds and a numeric offset should parse.""" + parser = SyslogParser() + line = "2025-03-20T10:15:32.123+02:00 host process[1234]: something happened" + entry = parser.parse(line) + + assert entry is not None + assert entry.timestamp is not None + assert entry.timestamp.microsecond == 123000 + assert entry.timestamp.utcoffset().total_seconds() == 2 * 3600 + class TestJSONLogParser: """Tests for JSONLogParser."""