Skip to content

normalize RFC 3339 'Z' UTC suffix in syslog parser - #28

Draft
HrachShah wants to merge 12 commits into
mainfrom
fix/syslog-z-suffix-timestamp
Draft

normalize RFC 3339 'Z' UTC suffix in syslog parser#28
HrachShah wants to merge 12 commits into
mainfrom
fix/syslog-z-suffix-timestamp

Conversation

@HrachShah

@HrachShah HrachShah commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

SyslogParser._parse_timestamp accepted the ISO 8601 / RFC 3339 single-letter 'Z' UTC suffix via the PATTERNS regex but then iterated strptime formats that only know %z (the +00:00 / -05:30 style). A timestamp like 2025-03-20T10:15:32Z matched the regex, fell through every format with a ValueError, and _parse_timestamp returned None. The line was kept (level, message, host, process all survive) but its timestamp was silently lost, which makes time-windowed analysis undercount exactly the entries that usually carry the most precise wall-clock information.

The fix normalises the trailing Z to +00:00 before strptime, mirroring the same fix already present in JSONLogParser._parse_timestamp_string. The legacy RFC 3164 branch (which uses no timezone suffix) is left untouched: ts_str in that branch is the bare string, ts_normalized only matters for the ISO 8601 branch.

Two new tests cover the Z suffix with and without fractional seconds, asserting the parsed datetime carries a real timezone offset and does not get dropped to None.

Summary by Sourcery

Normalize and robustly handle timestamps across syslog, JSON log parsing, analyzer, and CLI time filtering so RFC 3339 'Z' suffixes, mixed naive/aware datetimes, and extreme epoch values are parsed or skipped safely without crashing or silently dropping entries.

New Features:

  • Support parsing RFC 3339 'Z' UTC suffix in syslog timestamps, including fractional seconds.
  • Support analysis time filtering when logs and CLI boundaries mix naive and timezone-aware timestamps.

Bug Fixes:

  • Prevent loss of syslog timestamps that use RFC 3339 'Z' suffix by normalizing them to a strptime-compatible UTC offset.
  • Handle mixed naive and timezone-aware timestamps in analyzer time distributions and error grouping without raising TypeError.
  • Ensure CLI time filters work when log timestamps and user-supplied boundaries differ in timezone awareness.
  • Prevent JSON timestamp extraction from crashing on out-of-range or overflow epoch values, returning entries without timestamps instead.
  • Avoid treating boolean JSON fields as numeric epochs when extracting timestamps.

Enhancements:

  • Add tests covering RFC 3339 'Z' suffix handling, mixed naive/aware timestamp analysis, and JSON epoch edge cases to guard against regressions.

Zo Bot added 4 commits June 13, 2026 13:32
…ps in _parse_file

The analyze command parsed --start-time and --end-time with strptime() into
naive datetimes, while the JSON and Apache parsers returned tz-aware
datetimes (e.g. Z-suffixed ISO 8601 strings or +0000 access-log
timestamps). The boundary check in _parse_file then did
'timestamp < start_time', which Python refuses across tz-aware/naive
boundaries with 'TypeError: can't compare offset-naive and offset-aware
datetimes'. The test_analyze_time_filter case crashed on the first
such comparison and the test_analyze_apache_file case had no fixture
on disk to even open.

The fix normalizes both sides to tz-aware UTC before the comparison:
- Promote the top-level import to 'from datetime import datetime,
  timezone' (was a local import inside analyze()) so _align_to_utc can
  reach timezone.utc without relying on a local-import side effect.
- Tag the parsed start_dt/end_dt with timezone.utc when strptime
  produced a naive datetime.
- Add a small _align_to_utc(dt) helper that returns dt unchanged when
  it is already tz-aware, attaches timezone.utc when it is naive, and
  returns None when it is None. Apply it to the parsed timestamp in
  _parse_file so that a log line that itself is naive (e.g. RFC 3164
  syslog) still compares cleanly against a tz-aware boundary, and a
  log line that is tz-aware compares cleanly against a naive boundary
  after the boundary was promoted above.
