Skip to content

ApacheParser: accept a real (non-empty) user in the COMBINED pattern - #30

Open
HrachShah wants to merge 3 commits into
mainfrom
fix/apache-combined-user-real
Open

ApacheParser: accept a real (non-empty) user in the COMBINED pattern#30
HrachShah wants to merge 3 commits into
mainfrom
fix/apache-combined-user-real

Conversation

@HrachShah

@HrachShah HrachShah commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Switch the user slot in ApacheParser.COMBINED_PATTERN from \s+ to \S+ so a real production log line with a non-empty user field (e.g. 'frank') is matched by the combined pattern instead of falling through to the common pattern and dropping the referer and user_agent. A new regression test pins the behaviour.

Summary by Sourcery

Improve log parsing robustness for Apache combined logs and timezone-aware timestamps, while aligning CLI and library filtering behavior.

New Features:

  • Support non-empty user fields in Apache combined log entries without losing referer and user-agent metadata.

Enhancements:

  • Extend timestamp detection and parsing to better handle ISO 8601 formats with microseconds and timezones as well as Apache common log timestamps with offsets.
  • Normalize timezone-aware timestamps to naive when comparing against naive start/end time bounds in both CLI file parsing and library line filtering.

Tests:

  • Add regression test ensuring Apache combined log lines with a real user field retain user, referer, and user-agent metadata.
  • Add comprehensive tests for timestamp parsing formats, log level detection, error pattern normalization, and timezone-aware filtering against naive start/end bounds.

Summary by CodeRabbit

  • Bug Fixes

    • Improved timestamp handling so log entries with time zones can be filtered correctly by start/end time.
    • Fixed Apache combined log parsing so usernames are captured more reliably.
    • Prevented time zone-aware entries from causing filtering issues when compared with date-only bounds.
  • Tests

    • Added coverage for timestamp parsing, log-level detection, error-pattern normalization, Apache log parsing, and time zone-aware filtering.

Zo Bot added 3 commits July 4, 2026 17:31
…rt-time/--end-time

The previous compare-tz-aware-to-naive path raised TypeError on
`<=>/<` between a tz-aware log timestamp and a naive CLI bound
("Cannot compare tz-naive and tz-aware datetime-like objects"),
so any line with an offset (e.g. Apache/JSON logs with +00:00)
silently disappeared from filtered output. cli._parse_file already
handles this with `timestamp.replace(tzinfo=None)` when a tz is
attached; mirror that here in the underlying filter_lines helper
so the helper, the CLI, and any direct library caller all agree on
how a tz-aware log entry is compared against a tz-naive bound.

Two new TestFilterLinesTzAware cases pin the behaviour: a
15:00:00+00:00 entry is correctly dropped against a naive 16:00:00
start, and a 17:00:00+00:00 entry is kept.
The user slot in the Apache combined log format is just another token, not a
run of dashes — a real production line looks like
`192.168.1.10 - frank [..] "GET / HTTP/1.1" 200 ..`. The old
`(?P<user>\s+)` only matched the literal `- -` placeholder, so a real-user
line was reported as unparseable by COMBINED_PATTERN, parse() silently fell
through to COMMON_PATTERN, and the referer and user_agent captured by
COMBINED were dropped on the floor.

Switch the user slot to `\S+` so COMBINED matches real-world logs and
parse() carries referer and user_agent into metadata. A regression test
pins the new behaviour against a `frank` user.

Full test suite: 76 passed (was 75, +1 new).
@sourcery-ai

sourcery-ai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates Apache log parsing and timestamp handling to correctly support non-empty user fields, timezone-aware ISO 8601 and Apache-style timestamps (including offsets), and aligns CLI and utility filtering behavior via new tests.

Sequence diagram for timestamp parsing and timezone-normalized filtering

sequenceDiagram
    actor User
    participant CLI as cli._parse_file
    participant Utils as utils.parse_timestamp

    User->>CLI: _parse_file(start_time, end_time)
    CLI->>Utils: parse_timestamp(line)
    Utils-->>CLI: timestamp

    opt timestamp and timestamp.tzinfo
        CLI->>CLI: timestamp.replace(tzinfo=None)
    end

    alt compare_ts < start_time or compare_ts > end_time
        CLI-->>User: skip line
    else
        CLI-->>User: yield parsed line
    end
Loading

File-Level Changes

