1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-04 05:05:38 +01:00

Allow disabling managed log file (#170374)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
TheOtherAdam
2026-06-26 23:45:55 +08:00
committed by GitHub
parent 585dd72366
commit 393424fa88
5 changed files with 123 additions and 14 deletions
+36 -3
View File
@@ -66,6 +66,7 @@ from .const import (
BASE_PLATFORMS,
FORMAT_DATETIME,
KEY_DATA_LOGGING as DATA_LOGGING,
KEY_DATA_LOGGING_DISABLED_REASON as DATA_LOGGING_DISABLED_REASON,
SIGNAL_BOOTSTRAP_INTEGRATIONS,
)
from .core_config import async_process_ha_core_config
@@ -129,6 +130,11 @@ SETUP_ORDER_SORT_KEY = partial(contains, BASE_PLATFORMS)
ERROR_LOG_FILENAME = "home-assistant.log"
ENV_DISABLE_LOG_FILE = "HA_DISABLE_LOG_FILE"
ENV_DUPLICATE_LOG_FILE = "HA_DUPLICATE_LOG_FILE"
ENV_SUPERVISOR = "SUPERVISOR"
LOG_FILE_DISABLED_REASON_ENVIRONMENT = "environment"
LOG_FILE_DISABLED_REASON_SUPERVISOR = "supervisor"
# hass.data key for logging information.
DATA_REGISTRIES_LOADED: HassKey[None] = HassKey("bootstrap_registries_loaded")
@@ -642,10 +648,12 @@ async def async_enable_logging(
logger.setLevel(logging.INFO if verbose else logging.WARNING)
if log_file is None:
disabled_log_file_reason = _log_file_disabled_reason()
default_log_path = hass.config.path(ERROR_LOG_FILENAME)
if "SUPERVISOR" in os.environ and "HA_DUPLICATE_LOG_FILE" not in os.environ:
if disabled_log_file_reason:
# Rename the default log file if it exists, since previous versions created
# it even on Supervisor
# it before Supervisor disabled duplicate file logging or
# HA_DISABLE_LOG_FILE disabled the log file.
def rename_old_file() -> None:
"""Rename old log file in executor."""
if os.path.isfile(default_log_path):
@@ -657,6 +665,7 @@ async def async_enable_logging(
else:
err_log_path = default_log_path
else:
disabled_log_file_reason = None
err_log_path = os.path.abspath(log_file)
if err_log_path:
@@ -669,10 +678,34 @@ async def async_enable_logging(
# Save the log file location for access by other components.
hass.data[DATA_LOGGING] = err_log_path
elif disabled_log_file_reason == LOG_FILE_DISABLED_REASON_ENVIRONMENT:
hass.data[DATA_LOGGING_DISABLED_REASON] = disabled_log_file_reason
async_activate_log_queue_handler(hass)
def _log_file_disabled_reason() -> str | None:
"""Return why the log file is disabled."""
if ENV_SUPERVISOR in os.environ and ENV_DUPLICATE_LOG_FILE not in os.environ:
return LOG_FILE_DISABLED_REASON_SUPERVISOR
disable_log_file = os.environ.get(ENV_DISABLE_LOG_FILE)
if disable_log_file is None:
return None
try:
if cv.boolean(disable_log_file):
return LOG_FILE_DISABLED_REASON_ENVIRONMENT
except vol.Invalid:
_LOGGER.warning(
"Ignoring invalid %s value: %s. Expected a boolean value: "
"1/0, true/false, yes/no, on/off, or enable/disable",
ENV_DISABLE_LOG_FILE,
disable_log_file,
)
return None
def _create_log_file(
err_log_path: str, log_rotate_days: int | None
) -> RotatingFileHandler | TimedRotatingFileHandler:
@@ -734,7 +767,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
domains.update(DEFAULT_INTEGRATIONS_RECOVERY_MODE)
# Add domains depending on if the Supervisor is used or not
if "SUPERVISOR" in os.environ:
if ENV_SUPERVISOR in os.environ:
domains.update(DEFAULT_INTEGRATIONS_SUPERVISOR)
return domains
+2 -1
View File
@@ -1031,8 +1031,9 @@ SIGNAL_BOOTSTRAP_INTEGRATIONS: SignalType[dict[str, float]] = SignalType(
)
# hass.data key for logging information.
# hass.data keys for logging information.
KEY_DATA_LOGGING: HassKey[str] = HassKey("logging")
KEY_DATA_LOGGING_DISABLED_REASON: HassKey[str] = HassKey("logging_disabled_reason")
# Date/Time formats
+6
View File
@@ -50,6 +50,7 @@ from .const import (
CONF_URL,
CONF_USERNAME,
EVENT_CORE_CONFIG_UPDATE,
KEY_DATA_LOGGING_DISABLED_REASON,
LEGACY_CONF_WHITELIST_EXTERNAL_DIRS,
UnitOfLength,
__version__,
@@ -698,6 +699,11 @@ class Config:
"language": self.language,
"latitude": self.latitude,
"location_name": self.location_name,
"logging": {
"log_file_disabled_reason": self.hass.data.get(
KEY_DATA_LOGGING_DISABLED_REASON
),
},
"longitude": self.longitude,
"radius": self.radius,
"recovery_mode": self.recovery_mode,
+76 -10
View File
@@ -131,25 +131,78 @@ async def test_async_enable_logging(
@pytest.mark.parametrize(
("extra_env", "log_file_count", "old_log_file_count"),
[({}, 0, 1), ({"HA_DUPLICATE_LOG_FILE": "1"}, 1, 0)],
(
"env",
"log_file_count",
"old_log_file_count",
"data_logging",
"data_logging_disabled_reason",
),
[
pytest.param(
{"SUPERVISOR": "1"},
0,
1,
None,
None,
id="supervisor",
),
pytest.param(
{"SUPERVISOR": "1", "HA_DUPLICATE_LOG_FILE": "1"},
1,
0,
CONFIG_LOG_FILE,
None,
id="supervisor-duplicate-log-file",
),
pytest.param(
{"HA_DISABLE_LOG_FILE": "1"},
0,
1,
None,
"environment",
id="disable-log-file",
),
pytest.param(
{"HA_DISABLE_LOG_FILE": "0"},
1,
0,
CONFIG_LOG_FILE,
None,
id="disable-log-file-false",
),
pytest.param(
{"HA_DISABLE_LOG_FILE": "invalid"},
1,
0,
CONFIG_LOG_FILE,
None,
id="disable-log-file-invalid",
),
],
)
async def test_async_enable_logging_supervisor(
async def test_async_enable_logging_log_file_disable_control(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
extra_env: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
env: dict[str, str],
log_file_count: int,
old_log_file_count: int,
data_logging: str | None,
data_logging_disabled_reason: str | None,
) -> None:
"""Test the default log file is not created on Supervisor."""
"""Test the default log file disable controls."""
# Ensure we start with a clean slate
cleanup_log_files()
assert len(glob.glob(CONFIG_LOG_FILE)) == 0
assert len(glob.glob(ARG_LOG_FILE)) == 0
for env_var in ("SUPERVISOR", "HA_DUPLICATE_LOG_FILE", "HA_DISABLE_LOG_FILE"):
monkeypatch.delenv(env_var, raising=False)
for env_var, value in env.items():
monkeypatch.setenv(env_var, value)
with (
patch.dict(os.environ, {"SUPERVISOR": "1", **extra_env}),
patch(
"homeassistant.bootstrap.async_activate_log_queue_handler"
) as mock_async_activate_log_queue_handler,
@@ -157,11 +210,19 @@ async def test_async_enable_logging_supervisor(
):
await bootstrap.async_enable_logging(hass)
assert len(glob.glob(CONFIG_LOG_FILE)) == log_file_count
assert hass.data.get(bootstrap.DATA_LOGGING) == data_logging
assert (
hass.data.get(bootstrap.DATA_LOGGING_DISABLED_REASON)
== data_logging_disabled_reason
)
assert hass.config.as_dict()["logging"] == {
"log_file_disabled_reason": data_logging_disabled_reason,
}
mock_async_activate_log_queue_handler.assert_called_once()
mock_async_activate_log_queue_handler.reset_mock()
# Check that if the log file exists, it is renamed
def write_log_file():
def write_log_file() -> None:
with open(
get_test_config_dir("home-assistant.log"), "w", encoding="utf8"
) as f:
@@ -174,6 +235,11 @@ async def test_async_enable_logging_supervisor(
await bootstrap.async_enable_logging(hass)
assert len(glob.glob(CONFIG_LOG_FILE)) == log_file_count
assert len(glob.glob(f"{CONFIG_LOG_FILE}.old")) == old_log_file_count
assert hass.data.get(bootstrap.DATA_LOGGING) == data_logging
assert (
hass.data.get(bootstrap.DATA_LOGGING_DISABLED_REASON)
== data_logging_disabled_reason
)
mock_async_activate_log_queue_handler.assert_called_once()
mock_async_activate_log_queue_handler.reset_mock()
@@ -183,9 +249,9 @@ async def test_async_enable_logging_supervisor(
log_file="test.log",
)
mock_async_activate_log_queue_handler.assert_called_once()
# Even on Supervisor, the log file should be created
# if it is explicitly specified
# The log file should be created if it is explicitly specified.
assert len(glob.glob(ARG_LOG_FILE)) > 0
assert bootstrap.DATA_LOGGING in hass.data
cleanup_log_files()
+3
View File
@@ -928,6 +928,9 @@ async def test_config_as_dict() -> None:
"currency": "EUR",
"country": None,
"language": "en",
"logging": {
"log_file_disabled_reason": None,
},
"safe_mode": False,
"debug": False,
"radius": 100,