mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
fix(api): filter neo4j.io defunct connection logs in Sentry before_send (#10452)
This commit is contained in:
@@ -8,6 +8,7 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
- Finding groups list/latest now apply computed status/severity filters and finding-level prefilters (delta, region, service, category, resource group, scan, resource type), plus `check_title` support for sort/filter consistency [(#10428)](https://github.com/prowler-cloud/prowler/pull/10428)
|
||||
- Populate compliance data inside `check_metadata` for findings, which was always returned as `null` [(#10449)](https://github.com/prowler-cloud/prowler/pull/10449)
|
||||
- Filter transient Neo4j defunct connection logs in Sentry `before_send` to suppress false-positive alerts handled by `RetryableSession` retries [(#10452)](https://github.com/prowler-cloud/prowler/pull/10452)
|
||||
|
||||
## [1.23.0] (Prowler v5.22.0)
|
||||
|
||||
|
||||
@@ -4,14 +4,25 @@ from unittest.mock import MagicMock
|
||||
from config.settings.sentry import before_send
|
||||
|
||||
|
||||
def _make_log_record(msg, level=logging.ERROR, name="test", args=None):
|
||||
"""Build a real LogRecord so getMessage() works like in production."""
|
||||
record = logging.LogRecord(
|
||||
name=name,
|
||||
level=level,
|
||||
pathname="",
|
||||
lineno=0,
|
||||
msg=msg,
|
||||
args=args,
|
||||
exc_info=None,
|
||||
)
|
||||
return record
|
||||
|
||||
|
||||
def test_before_send_ignores_log_with_ignored_exception():
|
||||
"""Test that before_send ignores logs containing ignored exceptions."""
|
||||
log_record = MagicMock()
|
||||
log_record.msg = "Provider kubernetes is not connected"
|
||||
log_record.levelno = logging.ERROR # 40
|
||||
log_record = _make_log_record("Provider kubernetes is not connected")
|
||||
|
||||
hint = {"log_record": log_record}
|
||||
|
||||
event = MagicMock()
|
||||
|
||||
result = before_send(event, hint)
|
||||
@@ -36,12 +47,9 @@ def test_before_send_ignores_exception_with_ignored_exception():
|
||||
|
||||
def test_before_send_passes_through_non_ignored_log():
|
||||
"""Test that before_send passes through logs that don't contain ignored exceptions."""
|
||||
log_record = MagicMock()
|
||||
log_record.msg = "Some other error message"
|
||||
log_record.levelno = logging.ERROR # 40
|
||||
log_record = _make_log_record("Some other error message")
|
||||
|
||||
hint = {"log_record": log_record}
|
||||
|
||||
event = MagicMock()
|
||||
|
||||
result = before_send(event, hint)
|
||||
@@ -66,15 +74,53 @@ def test_before_send_passes_through_non_ignored_exception():
|
||||
|
||||
def test_before_send_handles_warning_level():
|
||||
"""Test that before_send handles warning level logs."""
|
||||
log_record = MagicMock()
|
||||
log_record.msg = "Provider kubernetes is not connected"
|
||||
log_record.levelno = logging.WARNING # 30
|
||||
log_record = _make_log_record(
|
||||
"Provider kubernetes is not connected", level=logging.WARNING
|
||||
)
|
||||
|
||||
hint = {"log_record": log_record}
|
||||
|
||||
event = MagicMock()
|
||||
|
||||
result = before_send(event, hint)
|
||||
|
||||
# Assert that the event was dropped (None returned)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_before_send_ignores_neo4j_defunct_connection():
|
||||
"""Test that before_send drops neo4j.io defunct connection logs.
|
||||
|
||||
The Neo4j driver logs transient connection errors at ERROR level
|
||||
before RetryableSession retries them. These are noise.
|
||||
|
||||
The driver uses %s formatting, so "defunct" is in the args, not
|
||||
in the template. This test mirrors the real LogRecord structure.
|
||||
"""
|
||||
log_record = _make_log_record(
|
||||
msg="[#%04X] _: <CONNECTION> error: %s: %r",
|
||||
name="neo4j.io",
|
||||
args=(
|
||||
0xE5CC,
|
||||
"Failed to read from defunct connection "
|
||||
"IPv4Address(('cloud-neo4j.prowler.com', 7687))",
|
||||
ConnectionResetError(104, "Connection reset by peer"),
|
||||
),
|
||||
)
|
||||
|
||||
hint = {"log_record": log_record}
|
||||
event = MagicMock()
|
||||
|
||||
assert before_send(event, hint) is None
|
||||
|
||||
|
||||
def test_before_send_passes_non_defunct_neo4j_log():
|
||||
"""Test that before_send passes through neo4j.io logs that are not about defunct connections."""
|
||||
log_record = _make_log_record(
|
||||
msg="Some other neo4j transport error",
|
||||
name="neo4j.io",
|
||||
)
|
||||
|
||||
hint = {"log_record": log_record}
|
||||
event = MagicMock()
|
||||
|
||||
assert before_send(event, hint) == event
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sentry_sdk
|
||||
|
||||
from config.env import env
|
||||
|
||||
IGNORED_EXCEPTIONS = [
|
||||
@@ -85,8 +86,20 @@ def before_send(event, hint):
|
||||
# Ignore logs with the ignored_exceptions
|
||||
# https://docs.python.org/3/library/logging.html#logrecord-objects
|
||||
if "log_record" in hint:
|
||||
log_msg = hint["log_record"].msg
|
||||
log_lvl = hint["log_record"].levelno
|
||||
log_record = hint["log_record"]
|
||||
log_msg = log_record.getMessage()
|
||||
log_lvl = log_record.levelno
|
||||
|
||||
# The Neo4j driver logs transient connection errors (defunct
|
||||
# connections, resets) at ERROR level via the `neo4j.io` logger.
|
||||
# `RetryableSession` handles these with retries. If all retries
|
||||
# are exhausted, the exception propagates and Sentry captures
|
||||
# it as a normal exception event.
|
||||
if (
|
||||
getattr(log_record, "name", "").startswith("neo4j.io")
|
||||
and "defunct" in log_msg
|
||||
):
|
||||
return None
|
||||
|
||||
# Handle Error and Critical events and discard the rest
|
||||
if log_lvl <= 40 and any(ignored in log_msg for ignored in IGNORED_EXCEPTIONS):
|
||||
|
||||
Reference in New Issue
Block a user