Change Details Files
Improve timestamp detection and parsing to preserve timezones and microseconds and support Apache common log offsets.
  • Extend the Apache-style timestamp regex to optionally capture numeric timezone offsets with or without colon.
  • Reorder and expand datetime format strings to prioritize timezone-aware and microsecond-inclusive ISO 8601 variants before naive formats.
  • Add explicit formats for ISO 8601 timestamps with spaces, with/without timezone and microseconds, and for Apache common log with optional timezone.
  • Document the ordering rationale and each format in the parsing helper docstring.
src/log_analyzer_cli/utils.py
Ensure time-bounded filtering works with both naive and timezone-aware timestamps without raising comparison errors.
  • Normalize tz-aware timestamps to naive before comparing against naive start_time/end_time bounds in filter_lines.
  • Mirror the same normalization strategy in the CLI file parsing path for start_time/end_time filtering.
  • Guard comparisons so they only run when a parsed timestamp is present.
src/log_analyzer_cli/utils.py
src/log_analyzer_cli/cli.py
Fix Apache combined log parsing to handle real (non-empty) user fields and retain referer and user-agent metadata.
  • Change the COMBINED_PATTERN user group to match non-whitespace tokens instead of arbitrary whitespace.
  • Add a regression test that parses a realistic combined log line with a non-empty user and asserts that user, referer, and user_agent metadata are captured.
src/log_analyzer_cli/parsers/apache.py
tests/test_parsers.py
Add comprehensive tests for utilities: timestamp parsing, log level detection, error pattern normalization, and timezone-aware filtering.
  • Add tests covering multiple ISO 8601 variants (with/without microseconds, Z or numeric offsets, space vs T separator) to ensure correct datetime and tzinfo handling.
  • Add tests for Apache common log timestamps with and without timezone offsets and for syslog-style timestamps without year.
  • Verify that microseconds and timezone offsets are preserved and that naive vs tz-aware behaviors are correct.
  • Add unit tests for detect_log_level, normalize_error_pattern replacements (IPs, ports, UUIDs, paths, numbers, hex), and filter_lines handling of tz-aware entries against naive bounds.
tests/test_utils.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 Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Timestamp parsing gains additional format support (ISO-8601 variants, Apache/common-log with optional timezone offsets). Filtering logic in both cli.py and utils.py now normalizes timezone-aware timestamps to naive before comparing against start/end bounds. The Apache parser's user-field regex was corrected to capture non-whitespace tokens. Tests were added for all changes.

Changes

Timestamp Parsing and Timezone-Aware Filtering

Layer / File(s) Summary
Timestamp format parsing
src/log_analyzer_cli/utils.py
parse_timestamp and _try_parse_datetime add support for additional ISO-8601 and Apache/common-log timestamp formats, including optional timezone offsets and trailing Z.
Timezone-normalized bounds filtering
src/log_analyzer_cli/cli.py, src/log_analyzer_cli/utils.py
filter_lines and _parse_file normalize timezone-aware timestamps to naive before comparing against start_time/end_time, replacing direct comparisons with guarded, normalized checks.
Apache combined log user field fix
src/log_analyzer_cli/parsers/apache.py, tests/test_parsers.py
COMBINED_PATTERN's user capture group changed from whitespace to non-whitespace matching, with a new test verifying correct user, referer, and user_agent metadata extraction.
Utility test coverage
tests/test_utils.py
New test module covers parse_timestamp format variants, detect_log_level, normalize_error_pattern, and tz-aware filter_lines behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LogFile
  participant ParseTimestamp as parse_timestamp/_try_parse_datetime
  participant FilterLines as filter_lines/_parse_file
  participant Output

  LogFile->>ParseTimestamp: raw timestamp string
  ParseTimestamp->>ParseTimestamp: match against expanded format list
  ParseTimestamp-->>FilterLines: parsed datetime (naive or tz-aware)
  FilterLines->>FilterLines: drop tzinfo if timestamp is tz-aware
  FilterLines->>FilterLines: compare normalized timestamp to start_time/end_time
  FilterLines-->>Output: included or excluded log line
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main parser fix and matches the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/apache-combined-user-real

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 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:

  • The timezone-normalization logic for timestamp comparisons is duplicated between filter_lines and _parse_file; consider extracting this into a shared helper to avoid divergence and make future changes easier.
  • In ApacheParser.COMBINED_PATTERN, switching user to \S+ means it will no longer match an actually empty user field; if truly empty usernames are possible in your logs, you may want a pattern that accepts both - and empty values while still capturing real usernames.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The timezone-normalization logic for timestamp comparisons is duplicated between `filter_lines` and `_parse_file`; consider extracting this into a shared helper to avoid divergence and make future changes easier.
