Django logging with dictionaries as context

This view in realestate\auth2\views.py validates form data and checks if the email or phone has already been taken. It also logs messages depending on situation and I pass dictionaries as logging context.

from globals import log_messages
from globals.logger import logger
# ...

def register_post(request: HttpRequest, template: str):
    form = RegisterForm(request.POST)

    # 2 Input validation & authorization
    if not form.is_valid():
        return render(request, template, {"form": form})

    data = form.cleaned_data

    # 6 Error handling
    if service.email_taken(data["email"]):
        messages.error(request, "Korisnik sa ovom imejl adresom već postoji.")

        # 5 Security logging
        logger.warning(log_messages.auth_view["register_email_taken"], extra={"email": data["email"]})
        return redirect(reverse("register"))

    # 6 Error handling
    if service.phone_taken(data["phone"]):
        messages.error(request, "Korisnik sa ovim brojem telefona već postoji.")

        # 5 Security logging
        logger.warning(log_messages.auth_view["register_phone_taken"], extra={"phone": data["phone"]})
        return redirect(reverse("register"))

    user = service.create(data) # method CRUD service that I made
    login2(request, user) # from django.contrib.auth import login as login2

    # 5 Security logging
    logger.info(log_messages.auth_view["register_success"], extra={"user_pk": user.pk})
    return redirect(reverse("listing-list"))

The global logger is instantiated in globals/logger.py:

import logging

logger = logging.getLogger(__name__)

The messages are present in globals/log_messages.py:

auth_view = {
    "register_email_taken": "Registration failed. Email address is already taken.",
    "register_phone_taken": "Registration failed. Phone number is already taken.",
    "register_success": "User registered successfully.",
    "login_success": "User logged in successfully.",
    "login_missing_credentials": "Login failed. Credentials do not exist.",
    "logout_success": "User logged out."
}

How logging is configured in realestate/settings.py:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s [%(levelname)s] %(message)s"
        }
    },
    "handlers": {
        "file": {
            "level": "DEBUG",
            "class": "logging.FileHandler",
            "filename": "realestate.log",
            "formatter": "standard"
        },
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "standard"
        }
    },
    "loggers": {
        "":
        {
            "handlers": ["file", "console"],
            "level": "DEBUG"
        }
    }
}

When I tried printing log messages with context to console, the context was not output along messages.

2026-07-11 12:55:43,185 [WARNING] Registration failed. Email address is already taken.

