From 740521746661b0df889bdff8a2549a4eaf9b2bca Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 7 Oct 2026 15:01:59 +0300 Subject: [PATCH] fix(logging): the application log rotates into a bounded, auto-created directory --- README.md | 2 ++ webvirtcloud/settings.py.template | 16 +++++++++---- webvirtcloud/test_logging.py | 40 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 webvirtcloud/test_logging.py diff --git a/README.md b/README.md index 813bbf33..df77b44e 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,8 @@ Failed logins are limited with `django-axes` (installed from `conf/requirements. OTP, when enabled, is enforced only by the login form, so close the two doors that skip it. Delete the `path('api-auth/', include('rest_framework.urls', ...))` line from your `webvirtcloud/urls.py` (the DRF browsable-API login created a password-only session), and add `"DEFAULT_AUTHENTICATION_CLASSES": ["rest_framework.authentication.SessionAuthentication"]` to the `REST_FRAMEWORK` block in your `webvirtcloud/settings.py` (without it the API accepts HTTP Basic credentials, a password-only bypass). The API then needs a session from the login form; password-based programmatic access no longer works. After updating settings, end any sessions opened through the old doors so they do not keep OTP-free access (empty the `django_session` table, e.g. `python3 manage.py shell -c "from django.contrib.sessions.models import Session; Session.objects.all().delete()"`); everyone signs in again. OTP is per user: a user without a confirmed device signs in with the password alone. +The application log now rotates (10 MB × 5 files) and defaults to `data/webvirtcloud.log` (override with the `WEBVIRTCLOUD_LOG_FILE` env var). Existing installs: copy the `LOGGING` `default` handler from `webvirtcloud/settings.py.template` into your `webvirtcloud/settings.py` (it adds `maxBytes`, `backupCount`, and an absolute `filename`); the old unbounded `webvirtcloud.log` in the app directory can be removed. + Migration: where the hosts cannot resolve each other's host names, set each compute's **Migration address** (the compute's edit page) to the IP the other hosts reach it on for migrations. Define shared storage as a storage pool on every host: a VM migrates only when its disk, backing and ISO files and block devices are volumes of the destination's pools (network disks are not checked). The "VM Clone Auto Migrate" setting is removed (migration `appsettings.0015`); a clone stays on the host of the VM it was made from. Migration `accounts.0007` transfers the first-login requirement from any existing diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index 61dd8803..df47116c 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -178,6 +178,12 @@ LOCALE_PATHS = [ "locale/", ] +# Resolve the application log path and make sure its directory exists, so a +# clean checkout, CI, or a Docker build (none of which create data/) does not +# fail when logging is configured. +WEBVIRTCLOUD_LOG_FILE = os.getenv("WEBVIRTCLOUD_LOG_FILE", str(BASE_DIR / "data" / "webvirtcloud.log")) +os.makedirs(os.path.dirname(WEBVIRTCLOUD_LOG_FILE), exist_ok=True) + LOGGING = { 'version': 1, 'disable_existing_loggers': False, @@ -192,10 +198,12 @@ LOGGING = { 'class': 'django.utils.log.AdminEmailHandler' }, 'default': { - 'level':'INFO', - 'class':'logging.handlers.RotatingFileHandler', - 'filename': 'webvirtcloud.log', - 'formatter':'standard', + 'level': 'INFO', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': WEBVIRTCLOUD_LOG_FILE, + 'maxBytes': 10 * 1024 * 1024, + 'backupCount': 5, + 'formatter': 'standard', }, }, 'loggers': { diff --git a/webvirtcloud/test_logging.py b/webvirtcloud/test_logging.py new file mode 100644 index 00000000..711082c9 --- /dev/null +++ b/webvirtcloud/test_logging.py @@ -0,0 +1,40 @@ +"""The app log is bounded (rotates) and written to a deterministic absolute +path, not the process working directory.""" + +import os + +from django.conf import settings +from django.test import TestCase + + +class LoggingConfigTestCase(TestCase): + def test_default_handler_rotates(self): + handler = settings.LOGGING["handlers"]["default"] + self.assertEqual(handler["class"], "logging.handlers.RotatingFileHandler") + self.assertGreater(handler["maxBytes"], 0) + self.assertGreater(handler["backupCount"], 0) + + def test_log_path_is_absolute_and_not_cwd(self): + handler = settings.LOGGING["handlers"]["default"] + self.assertTrue(os.path.isabs(handler["filename"]), handler["filename"]) + + def test_missing_log_directory_is_created_on_settings_import(self): + # A clean checkout / CI / Docker build may not have the log's parent dir; + # importing settings (logging config) must create it, not crash. + import subprocess + import sys + import tempfile + + tmp = tempfile.mkdtemp() + log = os.path.join(tmp, "nested", "wvc.log") + env = { + **os.environ, + "WEBVIRTCLOUD_LOG_FILE": log, + "DJANGO_SETTINGS_MODULE": "webvirtcloud.settings", + } + result = subprocess.run( + [sys.executable, "-c", "import django; django.setup()"], + env=env, capture_output=True, text=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertTrue(os.path.isdir(os.path.dirname(log)), log)