Skip to content

parsers/json_log: detect seconds/ms/μs/ns in numeric timestamps, swallow out-of-range and non-finite values - #26

Open
HrachShah wants to merge 1 commit into
mainfrom
fix/json-numeric-timestamp-units
Open

parsers/json_log: detect seconds/ms/μs/ns in numeric timestamps, swallow out-of-range and non-finite values#26
HrachShah wants to merge 1 commit into
mainfrom
fix/json-numeric-timestamp-units

Conversation

@HrachShah

@HrachShah HrachShah commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

JSONLogParser._extract_timestamp only handled two numeric units: anything
above 1e12 was treated as milliseconds, anything at or below as raw
seconds. Microsecond and nanosecond producers (Go's time.Now().UnixMicro()
and UnixNano(), Python's time.time_ns(), Java's System.nanoTime())
landed outside the millisecond branch, got passed directly to
datetime.fromtimestamp, and crashed with OverflowError /
ValueError: year 52218064 is out of range — which then bubbled all the
way out of parse() and turned the entire log line into a parse failure.

It also accepted NaN and ±inf because inf > 1e12 is True and
inf / 1000 is still inf, which then crashed downstream code that
expected a real datetime.

Fix

  • New JSONLogParser._numeric_to_datetime helper that converts by
    magnitude: keep dividing by 1000 until datetime.fromtimestamp accepts
    the result, or until we've tried all four units (seconds, ms, μs, ns).
  • NaN and ±infinity short-circuit to None so the timestamp field is
    simply absent instead of crashing.
  • After four failed attempts the value is genuinely out of any sane
    representation and we return None (was: raised).

Tests

Five new cases in tests/test_parsers.py::TestJSONNumericTimestampUnits:

  • microsecond and nanosecond timestamps both resolve to the correct date
  • +inf, -inf, NaN, null, and string-typed timestamp fields all
    parse successfully and return entry.timestamp is None (was: raised)
  • the existing 47-test suite still passes (52 total now)
>>> from log_analyzer_cli.parsers.json_log import JSONLogParser
>>> p = JSONLogParser()
>>> for v in [1647780800, 1647780800000, 1647780800000000, 1647780800000000000]:
...     e = p.parse(f'{{"timestamp": {v}}}')
...     print(v, "->", e.timestamp)
1647780800 -> 2022-03-20 12:53:20
1647780800000 -> 2022-03-20 12:53:20
1647780800000000 -> 2022-03-20 12:53:20
1647780800000000000 -> 2022-03-20 12:53:20

Summary by Sourcery

Handle numeric JSON log timestamps across multiple units and make invalid numeric values fail gracefully instead of raising.

Bug Fixes:

  • Support JSON numeric timestamps expressed in seconds, milliseconds, microseconds, or nanoseconds without raising out-of-range errors.
  • Treat NaN, infinity, and other non-finite numeric timestamp values as missing timestamps instead of propagating exceptions.

Documentation:

  • Document the improved JSON numeric timestamp handling and non-finite value behavior in the changelog.

Tests:

  • Add JSONLogParser tests covering second, millisecond, microsecond, and nanosecond numeric timestamps and their resolved datetimes.
  • Add tests verifying that extremely large, NaN, and null numeric timestamp fields parse successfully and yield a None timestamp.

Summary by CodeRabbit

  • Bug Fixes

    • Improved JSON timestamp parsing to handle numeric values in seconds, milliseconds, microseconds, and nanoseconds.
    • Prevented failures for invalid or non-finite timestamps; these now return no timestamp instead of breaking parsing.
  • Tests

    • Added coverage for multiple numeric timestamp formats and invalid timestamp values.

@sourcery-ai

sourcery-ai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Reviewer's Guide

Extends JSONLogParser’s numeric timestamp handling to robustly interpret seconds/ms/μs/ns by magnitude, and makes invalid or non-finite numeric timestamps return None instead of raising, with accompanying tests and changelog entry.

Sequence diagram for numeric timestamp parsing in JSONLogParser

sequenceDiagram
    participant Caller
    participant JSONLogParser
    participant _extract_timestamp
    participant _numeric_to_datetime
    participant datetime_fromtimestamp

    Caller->>JSONLogParser: parse(json_line)
    JSONLogParser->>_extract_timestamp: _extract_timestamp(data)
    _extract_timestamp->>_numeric_to_datetime: _numeric_to_datetime(value)

    alt [value is NaN or infinite]
        _numeric_to_datetime-->>_extract_timestamp: return None
    else [value is finite]
        loop up to 4 units
            _numeric_to_datetime->>datetime_fromtimestamp: fromtimestamp(seconds)
            alt [accepts]
                datetime_fromtimestamp-->>_numeric_to_datetime: datetime
                _numeric_to_datetime-->>_extract_timestamp: return datetime (break)
            else [raises OverflowError/OSError/ValueError]
                _numeric_to_datetime->>_numeric_to_datetime: seconds /= 1000.0
            end
        end
        alt [no unit accepted]
            _numeric_to_datetime-->>_extract_timestamp: return None
        end
    end

    _extract_timestamp-->>JSONLogParser: timestamp or None
    JSONLogParser-->>Caller: entry with timestamp or None