I tried adding extra parameter to the logger format `"format": "%(asctime)s [%(levelname)s] %(message)s %(extra)s", but then the errors were printed.

--- Logging error ---
Traceback (most recent call last):
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 472, in format
    return self._format(record)
           ~~~~~~~~~~~~^^^^^^^^
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 468, in _format
    return self._fmt % values
           ~~~~~~~~~~^~~~~~~~
KeyError: 'extra'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 1151, in emit
    msg = self.format(record)
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 999, in format
    return fmt.format(record)
           ~~~~~~~~~~^^^^^^^^
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 715, in format
    s = self.formatMessage(record)
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 684, in formatMessage
    return self._style.format(record)
           ~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 474, in format
    raise ValueError('Formatting field not found in record: %s' % e)
ValueError: Formatting field not found in record: 'extra'
Call stack:
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\manage.py", line 22, in <module>
    main()
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\__init__.py", line 443, in execute_from_command_line
    utility.execute()
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\__init__.py", line 437, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\commands\test.py", line 24, in run_from_argv
    super().run_from_argv(argv)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\base.py", line 420, in run_from_argv
    self.execute(*args, **cmd_options)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\base.py", line 464, in execute
    output = self.handle(*args, **options)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\commands\test.py", line 63, in handle
    failures = test_runner.run_tests(test_labels)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\runner.py", line 1134, in run_tests
    result = self.run_suite(suite)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\runner.py", line 1061, in run_suite
    return runner.run(suite)
  File "C:\_dev\Python314\Lib\unittest\runner.py", line 257, in run
    test(result)
  File "C:\_dev\Python314\Lib\unittest\suite.py", line 84, in __call__
    return self.run(*args, **kwds)
  File "C:\_dev\Python314\Lib\unittest\suite.py", line 122, in run
    test(result)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\testcases.py", line 313, in __call__
    self._setup_and_call(result)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\testcases.py", line 368, in _setup_and_call
    super().__call__(result)
  File "C:\_dev\Python314\Lib\unittest\case.py", line 725, in __call__
    return self.run(*args, **kwds)
  File "C:\_dev\Python314\Lib\unittest\case.py", line 669, in run
    self._callTestMethod(testMethod)
  File "C:\_dev\Python314\Lib\unittest\case.py", line 615, in _callTestMethod
    result = method()
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\auth2\tests.py", line 44, in test_register_email_taken
    response = self.client.post(reverse("register"), data)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 1156, in post
    response = super().post(
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 499, in post
    return self.generic(
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 671, in generic
    return self.request(**r)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 1085, in request
    response = self.handler(environ)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 186, in __call__
    response = self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\base.py", line 141, in get_response
    response = self._middleware_chain(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\base.py", line 198, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\auth2\views.py", line 92, in register
    return register_post(request, template) if request.method == "POST" else register_get(request, template)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django_ratelimit\decorators.py", line 27, in _wrapped
    return fn(request, *args, **kw)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\auth2\views.py", line 38, in register_post
    logger.warning(log_messages.auth_view["register_email_taken"], extra={"email": data["email"]})
Message: 'Registration failed. Email address is already taken.'
Arguments: ()

I also tried this format, but I don't think it's suitable for context that varies in size and content.

"format": "%(asctime)s [%(levelname)s] %(message)s %(email)s %(phone)s %(user_pk)s"

Errors again:

Traceback (most recent call last):
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 472, in format
    return self._format(record)
           ~~~~~~~~~~~~^^^^^^^^
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 468, in _format
    return self._fmt % values
           ~~~~~~~~~~^~~~~~~~
KeyError: 'phone'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 1151, in emit
    msg = self.format(record)
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 999, in format
    return fmt.format(record)
           ~~~~~~~~~~^^^^^^^^
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 715, in format
    s = self.formatMessage(record)
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 684, in formatMessage
    return self._style.format(record)
           ~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "C:\_dev\Python314\Lib\logging\__init__.py", line 474, in format
    raise ValueError('Formatting field not found in record: %s' % e)
ValueError: Formatting field not found in record: 'phone'
Call stack:
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\manage.py", line 22, in <module>
    main()
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\__init__.py", line 443, in execute_from_command_line
    utility.execute()
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\__init__.py", line 437, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\commands\test.py", line 24, in run_from_argv
    super().run_from_argv(argv)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\base.py", line 420, in run_from_argv
    self.execute(*args, **cmd_options)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\base.py", line 464, in execute
    output = self.handle(*args, **options)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\management\commands\test.py", line 63, in handle
    failures = test_runner.run_tests(test_labels)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\runner.py", line 1134, in run_tests
    result = self.run_suite(suite)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\runner.py", line 1061, in run_suite
    return runner.run(suite)
  File "C:\_dev\Python314\Lib\unittest\runner.py", line 257, in run
    test(result)
  File "C:\_dev\Python314\Lib\unittest\suite.py", line 84, in __call__
    return self.run(*args, **kwds)
  File "C:\_dev\Python314\Lib\unittest\suite.py", line 122, in run
    test(result)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\testcases.py", line 313, in __call__
    self._setup_and_call(result)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\testcases.py", line 368, in _setup_and_call
    super().__call__(result)
  File "C:\_dev\Python314\Lib\unittest\case.py", line 725, in __call__
    return self.run(*args, **kwds)
  File "C:\_dev\Python314\Lib\unittest\case.py", line 669, in run
    self._callTestMethod(testMethod)
  File "C:\_dev\Python314\Lib\unittest\case.py", line 615, in _callTestMethod
    result = method()
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\auth2\tests.py", line 44, in test_register_email_taken
    response = self.client.post(reverse("register"), data)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 1156, in post
    response = super().post(
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 499, in post
    return self.generic(
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 671, in generic
    return self.request(**r)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 1085, in request
    response = self.handler(environ)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\test\client.py", line 186, in __call__
    response = self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\base.py", line 141, in get_response
    response = self._middleware_chain(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\utils\deprecation.py", line 309, in __call__
    response = response or self.get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django\core\handlers\base.py", line 198, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\auth2\views.py", line 92, in register
    return register_post(request, template) if request.method == "POST" else register_get(request, template)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\venv\Lib\site-packages\django_ratelimit\decorators.py", line 27, in _wrapped
    return fn(request, *args, **kw)
  File "D:\Documents\_Dokumenti\Projekti\_Master\Real Estate App\Django\secure\realestate\auth2\views.py", line 38, in register_post
    logger.warning(log_messages.auth_view["register_email_taken"], extra={"email": data["email"]})
Message: 'Registration failed. Email address is already taken.'
Arguments: ()

I also did:

"format": "%(asctime)s [%(levelname)s] %(message)s %(extra)s"
logger.warning(log_messages.auth_view["register_email_taken"], extra={"extra": {"email": data["email"]}})

which also printed the context.

2026-07-11 13:27:48,145 [WARNING] Registration failed. Email address is already taken. {'email': 'test@example.com'}

But I'd rather not have a dictionary be nested in another.

Вернуться на верх