- Add two new tests after test_analyze_time_filter that exercise the
  exact failure modes: a JSON log with Z-suffixed timestamps filtered
  against a naive --start-time, and a log that mixes naive and
  tz-aware timestamps filtered against the same naive boundary.

Verification: python -m pytest tests/ goes from 2 failed + 45 passed
to 49 passed, and the new tests cover both directions of the
tz-mismatch.
A single log file can contain both naive timestamps from a syslog emitter
(2025-03-20 10:15:32) and tz-aware ISO timestamps from a JSON logger
(2025-03-20T10:15:33Z). The previous code fed the raw entry.timestamp values
to sorted(), min()/max(), and the < / > comparisons in _add_to_error_group;
Python rejects mixing offset-naive and offset-aware datetimes with
'TypeError: can't compare offset-naive and offset-aware datetimes', which
crashed TimeDistribution construction and error-group first/last_seen
computation on any such file.

The fix introduces a small _normalize_timestamp helper that promotes naive
datetimes to UTC (and leaves tz-aware datetimes as-is) on a *local copy* used
by sorted() and the < / > comparisons. The original entry.timestamp on each
ParsedEntry is left untouched, so callers still see the exact value the
parser produced. The new tests in test_analyzer.py cover the helper directly
(sorted/min/max), the time_distribution sort path with a mixed list, and the
error-group first/last_seen comparison path with the same mix.
JSONLogParser._extract_timestamp passed numeric timestamps straight into
datetime.fromtimestamp(), so any value that overflowed the year range
(year > 9999, year < 1, or values beyond platform time_t) raised a
ValueError/OverflowError and the whole log line was dropped — including
its level and message metadata, which are usually the parts an operator
actually needs when triaging an incident.

Wrap the fromtimestamp call so out-of-range epochs yield a timestamp-less
entry instead of crashing. Also exclude bool from the numeric branch
since True/False are isinstance(int) in Python and would otherwise be
interpreted as epoch 1 / epoch 0. Three new tests cover the far-future,
far-past, and float-overflow cases that previously raised uncaught
exceptions.
SyslogParser._parse_timestamp accepted the ISO 8601 'Z' suffix via the
PATTERNS regex but then iterated strptime formats that only know %z
('+00:00' / '-05:30' style). A timestamp like '2025-03-20T10:15:32Z'
matched the regex, fell through every format with a ValueError, and
_parse_timestamp returned None. The line was kept (level, message,
host, process all survive) but its timestamp was silently lost, which
makes time-windowed analysis undercount lines from the very entries
that usually carry the most precise wall-clock information.

Normalise the trailing 'Z' to '+00:00' before strptime, mirroring the
same fix already present in JSONLogParser._parse_timestamp_string. The
ts_str variable in the legacy 'RFC 3164' branch (which uses no timezone
suffix) is left untouched.

Two new tests cover the Z suffix with and without fractional seconds,
asserting that the parsed datetime carries a real timezone offset
rather than coming back as a naive None-returned entry.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 40b76688-a267-4b1a-b516-1057362e849f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/syslog-z-suffix-timestamp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR fixes timestamp handling across parsers and analysis: syslog RFC 3339 'Z' UTC suffix is normalized so timestamps are preserved, JSON epoch parsing is hardened against out-of-range values, and analyzer/CLI date-time logic is updated to safely handle mixed naive and timezone-aware datetimes when filtering and computing time distributions/error group bounds, with new tests covering these scenarios.

Sequence diagram for updated timestamp parsing and normalization