Loading

File-Level Changes

Change Details Files
Make JSONLogParser interpret numeric timestamps across seconds, milliseconds, microseconds, and nanoseconds using a magnitude-based heuristic and avoid crashes on invalid numeric values.
  • Refactor _extract_timestamp to delegate numeric timestamp handling to a new helper method.
  • Introduce _numeric_to_datetime to repeatedly divide by 1000 and call datetime.fromtimestamp up to four times, covering seconds/ms/μs/ns.
  • Short-circuit NaN and infinite numeric values to return None instead of raising from datetime.fromtimestamp.
  • Return None after exhausting all unit attempts when no valid datetime can be constructed, instead of propagating OverflowError/OSError/ValueError.
src/log_analyzer_cli/parsers/json_log.py
Add tests that cover multiple numeric timestamp units and invalid numeric/null timestamp values for the JSON parser.
  • Remove the obsolete test that checked parsing under various level field names, likely moved or superseded elsewhere.
  • Add TestJSONNumericTimestampUnits test class with cases for second, microsecond, and nanosecond numeric timestamps resolving to the correct datetime.
  • Add tests ensuring huge out-of-range numeric timestamps produce entries with timestamp set to None rather than raising.
  • Add tests verifying that NaN and null timestamp fields are handled gracefully, yielding timestamp is None.
tests/test_parsers.py
Document the improved numeric timestamp handling and error behavior in the changelog.
  • Add an Unreleased section describing the new numeric timestamp unit support and the behavior for NaN/infinity/non-finite floats.
  • Clarify that previously these values could crash with ValueError from datetime.fromtimestamp and now result in a None timestamp field.
CHANGELOG.md

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 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Refactors JSON timestamp parsing in JSONLogParser to add a _numeric_to_datetime helper that handles seconds, milliseconds, microseconds, and nanoseconds via magnitude-based unit detection, returning None for invalid or non-finite values instead of raising. Updates tests and changelog accordingly.

Changes

Numeric timestamp parsing fix

Layer / File(s) Summary
Numeric timestamp conversion logic
src/log_analyzer_cli/parsers/json_log.py
Adds _numeric_to_datetime static method with NaN/inf checks and a divide-by-1000 retry loop to resolve seconds/ms/µs/ns timestamps; _extract_timestamp now delegates to it instead of using a fixed magnitude threshold.
Tests and changelog
tests/test_parsers.py, CHANGELOG.md
Adds TestJSONNumericTimestampUnits covering microsecond, nanosecond, second, out-of-range, NaN, and null timestamp cases; removes an unrelated level-field-name test; updates changelog to describe the fix.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

No sequence diagram generated; the change is a localized parsing logic refactor within a single function/helper, not a multi-component interaction flow.

🚥 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 accurately summarizes the main parser change and the new handling for invalid numeric timestamps.
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/json-numeric-timestamp-units

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 test_parse_json_various_level_names test was removed without an obvious replacement; if that behavior is still supported, consider keeping or relocating the test to avoid a silent regression in level-field handling.
  • In _numeric_to_datetime, the NaN/inf detection would be clearer and more conventional using math.isnan/math.isfinite rather than value != value and explicit float('inf') comparisons.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `test_parse_json_various_level_names` test was removed without an obvious replacement; if that behavior is still supported, consider keeping or relocating the test to avoid a silent regression in level-field handling.
- In `_numeric_to_datetime`, the NaN/inf detection would be clearer and more conventional using `math.isnan`/`math.isfinite` rather than `value != value` and explicit `float('inf')` comparisons.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_parsers.py (1)

84-85: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the level-field alias coveragetests/test_parsers.py:84-85 removes the only test that exercises severity/loglevel handling, and no other test covers those aliases. Restore it or add an equivalent check.

🤖 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_parsers.py` around lines 84 - 85, The parser test coverage for the
level-field aliases was removed, and no other test currently verifies
severity/loglevel handling. Restore the deleted assertion in
tests/test_parsers.py or add an equivalent test in the same test area that
exercises the parser path for severity and loglevel aliases, using the existing
parser/test helper symbols to keep the coverage intact.
🤖 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/parsers/json_log.py`:
- Around line 84-108: The _numeric_to_datetime helper is converting numeric JSON
timestamps with local time semantics, which makes parsed values depend on the
host timezone. Update the datetime.fromtimestamp path in json_log.py’s
JSONLogParser._numeric_to_datetime to always normalize through UTC while keeping
the returned datetime naive like the rest of the parser, and preserve the
existing magnitude-based fallback loop for
seconds/milliseconds/microseconds/nanoseconds.

