Skip to content

Commit

Permalink
feat: include filename and line number in logs/errors
Browse files Browse the repository at this point in the history
  • Loading branch information
taobojlen committed Jul 2, 2024
1 parent 9ef2e74 commit 28c75a4
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 1 deletion.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extend-select = [
"B", # bugbear
"FIX", # disallow FIXME/TODO comments
"F", # pyflakes
"T20", # flake8-print
]

[tool.pyright]
Expand Down
4 changes: 4 additions & 0 deletions src/queryspy/listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from django.conf import settings
from django.db import models

from queryspy.util import get_caller

from .errors import NPlusOneError, QuerySpyError

ModelAndField = tuple[Type[models.Model], str]
Expand Down Expand Up @@ -60,6 +62,8 @@ def _alert(self, model: type[models.Model], field: str, message: str):
if is_allowlisted:
return

caller = get_caller()
message = f"{message} at {caller.filename}:{caller.lineno} in {caller.function}"
if should_error:
raise self.error_class(message)
else:
Expand Down
15 changes: 15 additions & 0 deletions src/queryspy/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import inspect

PATTERNS = ["site-packages", "queryspy/listeners.py", "queryspy/patch.py"]


def get_caller() -> inspect.FrameInfo:
"""
Returns the filename and line number of the current caller,
excluding any code in site-packages or queryspy.
"""
return next(
frame
for frame in inspect.stack()[1:]
if not any(pattern in frame.filename for pattern in PATTERNS)
)
22 changes: 21 additions & 1 deletion tests/test_listeners.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import re

import pytest
from djangoproject.social.models import Post, User
Expand All @@ -20,7 +21,26 @@ def test_can_log_errors(settings, caplog):
with caplog.at_level(logging.WARNING):
for user in User.objects.all():
_ = list(user.posts.all())
assert "N+1 detected on User.posts" in caplog.text
assert (
re.search(
r"N\+1 detected on User\.posts at .*\/test_listeners\.py:23 in test_can_log_errors",
caplog.text,
)
is not None
), f"{caplog.text} does not match regex"


@queryspy_context()
def test_errors_include_caller():
[user_1, user_2] = UserFactory.create_batch(2)
PostFactory.create(author=user_1)
PostFactory.create(author=user_2)
with pytest.raises(
NPlusOneError,
match=r"N\+1 detected on User\.posts at .*\/test_listeners\.py:43 in test_errors_include_caller",
):
for user in User.objects.all():
_ = list(user.posts.all())


@queryspy_context()
Expand Down

0 comments on commit 28c75a4

Please sign in to comment.