Repository: kedro-org/kedro

Issue:
Make framework-side logging.yml user-specifiable
> **Note**. Non-breaking, goes in 0.18.x.

As explained in https://github.com/kedro-org/kedro/issues/2205, there's a portion of framework-side logging setup in `_ProjectLogging` that cannot be modified by users. It would solve several issues if we could come up with a way to make this code user-accessible:
- https://github.com/kedro-org/kedro/issues/2208
- https://github.com/kedro-org/kedro/issues/1930
- https://github.com/kedro-org/kedro/issues/1719
- https://github.com/kedro-org/kedro/issues/1733

The proposed solution is to introduce an environment variable `KEDRO_LOGGING_CONFIG` (name tbc) which allows a user to provide an path to a logging.yml configuration file instead of the framework `default_logging.yml`. A user could then run `export KEDRO_LOGGING_CONFIG=conf/base/logging.yml` for example. 

Docs will need to be updated with this.

The new code in `_ProjectLogging` would look something like this:
```
class _ProjectLogging(UserDict):
    # pylint: disable=super-init-not-called
    def __init__(self):
        """Initialise project logging. The path to logging configuration is given in
        environment variable KEDRO_LOGGING_CONFIG (defaults to default_logging.yml)."""
        path = os.environ.get("KEDRO_LOGGING_CONFIG", Path(__file__).parent / "default_logging.yml")
        logging_config = Path(path).read_text(encoding="utf-8")
        self.configure(yaml.safe_load(logging_config))
        ...
```


Discussion:

Resolving patch:
diff --git a/kedro/framework/project/__init__.py b/kedro/framework/project/__init__.py
--- a/kedro/framework/project/__init__.py
+++ b/kedro/framework/project/__init__.py
@@ -112,7 +112,6 @@ class _ProjectSettings(LazySettings):
     )
 
     def __init__(self, *args, **kwargs):
-
         kwargs.update(
             validators=[
                 self._CONF_SOURCE,
@@ -133,6 +132,7 @@ def _load_data_wrapper(func):
     """Wrap a method in _ProjectPipelines so that data is loaded on first access.
     Taking inspiration from dynaconf.utils.functional.new_method_proxy
     """
+
     # pylint: disable=protected-access
     def inner(self, *args, **kwargs):
         self._load_data()
@@ -212,12 +212,13 @@ def configure(self, pipelines_module: Optional[str] = None) -> None:
 class _ProjectLogging(UserDict):
     # pylint: disable=super-init-not-called
     def __init__(self):
-        """Initialise project logging with default configuration. Also enable
-        rich tracebacks."""
-        default_logging = (Path(__file__).parent / "default_logging.yml").read_text(
-            encoding="utf-8"
+        """Initialise project logging. The path to logging configuration is given in
+        environment variable KEDRO_LOGGING_CONFIG (defaults to default_logging.yml)."""
+        path = os.environ.get(
+            "KEDRO_LOGGING_CONFIG", Path(__file__).parent / "default_logging.yml"
         )
-        self.configure(yaml.safe_load(default_logging))
+        logging_config = Path(path).read_text(encoding="utf-8")
+        self.configure(yaml.safe_load(logging_config))
         logging.captureWarnings(True)
 
         # We suppress click here to hide tracebacks related to it conversely,


Test patch:
diff --git a/tests/framework/project/test_logging.py b/tests/framework/project/test_logging.py
--- a/tests/framework/project/test_logging.py
+++ b/tests/framework/project/test_logging.py
@@ -1,8 +1,10 @@
 # pylint: disable=import-outside-toplevel,unused-import,reimported
 import logging
 import sys
+from pathlib import Path
 
 import pytest
+import yaml
 
 from kedro.framework.project import LOGGING, configure_logging
 
@@ -27,6 +29,19 @@ def test_default_logging_config():
     assert logging.getLogger("kedro").level == logging.INFO
 
 
+def test_environment_variable_logging_config(monkeypatch, tmp_path):
+    config_path = Path(tmp_path) / "logging.yml"
+    monkeypatch.setenv("KEDRO_LOGGING_CONFIG", config_path.absolute())
+    logging_config = {"version": 1, "loggers": {"kedro": {"level": "WARNING"}}}
+    with config_path.open("w", encoding="utf-8") as f:
+        yaml.dump(logging_config, f)
+    del sys.modules["kedro.framework.project"]
+    from kedro.framework.project import LOGGING  # noqa
+
+    assert LOGGING.data == logging_config
+    assert logging.getLogger("kedro").level == logging.WARNING
+
+
 def test_configure_logging():
     logging_config = {"version": 1, "loggers": {"kedro": {"level": "WARNING"}}}
     configure_logging(logging_config)
diff --git a/tests/ipython/test_ipython.py b/tests/ipython/test_ipython.py
--- a/tests/ipython/test_ipython.py
+++ b/tests/ipython/test_ipython.py
@@ -205,7 +205,6 @@ def test_load_extension_not_in_kedro_project(self, mocker, caplog):
         assert expected_message in log_messages
 
     def test_load_extension_register_line_magic(self, mocker, ipython):
-
         mocker.patch("kedro.ipython._find_kedro_project")
         mock_reload_kedro = mocker.patch("kedro.ipython.reload_kedro")
         load_ipython_extension(ipython)