Skip to content

filter log entries from parsed levels and timezone-normalise CLI filters#17

Open
HrachShah wants to merge 1 commit into
mainfrom
fix/cli-filter-on-parsed-level-and-tz-aware-time
Open

filter log entries from parsed levels and timezone-normalise CLI filters#17
HrachShah wants to merge 1 commit into
mainfrom
fix/cli-filter-on-parsed-level-and-tz-aware-time

Conversation

@HrachShah

@HrachShah HrachShah commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

The CLI's _parse_file filtered by detect_log_level(line) (raw text scan) and parse_timestamp(line) (extracted from the raw line) before calling parser.parse(line). Both disagree with the values the parser actually assigns, so the filters and the analyzer were working from different inputs.

Level filter mismatch:

  • An Apache 200 with "ERROR" in the User-Agent was INFO per the parser (200 status code) but ERROR per the raw-text scan, so --levels INFO wrongly dropped it.
  • An Apache 500 with no level keyword in the line was ERROR per the parser but UNKNOWN per the raw-text scan, so --levels ERROR wrongly dropped it.

Time filter mismatch:

The CLI parsed --start-time "2025-03-20 10:00:00" as a naive datetime while the JSON log carries timezone-aware timestamps (e.g. 2025-03-20T10:15:32.123Z). The comparison timestamp < start_time raised TypeError: can't compare offset-naive and offset-aware datetimes inside the outer try/except (OSError, ValueError, RuntimeError) in analyze(), where it was caught and reported as a generic "Error: ..." exit code 1. The CLI's documented exit code 0 for a successful filtered run was unreachable for the common case of a JSON log with TZ-aware timestamps.

Fix:

In src/log_analyzer_cli/cli.py:

  • _parse_file now calls parser.parse(line) first and filters on the parsed entry's level and timestamp. The pattern filter still runs against the raw line (it has no parsed equivalent and the user wrote a regex against the line text on purpose).
  • analyze now tags the parsed --start-time / --end-time with tzinfo=timezone.utc so the comparison against a TZ-aware log timestamp works.

Tests added (in tests/test_cli.py):

  • test_analyze_time_filter_naive_start_with_aware_log — writes a JSON log with 2025-03-20T10:15:32.123Z and 2025-03-20T10:17:00.000Z entries, runs the CLI with --start-time 2025-03-20 10:16:00, asserts exit code 0, asserts the 10:15 entry is excluded and the 10:17 entry is included.
  • test_analyze_level_filter_uses_parser_level_not_raw_text — writes an Apache log with a 200 whose User-Agent contains "ERROR" and a 500 with no level keyword, asserts that --levels ERROR returns exactly the 500 and --levels INFO returns exactly the 200.

Both tests fail on main and pass after the fix. pytest tests/ is 49 passed, 0 failed (up from 47 passed, 2 failed on main). The remaining on-disk failure on main (test_analyze_apache_file) is a pre-existing fixture issue — the test references examples/apache-sample.log which is gitignored and not committed; the fix here is unrelated.

Summary by Sourcery

Align CLI log filtering with parser output and normalise time filters to work with timezone-aware log timestamps.

Bug Fixes:

  • Ensure --levels filters logs based on the parser-assigned level instead of raw text keywords to avoid mismatched inclusions or exclusions.
  • Fix analyze time filtering by making parsed start and end times timezone-aware so comparisons with TZ-aware log timestamps no longer fail.

Tests:

  • Add CLI test covering naive --start-time filtering against timezone-aware JSON log entries and verifying correct inclusion/exclusion and exit code.
  • Add CLI test ensuring level filters respect parser-derived levels rather than raw-line keyword detection for Apache logs.

Summary by CodeRabbit

  • Bug Fixes

    • Improved log filtering accuracy by applying timestamp and level filters to parsed values rather than raw text.
    • Enhanced timezone handling when filtering logs by start/end time.
  • Tests

    • Added regression tests verifying timestamp filter behavior with timezone-aware logs.
    • Added tests confirming level-based filtering uses parsed values.

The CLI's `_parse_file` filtered by `detect_log_level(line)` (raw text scan)
and `parse_timestamp(line)` (extracted from the raw line) before calling
`parser.parse(line)`. Both disagree with the values the parser actually
assigns, so the filters and the analyzer were working from different
inputs.

Level filter: an Apache 200 with "ERROR" in the User-Agent was INFO per
the parser (200 status code) but ERROR per the raw-text scan, so
`--levels INFO` wrongly dropped it; an Apache 500 with no level keyword
in the line was ERROR per the parser but UNKNOWN per the raw-text scan,
so `--levels ERROR` wrongly dropped it. Fix: parse the line first, then
filter by `parsed.level`.