---

Outside diff comments:
In `@tests/test_parsers.py`:
- Around line 84-85: The parser test coverage for the level-field aliases was
removed, and no other test currently verifies severity/loglevel handling.
Restore the deleted assertion in tests/test_parsers.py or add an equivalent test
in the same test area that exercises the parser path for severity and loglevel
aliases, using the existing parser/test helper symbols to keep the coverage
intact.
🪄 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: f9af16b5-0d5b-49a0-96c9-a6a6401419e3

📥 Commits

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

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/log_analyzer_cli/parsers/json_log.py
  • tests/test_parsers.py

Comment on lines +84 to +108
@staticmethod
def _numeric_to_datetime(value: float) -> Optional[datetime]:
"""Convert a numeric timestamp to a datetime.

JSON log producers disagree on the unit: seconds (Unix epoch),
milliseconds (Java ``System.currentTimeMillis()``), microseconds
(``UnixMicro()``), or nanoseconds (``UnixNano()``). The
heuristic is magnitude-based — anything above the year ~33658
boundary can't be a raw second count, so we keep dividing by
1000 until the result lands in a sensible range or we run out
of plausible units.
"""
if value != value or value in (float("inf"), float("-inf")):
return None

# datetime.fromtimestamp can't represent years far outside the
# usual range; clamp early so a stray nanosecond value doesn't
# bubble up as "year 52218064 is out of range".
seconds = float(value)
for _ in range(4):
try:
return datetime.fromtimestamp(seconds)
except (OverflowError, OSError, ValueError):
seconds /= 1000.0
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check other parsers for tz-aware vs naive datetime handling, and CI timezone config
rg -n 'fromtimestamp|tzinfo|timezone' src/log_analyzer_cli/parsers -A2 -B2
fd -e yml -e yaml . .github | xargs -r rg -n 'TZ|timezone' -i

Repository: HrachShah/log-analyzer-cli

Length of output: 1031


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant parser files and datetime usage sites
git ls-files 'src/log_analyzer_cli/**/*.py' 'tests/**/*.py' '.github/**/*.yml' '.github/**/*.yaml' | sed -n '1,200p'

printf '\n--- datetime-related usages ---\n'
rg -n 'datetime\.fromtimestamp|datetime\.utcnow|datetime\.now|timezone\.utc|tzinfo|astimezone|replace\(tzinfo|compare|<=|>=|<|>' src/log_analyzer_cli tests -g '*.py' -A2 -B2

printf '\n--- json_log parser file outline ---\n'
ast-grep outline src/log_analyzer_cli/parsers/json_log.py --view expanded

printf '\n--- relevant tests around timestamp parsing ---\n'
rg -n 'hour|minute|second|fromtimestamp|timestamp|timezone|utc' tests/test_parsers.py -A3 -B3

printf '\n--- CI timezone settings ---\n'
rg -n 'TZ|timezone|UTC' .github -g '*.yml' -g '*.yaml' -i -A2 -B2

Repository: HrachShah/log-analyzer-cli

Length of output: 40470


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from datetime import datetime, timezone
ts = 1647780800
print("utc:", datetime.fromtimestamp(ts, tz=timezone.utc))
print("local:", datetime.fromtimestamp(ts))
PY

printf '\n--- parse_timestamp and filters ---\n'
sed -n '1,240p' src/log_analyzer_cli/utils.py

printf '\n--- JSON parser timestamp helpers ---\n'
sed -n '1,180p' src/log_analyzer_cli/parsers/json_log.py

printf '\n--- generic/syslog timestamp helpers ---\n'
sed -n '1,170p' src/log_analyzer_cli/parsers/generic.py
printf '\n'
sed -n '1,180p' src/log_analyzer_cli/parsers/syslog.py
printf '\n'
sed -n '1,160p' src/log_analyzer_cli/parsers/apache.py

Repository: HrachShah/log-analyzer-cli

Length of output: 22187


Normalize numeric JSON timestamps to UTC at src/log_analyzer_cli/parsers/json_log.py:105. datetime.fromtimestamp(seconds) uses the host’s local timezone, so the same epoch value parses to different wall-clock times on non-UTC machines. That makes the numeric JSON path environment-dependent and breaks the new timestamp assertions outside UTC. Keep this conversion in UTC while preserving the parser’s current naive-datetime style.

🤖 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/parsers/json_log.py` around lines 84 - 108, The
_numeric_to_datetime helper is converting numeric JSON timestamps with local
time semantics, which makes parsed values depend on the host timezone. Update
the datetime.fromtimestamp path in json_log.py’s
JSONLogParser._numeric_to_datetime to always normalize through UTC while keeping
the returned datetime naive like the rest of the parser, and preserve the
existing magnitude-based fallback loop for
seconds/milliseconds/microseconds/nanoseconds.

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