sequenceDiagram
    actor User
    participant CLI as CLI_analyze
    participant Parser as SyslogParser
    participant Analyzer as LogAnalyzer_analyze

    User->>CLI: analyze(start_time,end_time,log_format,file)
    CLI->>CLI: datetime.strptime(start_time)
    CLI->>CLI: datetime.strptime(end_time)
    CLI->>CLI: _align_to_utc(start_dt)
    CLI->>CLI: _align_to_utc(end_dt)

    CLI->>Parser: parse_timestamp(line)
    Parser->>Parser: _parse_timestamp(ts_str)
    Parser->>Parser: ts_normalized = ts_str[:-1] + "+00:00" [ts_str endswith Z]
    Parser->>Parser: datetime.strptime(ts_normalized,fmt)
    Parser-->>CLI: timestamp

    CLI->>CLI: _align_to_utc(timestamp)
    CLI-->>Analyzer: ParsedEntry(timestamp,level,message,...)

    Analyzer->>Analyzer: _normalize_timestamp(entry.timestamp)
    Analyzer->>Analyzer: sorted(normalized_timestamps)
    Analyzer->>Analyzer: _normalize_timestamp(group.first_seen)
    Analyzer->>Analyzer: _normalize_timestamp(group.last_seen)
    Analyzer-->>User: TimeDistribution and error_groups
Loading

File-Level Changes

Change Details Files
Normalize RFC 3339 'Z' UTC suffix in syslog timestamp parsing so ISO-style syslog lines retain a timezone-aware datetime instead of losing their timestamp.
  • Extend supported syslog datetime formats to include ISO-8601 without timezone and with fractional seconds plus timezone.
  • Introduce normalization of trailing 'Z' to '+00:00' before attempting strptime parsing, while keeping RFC 3164-style timestamps unchanged.
  • Switch non-legacy formats to parse using the normalized timestamp string so %z can match the synthetic '+00:00' offset.
src/log_analyzer_cli/parsers/syslog.py
Harden JSON timestamp extraction against out-of-range numeric epochs and improve Z-suffixed string parsing behavior.
  • Avoid treating booleans as numeric timestamps when inspecting JSON fields.
  • Convert large millisecond epochs to seconds before calling datetime.fromtimestamp.
  • Wrap fromtimestamp in a try/except that catches ValueError, OverflowError, and OSError and returns None instead of propagating, so entries are kept but timestamp-less.
  • Ensure string timestamps are parsed via _parse_timestamp_string and only returned when parsing succeeds.
src/log_analyzer_cli/parsers/json_log.py
Make analyzer robust to mixed naive and timezone-aware datetime values when computing time distributions and error group first/last_seen.
  • Introduce _normalize_timestamp helper that attaches UTC tzinfo to naive datetimes and leaves aware ones unchanged.
  • Use normalized timestamps as the sort key for time_distribution entries to avoid TypeError on mixed naive/aware lists.
  • Apply normalization when updating error group first_seen/last_seen comparisons while still storing the original entry.timestamp values in the group.
src/log_analyzer_cli/analyzer.py
tests/test_analyzer.py
Align CLI time filtering to operate in UTC with consistent timezone-aware boundaries and parsed entry timestamps.
  • Parse --start-time and --end-time as naive datetimes and immediately attach UTC tzinfo if missing.
  • Add _align_to_utc helper to promote parsed timestamps to UTC when they are naive while preserving existing tzinfo on aware datetimes.
  • Normalize parsed line timestamps using _align_to_utc before applying start/end-time comparisons in _parse_file.
  • Add CLI integration tests for time filtering against logs with aware timestamps and mixed naive/aware entries.
src/log_analyzer_cli/cli.py
tests/test_cli.py
Extend parser tests to cover new timestamp edge cases and regression scenarios introduced by Z-suffix handling and epoch overflow protection.
  • Add syslog parser tests for RFC 3339 Z-suffixed timestamps with and without fractional seconds, asserting preservation of timezone information.
  • Add JSON parser tests verifying that far-future, far-past, and overly large float epochs no longer crash parsing but result in entries without timestamps.
  • Add JSON parser test for entries with missing timestamp fields to assert they are still parsed successfully.
tests/test_parsers.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant