From cdef1831ba343a6d5d46e0879a47a9f4a351bd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Mon, 8 Dec 2025 16:35:56 +0100 Subject: [PATCH] Add option to Core settings to enable duplicated logs (#6400) Introduce new option `duplicate_log_file` to HA Core configuration that will set an environment variable `HA_DUPLICATE_LOG_FILE=1` for the Core container if enabled. This will serve as a flag for Core to enable the legacy log file, along the standard logging which is handled by Systemd Journal. --- supervisor/api/homeassistant.py | 6 ++++++ supervisor/const.py | 1 + supervisor/docker/const.py | 1 + supervisor/docker/homeassistant.py | 3 +++ supervisor/homeassistant/module.py | 11 +++++++++++ supervisor/homeassistant/validate.py | 2 ++ tests/docker/test_homeassistant.py | 24 ++++++++++++++++++++++++ 7 files changed, 48 insertions(+) diff --git a/supervisor/api/homeassistant.py b/supervisor/api/homeassistant.py index 673869c71..ff74ed539 100644 --- a/supervisor/api/homeassistant.py +++ b/supervisor/api/homeassistant.py @@ -18,6 +18,7 @@ from ..const import ( ATTR_BLK_WRITE, ATTR_BOOT, ATTR_CPU_PERCENT, + ATTR_DUPLICATE_LOG_FILE, ATTR_IMAGE, ATTR_IP_ADDRESS, ATTR_JOB_ID, @@ -55,6 +56,7 @@ SCHEMA_OPTIONS = vol.Schema( vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(str), vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(str), vol.Optional(ATTR_BACKUPS_EXCLUDE_DATABASE): vol.Boolean(), + vol.Optional(ATTR_DUPLICATE_LOG_FILE): vol.Boolean(), } ) @@ -112,6 +114,7 @@ class APIHomeAssistant(CoreSysAttributes): ATTR_AUDIO_INPUT: self.sys_homeassistant.audio_input, ATTR_AUDIO_OUTPUT: self.sys_homeassistant.audio_output, ATTR_BACKUPS_EXCLUDE_DATABASE: self.sys_homeassistant.backups_exclude_database, + ATTR_DUPLICATE_LOG_FILE: self.sys_homeassistant.duplicate_log_file, } @api_process @@ -151,6 +154,9 @@ class APIHomeAssistant(CoreSysAttributes): ATTR_BACKUPS_EXCLUDE_DATABASE ] + if ATTR_DUPLICATE_LOG_FILE in body: + self.sys_homeassistant.duplicate_log_file = body[ATTR_DUPLICATE_LOG_FILE] + await self.sys_homeassistant.save_data() @api_process diff --git a/supervisor/const.py b/supervisor/const.py index 8b7c9cca9..d9f6431b1 100644 --- a/supervisor/const.py +++ b/supervisor/const.py @@ -179,6 +179,7 @@ ATTR_DOCKER = "docker" ATTR_DOCKER_API = "docker_api" ATTR_DOCUMENTATION = "documentation" ATTR_DOMAINS = "domains" +ATTR_DUPLICATE_LOG_FILE = "duplicate_log_file" ATTR_ENABLE = "enable" ATTR_ENABLE_IPV6 = "enable_ipv6" ATTR_ENABLED = "enabled" diff --git a/supervisor/docker/const.py b/supervisor/docker/const.py index b5ccc5b03..d9bcc31ae 100644 --- a/supervisor/docker/const.py +++ b/supervisor/docker/const.py @@ -133,6 +133,7 @@ class PullImageLayerStage(Enum): return None +ENV_DUPLICATE_LOG_FILE = "HA_DUPLICATE_LOG_FILE" ENV_TIME = "TZ" ENV_TOKEN = "SUPERVISOR_TOKEN" ENV_TOKEN_OLD = "HASSIO_TOKEN" diff --git a/supervisor/docker/homeassistant.py b/supervisor/docker/homeassistant.py index 273271901..6581c4da4 100644 --- a/supervisor/docker/homeassistant.py +++ b/supervisor/docker/homeassistant.py @@ -14,6 +14,7 @@ from ..homeassistant.const import LANDINGPAGE from ..jobs.const import JobConcurrency from ..jobs.decorator import Job from .const import ( + ENV_DUPLICATE_LOG_FILE, ENV_TIME, ENV_TOKEN, ENV_TOKEN_OLD, @@ -174,6 +175,8 @@ class DockerHomeAssistant(DockerInterface): } if restore_job_id: environment[ENV_RESTORE_JOB_ID] = restore_job_id + if self.sys_homeassistant.duplicate_log_file: + environment[ENV_DUPLICATE_LOG_FILE] = "1" await self._run( tag=(self.sys_homeassistant.version), name=self.name, diff --git a/supervisor/homeassistant/module.py b/supervisor/homeassistant/module.py index 9f2cba6b2..ce0a895f6 100644 --- a/supervisor/homeassistant/module.py +++ b/supervisor/homeassistant/module.py @@ -23,6 +23,7 @@ from ..const import ( ATTR_AUDIO_OUTPUT, ATTR_BACKUPS_EXCLUDE_DATABASE, ATTR_BOOT, + ATTR_DUPLICATE_LOG_FILE, ATTR_IMAGE, ATTR_MESSAGE, ATTR_PORT, @@ -299,6 +300,16 @@ class HomeAssistant(FileConfiguration, CoreSysAttributes): """Set whether backups should exclude database by default.""" self._data[ATTR_BACKUPS_EXCLUDE_DATABASE] = value + @property + def duplicate_log_file(self) -> bool: + """Return True if Home Assistant should duplicate logs to file.""" + return self._data[ATTR_DUPLICATE_LOG_FILE] + + @duplicate_log_file.setter + def duplicate_log_file(self, value: bool) -> None: + """Set whether Home Assistant should duplicate logs to file.""" + self._data[ATTR_DUPLICATE_LOG_FILE] = value + async def load(self) -> None: """Prepare Home Assistant object.""" await asyncio.wait( diff --git a/supervisor/homeassistant/validate.py b/supervisor/homeassistant/validate.py index 1c8267d47..3b9f5eb2f 100644 --- a/supervisor/homeassistant/validate.py +++ b/supervisor/homeassistant/validate.py @@ -10,6 +10,7 @@ from ..const import ( ATTR_AUDIO_OUTPUT, ATTR_BACKUPS_EXCLUDE_DATABASE, ATTR_BOOT, + ATTR_DUPLICATE_LOG_FILE, ATTR_IMAGE, ATTR_PORT, ATTR_REFRESH_TOKEN, @@ -36,6 +37,7 @@ SCHEMA_HASS_CONFIG = vol.Schema( vol.Optional(ATTR_AUDIO_OUTPUT, default=None): vol.Maybe(str), vol.Optional(ATTR_AUDIO_INPUT, default=None): vol.Maybe(str), vol.Optional(ATTR_BACKUPS_EXCLUDE_DATABASE, default=False): vol.Boolean(), + vol.Optional(ATTR_DUPLICATE_LOG_FILE, default=False): vol.Boolean(), vol.Optional(ATTR_OVERRIDE_IMAGE, default=False): vol.Boolean(), }, extra=vol.REMOVE_EXTRA, diff --git a/tests/docker/test_homeassistant.py b/tests/docker/test_homeassistant.py index 5e4833325..6df19253f 100644 --- a/tests/docker/test_homeassistant.py +++ b/tests/docker/test_homeassistant.py @@ -46,6 +46,7 @@ async def test_homeassistant_start( "TZ": ANY, "SUPERVISOR_TOKEN": ANY, "HASSIO_TOKEN": ANY, + # no "HA_DUPLICATE_LOG_FILE" } assert run.call_args.kwargs["mounts"] == [ DEV_MOUNT, @@ -105,6 +106,28 @@ async def test_homeassistant_start( assert "volumes" not in run.call_args.kwargs +async def test_homeassistant_start_with_duplicate_log_file( + coresys: CoreSys, tmp_supervisor_data: Path, path_extern +): + """Test starting homeassistant with duplicate_log_file enabled.""" + coresys.homeassistant.version = AwesomeVersion("2025.12.0") + coresys.homeassistant.duplicate_log_file = True + + with ( + patch.object(DockerAPI, "run") as run, + patch.object( + DockerHomeAssistant, "is_running", side_effect=[False, False, True] + ), + patch("supervisor.homeassistant.core.asyncio.sleep"), + ): + await coresys.homeassistant.core.start() + + run.assert_called_once() + env = run.call_args.kwargs["environment"] + assert "HA_DUPLICATE_LOG_FILE" in env + assert env["HA_DUPLICATE_LOG_FILE"] == "1" + + async def test_landingpage_start( coresys: CoreSys, tmp_supervisor_data: Path, path_extern ): @@ -133,6 +156,7 @@ async def test_landingpage_start( "TZ": ANY, "SUPERVISOR_TOKEN": ANY, "HASSIO_TOKEN": ANY, + # no "HA_DUPLICATE_LOG_FILE" } assert run.call_args.kwargs["mounts"] == [ DEV_MOUNT,