nailo2c opened a new pull request, #60384:
URL: https://github.com/apache/airflow/pull/60384

   closes: #56340 
   
   # Why
   
   The user configured a custom logging handler in Airflow by following this 
[doc](https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/advanced-logging-configuration.html).
   
   It works in a direct Python test, but it does not work when setting 
`logging_config_class`.
   
   # How
   
   It lacks the `load_logging_config` step in the `configure_logging` function; 
adding it resolves this issue.
   
https://github.com/apache/airflow/blob/67c95c3f80a07ac42e4c39c3aac6c31cb36417f8/task-sdk/src/airflow/sdk/log.py#L96
   
   # What
   
   ## Step 1: Setup
   
   Here are the settings I used to reproduce this issue locally:
   
   <details>
   <summary><code>files/airflow-breeze-config/environment_variables.env</code> 
<b>(Click to expand)</b></summary>
   
   ```console
   AIRFLOW__LOGGING__LOGGING_CONFIG_CLASS=log_config.LOGGING_CONFIG
   PYTHONPATH=/files/config${PYTHONPATH:+:$PYTHONPATH}
   ```
   
   </details>
   
   <details>
   <summary><code>files/config/clickhouse_logging.py</code> <b>(Click to 
expand)</b></summary>
   
   ```python
   import json
   import logging
   import os
   from pathlib import Path
   from threading import Lock
   from typing import Any
   
   DEFAULT_LOG_PATH = os.path.join(
       os.environ.get("AIRFLOW_HOME", "/opt/airflow"),
       "logs",
       "clickhouse_handler.log",
   )
   
   
   class ClickHouseHTTPHandler(logging.Handler):
       def __init__(self, log_path: str | None = None) -> None:
           super().__init__()
           self.log_path = log_path or DEFAULT_LOG_PATH
           Path(self.log_path).parent.mkdir(parents=True, exist_ok=True)
           self._lock = Lock()
   
       def emit(self, record: logging.LogRecord) -> None:
           try:
               payload: dict[str, Any] = {
                   "ts": record.created,
                   "logger": record.name,
                   "level": record.levelname,
                   "message": self.format(record),
               }
               line = json.dumps(payload, ensure_ascii=True)
               with self._lock:
                   with open(self.log_path, "a", encoding="utf-8") as handle:
                       handle.write(line + "\n")
           except Exception:
               self.handleError(record)
   ```
   
   </details>
   
   <details>
   <summary><code>files/config/log_config.py</code> <b>(Click to 
expand)</b></summary>
   
   ```python
   import os
   from copy import deepcopy
   
   from airflow.config_templates.airflow_local_settings import 
DEFAULT_LOGGING_CONFIG
   
   LOGGING_CONFIG = deepcopy(DEFAULT_LOGGING_CONFIG)
   REMOTE_TASK_LOG = None        # to prevent triggerer and dag-processor crash
   DEFAULT_REMOTE_CONN_ID = None # to prevent triggerer and dag-processor crash
   
   LOGGING_CONFIG["handlers"]["clickhouse"] = {
       "class": "clickhouse_logging.ClickHouseHTTPHandler",
       "log_path": os.environ.get(
           "AIRFLOW__CLICKHOUSE_LOG_PATH",
           os.path.join(os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "logs", 
"clickhouse_handler.log"),
       ),
       "level": "INFO",
   }
   
   task_handlers = list(LOGGING_CONFIG["loggers"]["airflow.task"]["handlers"])
   if "clickhouse" not in task_handlers:
       task_handlers.append("clickhouse")
   LOGGING_CONFIG["loggers"]["airflow.task"]["handlers"] = task_handlers
   ```
   
   </details>
   
   <details>
   <summary><code>files/dags/repro_logging_config_class.py</code> <b>(Click to 
expand)</b></summary>
   
   ```python
   import logging
   
   import pendulum
   
   from airflow import DAG
   from airflow.operators.python import PythonOperator
   
   
   def _describe_handlers(logger: logging.Logger) -> list[str]:
       descriptions = []
       for handler in logger.handlers:
           name = getattr(handler, "name", None)
           level = logging.getLevelName(handler.level)
           descriptions.append(
               
f"{handler.__class__.__module__}.{handler.__class__.__name__}(name={name}, 
level={level})"
           )
       return descriptions
   
   
   def emit_logs() -> None:
       logger = logging.getLogger("airflow.task")
       logger.info("airflow.task handlers: %s", _describe_handlers(logger))
       logger.info("airflow.task propagate=%s parent=%s", logger.propagate, 
getattr(logger.parent, "name", None))
       logger.info("stdlib logger message from repro task")
       print("stdout message from repro task")
   
   
   with DAG(
       dag_id="repro_logging_config_class",
       start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
       schedule=None,
       catchup=False,
       tags=["repro"],
   ) as dag:
       PythonOperator(task_id="emit_logs", python_callable=emit_logs)
   ```
   
   </details>
   
   ## Step 2: Before the fix - Reproducing the issue
   
   First, make sure the Airflow config has the correct value:
   ```console
   [Breeze:3.10.19] root@e09e71f55eb8:/opt/airflow/logs$ airflow config 
get-value logging logging_config_class
   log_config.LOGGING_CONFIG
   ```
   
   I ran this command in the container, and it wrote a line to 
`logs/clickhouse_handler.log` as expected:
   ```console
   PYTHONPATH=/files/config python - <<'PY'
   import logging
   from clickhouse_logging import ClickHouseHTTPHandler
   
   logger = logging.getLogger("airflow.task")
   logger.setLevel(logging.INFO)
   logger.addHandler(ClickHouseHTTPHandler())
   logger.info("direct python handler test")
   PY
   ```
   
   `logs/clickhouse_handler.log`
   ```console
   {"ts": 1768007319.4109492, "logger": "airflow.task", "level": "INFO", 
"message": "direct python handler test"}
   ```
   
   However, when I triggered the DAG, nothing was written to the log file, and 
the UI logs showed that the handler had not been loaded from the config.
   
   <img width="1913" height="505" alt="截圖 2026-01-10 下午9 24 50" 
src="https://github.com/user-attachments/assets/e0294fef-6b38-4309-ad45-1ca1f85e327c";
 />
   
   ## Step 3: After the fix
   
   After the fix, it works as expected and the config is loaded:
   
   <img width="1906" height="512" alt="截圖 2026-01-10 下午9 25 00" 
src="https://github.com/user-attachments/assets/223923c8-a7ac-4693-90bc-c90b77f4e9cc";
 />
   
   It also wrote logs to `logs/clickhouse_handler.log`:
   ```console
   {"ts": 1768084859.17813, "logger": "airflow.task", "level": "INFO", 
"message": "airflow.task handlers: 
['airflow.utils.log.file_task_handler.FileTaskHandler(name=task, 
level=NOTSET)', 'clickhouse_logging.ClickHouseHTTPHandler(name=clickhouse, 
level=INFO)']"}
   {"ts": 1768084859.1806972, "logger": "airflow.task", "level": "INFO", 
"message": "airflow.task propagate=True parent=airflow"}
   {"ts": 1768084859.1817467, "logger": "airflow.task", "level": "INFO", 
"message": "stdlib logger message from repro task"}
   ```
   
   
   ## Was generative AI tooling used to co-author this PR?
   - [X] Yes (please specify the tool below)
   Generated-by: gpt-5.2-codex following [the 
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to