Time filter: the JSON parser returns timezone-aware datetimes (the Z
suffix is converted to +00:00 in `_parse_timestamp_string`); the CLI
parsed `--start-time` / `--end-time` as naive datetimes and then
compared them, raising "can't compare offset-naive and offset-aware
datetimes" inside the outer try/except in `analyze()`. Fix: attach
`tzinfo=timezone.utc` to the parsed filter values so a naive
`YYYY-MM-DD HH:MM:SS` argument is treated as UTC and the comparison
works against both naive and aware log timestamps.

Includes two regression tests:
- `test_analyze_time_filter_naive_start_with_aware_log` passes a naive
  `--start-time` against a JSON log with `Z` timestamps and asserts the
  late entry is kept and the early one is dropped
- `test_analyze_level_filter_uses_parser_level_not_raw_text` writes an
  Apache log with a 200 + "ERROR" UA line and a 500 line, and checks
  that `--levels ERROR` keeps only the 500 and `--levels INFO` keeps
  only the 200

All 49 tests in the suite pass.
@sourcery-ai

sourcery-ai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adjusts the CLI log analysis pipeline so level and time filters operate on parser-derived values instead of raw-line heuristics, and normalizes CLI time filters to UTC, with tests covering timezone-aware logs and parser-based level filtering.

Flow diagram for updated CLI log filtering pipeline

flowchart TD
    A[analyze] --> B[parse start_time and end_time]
    B --> C[replace tzinfo with timezone.utc]
    C --> D[_parse_file]

    subgraph _parse_file
        D --> E[read_log_file]
        E --> F[strip empty lines]
        F --> G{search_pattern matches?}
        G -- no --> E
        G -- yes --> H[parser.parse]
        H --> I{parsed is not None?}
        I -- no --> E
        I -- yes --> J{include_levels set?}
        J -- yes --> K{parsed.level in include_levels?}
        K -- no --> E
        K -- yes --> L
        J -- no --> L
        L{within start_time/end_time?} -->|no| E
        L -->|yes| M[append parsed to entries]
        M --> E
    end

    D --> N[return entries to analyze]
Loading

File-Level Changes

Change Details Files
Filter log entries in _parse_file using parser-parsed entries for level and timestamp instead of raw-text detection.
  • Update _parse_file docstring to explain that level and time filtering now operate on parsed entries rather than raw line text.
  • Remove pre-parse level filtering using detect_log_level and instead filter by include_levels against parsed.level.
  • Remove pre-parse timestamp filtering using parse_timestamp and instead filter by start_time/end_time against parsed.timestamp.
  • Ensure pattern/regex filtering still runs on the raw line before parsing, and skip empty lines early.
  • Append parsed entries to the result list only after they pass pattern, level, and time checks.
src/log_analyzer_cli/cli.py
Normalize CLI-provided start and end times to UTC when comparing against log timestamps.
  • After successfully parsing start_time, call replace(tzinfo=timezone.utc) to make start_dt timezone-aware.
  • After successfully parsing end_time, call replace(tzinfo=timezone.utc) to make end_dt timezone-aware.
  • Rely on these timezone-aware datetimes to avoid TypeError when logs use offset-aware timestamps.
src/log_analyzer_cli/cli.py
Add CLI tests to validate timezone-handling and parser-based level filtering behavior.
  • Add test that builds a JSON log with UTC timestamps and verifies a naive --start-time filter excludes earlier and includes later entries, with exit code 0 and correct parsed_entries count.
  • Add test that builds an Apache log where raw text level keywords disagree with parser-derived levels, asserting that --levels filters by parsed level rather than keyword scan for INFO and ERROR cases.
  • Keep existing help test intact while adjusting surrounding whitespace.
tests/test_cli.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

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

_parse_file in cli.py is reworked to filter log entries using parsed fields (parsed.level, parsed.timestamp) instead of raw-line heuristics. User-supplied --start-time/--end-time values are now coerced to timezone.utc before comparison. Two regression tests are added to validate TZ-aware timestamp filtering and parser-driven level filtering.

Changes

Post-parse filtering pipeline and UTC coercion