- In `ApacheParser.COMBINED_PATTERN`, switching `user` to `\S+` means it will no longer match an actually empty user field; if truly empty usernames are possible in your logs, you may want a pattern that accepts both `-` and empty values while still capturing real usernames.

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

🧹 Nitpick comments (1)
src/log_analyzer_cli/utils.py (1)

208-227: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared tz-normalization logic to avoid duplication.

The tz-aware-to-naive normalization block is duplicated verbatim in filter_lines here and in cli._parse_file (lines 217-225). Extracting a small helper (e.g., _normalize_for_compare(timestamp)) would keep the two paths in sync and reduce the risk of them diverging.

♻️ Suggested helper extraction
+def _to_naive(ts: Optional[datetime]) -> Optional[datetime]:
+    """Drop tzinfo for comparison against naive bounds."""
+    return ts.replace(tzinfo=None) if (ts and ts.tzinfo) else ts
+
 # In filter_lines:
-        if start_time and timestamp:
-            compare_ts = (
-                timestamp.replace(tzinfo=None)
-                if timestamp.tzinfo
-                else timestamp
-            )
-            if compare_ts < start_time:
-                continue
-        if end_time and timestamp:
-            compare_ts = (
-                timestamp.replace(tzinfo=None)
-                if timestamp.tzinfo
-                else timestamp
-            )
-            if compare_ts > end_time:
-                continue
+        if start_time and timestamp:
+            if _to_naive(timestamp) < start_time:
+                continue
+        if end_time and timestamp:
+            if _to_naive(timestamp) > end_time:
+                continue
🤖 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/utils.py` around lines 208 - 227, The timestamp
normalization logic is duplicated in filter_lines and cli._parse_file, so
extract it into a shared helper such as _normalize_for_compare(timestamp) in
utils.py and use that helper in both comparison paths. Keep the helper behavior
identical for tz-aware and tz-naive timestamps, and update the existing
start_time/end_time checks to call it instead of repeating the same
replace(tzinfo=None) block.
🤖 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 `@tests/test_utils.py`:
- Line 7: Remove the unused imports from the test module to satisfy flake8 F401;
in the test file, delete the unused `pytest` import and the unused
`read_log_file` import so only symbols actually referenced by the tests remain.

---

Nitpick comments:
In `@src/log_analyzer_cli/utils.py`:
- Around line 208-227: The timestamp normalization logic is duplicated in
filter_lines and cli._parse_file, so extract it into a shared helper such as
_normalize_for_compare(timestamp) in utils.py and use that helper in both
comparison paths. Keep the helper behavior identical for tz-aware and tz-naive
timestamps, and update the existing start_time/end_time checks to call it
instead of repeating the same replace(tzinfo=None) block.
🪄 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: 45da5255-896b-4dd6-b793-ecbb4bb9a8ee

📥 Commits

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

📒 Files selected for processing (5)
  • src/log_analyzer_cli/cli.py
  • src/log_analyzer_cli/parsers/apache.py
  • src/log_analyzer_cli/utils.py
  • tests/test_parsers.py
  • tests/test_utils.py

Comment thread tests/test_utils.py

from datetime import datetime, timezone, timedelta

import pytest

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove unused imports flagged by flake8.

CI fails with F401: pytest and read_log_file are imported but never used in this test module.

🛡️ Proposed fix
 from datetime import datetime, timezone, timedelta
 
-import pytest
-
 from log_analyzer_cli.utils import (
     detect_log_level,
     filter_lines,
     normalize_error_pattern,
     parse_timestamp,
-    read_log_file,
 )

Also applies to: 9-9

🧰 Tools
🪛 GitHub Actions: CI / 2_test (3.11).txt

[error] 7-7: flake8 (F401): 'pytest' imported but unused.

🪛 GitHub Actions: CI / 3_test (3.10).txt

[error] 7-7: flake8: F401 'pytest' imported but unused

🪛 GitHub Actions: CI / test (3.10)

[error] 7-7: flake8 (F401) 'pytest' imported but unused

🪛 GitHub Actions: CI / test (3.11)

[error] 7-7: flake8: F401 'pytest' imported but unused

🤖 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 `@tests/test_utils.py` at line 7, Remove the unused imports from the test
module to satisfy flake8 F401; in the test file, delete the unused `pytest`
import and the unused `read_log_file` import so only symbols actually referenced
by the tests remain.

Sources: Linters/SAST tools, Pipeline failures

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