Layer / File(s) Summary
UTC coercion and post-parse filtering pipeline
src/log_analyzer_cli/cli.py
Imports timezone; marks start_dt/end_dt with tzinfo=timezone.utc; expands _parse_file docstring; replaces detect_log_level/parse_timestamp raw-line pre-filter with a pipeline that calls parser.parse(line) first and then filters on parsed.level and parsed.timestamp.
Regression tests for post-parse filtering
tests/test_cli.py
Adds a test asserting naive --start-time correctly excludes pre-cutoff entries from a TZ-aware JSON log, a test asserting -l level filtering uses the parser-assigned level on an Apache-style log, and updates test_help ordering.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A rabbit once filtered by guessing at text,
But the parser's own fields proved far more correct.
parsed.level, parsed.timestamp—now these are the guide,
With UTC coercion to keep timezones aligned.
No more raw-line heuristics, just facts from the parse—
The log entries flow cleanly, and nothing is sparse! 🕐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the two main fixes: filtering on parsed levels (instead of raw text heuristics) and timezone-normalizing CLI filters to UTC for proper comparison with timezone-aware log timestamps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cli-filter-on-parsed-level-and-tz-aware-time

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 and usage tips.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In _parse_file, the imports of detect_log_level and parse_timestamp are now unused after switching to filtering on parsed entries; consider removing these imports to avoid confusion and keep the function focused.
  • Tagging start_dt and end_dt with timezone.utc unconditionally fixes the naive-vs-aware comparison error but may be semantically wrong for non-UTC logs; consider deriving the timezone from the parsed log timestamps or making the CLI accept an explicit timezone for these filters.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_parse_file`, the imports of `detect_log_level` and `parse_timestamp` are now unused after switching to filtering on `parsed` entries; consider removing these imports to avoid confusion and keep the function focused.
- Tagging `start_dt` and `end_dt` with `timezone.utc` unconditionally fixes the naive-vs-aware comparison error but may be semantically wrong for non-UTC logs; consider deriving the timezone from the parsed log timestamps or making the CLI accept an explicit timezone for these filters.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/log_analyzer_cli/cli.py`:
- Around line 232-235: The datetime comparison logic that checks
parsed.timestamp against start_time and end_time will fail with a TypeError when
the timestamp is naive (lacks timezone info), because Python cannot compare
aware and naive datetime objects. Before performing the comparison checks in the
filtering block (where start_time and end_time are compared), normalize any
naive timestamps by detecting if parsed.timestamp lacks tzinfo and converting it
to a UTC-aware datetime object. This ensures all datetime comparisons are
between objects in the same timezone state, preventing the TypeError from
occurring.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7bff2899-06a6-415b-9f23-40cdd622ef71

📥 Commits

Reviewing files that changed from the base of the PR and between e93757f and 7254990.

📒 Files selected for processing (2)
  • src/log_analyzer_cli/cli.py
  • tests/test_cli.py

Comment on lines +232 to +235
if start_time and parsed.timestamp and parsed.timestamp < start_time:
continue
if end_time and parsed.timestamp and parsed.timestamp > end_time:
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Compare points introduced in CLI =="
rg -n -C2 --type=py 'parsed\.timestamp < start_time|parsed\.timestamp > end_time' src/log_analyzer_cli/cli.py

echo
echo "== Parser timestamp construction (look for naive datetime creation) =="
rg -n -C3 --type=py 'def parse\(|ParsedEntry\(|timestamp\s*=|strptime\(|fromisoformat\(|tzinfo|timezone|astimezone' src/log_analyzer_cli/parsers

echo
echo "== Any timestamp parsing helpers outside parsers =="
rg -n -C3 --type=py 'parse_timestamp|_parse_timestamp|strptime\(|fromisoformat\(' src/log_analyzer_cli

Repository: HrachShah/log-analyzer-cli

Length of output: 22486


🏁 Script executed:

# Check lines around 108 and 117 for UTC coercion
head -130 src/log_analyzer_cli/cli.py | tail -40

Repository: HrachShah/log-analyzer-cli

Length of output: 1535


🏁 Script executed:

# Check lines 225-245 to see full filter context and variable names
sed -n '225,245p' src/log_analyzer_cli/cli.py

Repository: HrachShah/log-analyzer-cli

Length of output: 525


Datetime comparison will fail when parsers return naive timestamps.

Lines 232–234 compare parsed.timestamp against UTC-aware datetimes (start_time and end_time passed from lines 108 and 117). Python raises TypeError when comparing aware and naive datetime objects. Multiple parsers emit naive datetimes:

  • syslog.py (lines 128–131): Returns naive datetime from strptime()
  • generic.py (lines 87–92): Returns naive datetime from strptime()
  • apache.py (line 119): Returns naive datetime when logs lack timezone info
  • json_log.py (line 81): Returns naive datetime for certain timestamp values

When a log entry has a naive timestamp, the comparison at lines 232–234 will crash with TypeError: '<' not supported between instances of 'datetime.datetime' and 'datetime.datetime' instead of being caught by the outer exception handler (lines 96–99, which does not list TypeError).

Normalize naive timestamps before comparison, or convert all timestamps to a consistent timezone state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log_analyzer_cli/cli.py` around lines 232 - 235, The datetime comparison
logic that checks parsed.timestamp against start_time and end_time will fail
with a TypeError when the timestamp is naive (lacks timezone info), because
Python cannot compare aware and naive datetime objects. Before performing the
comparison checks in the filtering block (where start_time and end_time are
compared), normalize any naive timestamps by detecting if parsed.timestamp lacks
tzinfo and converting it to a UTC-aware datetime object. This ensures all
datetime comparisons are between objects in the same timezone state, preventing
the TypeError from occurring.

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