This is an automated email from the ASF dual-hosted git repository.
amoghdesai pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 8b4d2e60c88 Move LoggingMixin to task sdk internal module (#58551)
8b4d2e60c88 is described below
commit 8b4d2e60c886c4a7966c3fb91769dbd268139312
Author: Amogh Desai <[email protected]>
AuthorDate: Tue Nov 25 18:06:27 2025 +0530
Move LoggingMixin to task sdk internal module (#58551)
---
.../logging-monitoring/logging-tasks.rst | 11 ++-
task-sdk/src/airflow/sdk/bases/hook.py | 2 +-
task-sdk/src/airflow/sdk/bases/notifier.py | 2 +-
.../sdk/definitions/_internal/logging_mixin.py | 89 ++++++++++++++++++++++
4 files changed, 98 insertions(+), 6 deletions(-)
diff --git
a/airflow-core/docs/administration-and-deployment/logging-monitoring/logging-tasks.rst
b/airflow-core/docs/administration-and-deployment/logging-monitoring/logging-tasks.rst
index 5ab451f29b6..2403a487f96 100644
---
a/airflow-core/docs/administration-and-deployment/logging-monitoring/logging-tasks.rst
+++
b/airflow-core/docs/administration-and-deployment/logging-monitoring/logging-tasks.rst
@@ -61,10 +61,13 @@ Airflow uses the standard Python `logging
<https://docs.python.org/3/library/log
write logs, and for the duration of a task, the root logger is configured to
write to the task's log.
Most operators will write logs to the task log automatically. This is because
they
-have a ``log`` logger that you can use to write to the task log.
-This logger is created and configured by
:class:`~airflow.utils.log.LoggingMixin` that all
-operators derive from. But also due to the root logger handling, any standard
logger (using default settings) that
-propagates logging to the root will also write to the task log.
+have a ``log`` property (of type :class:`~airflow.sdk.types.Logger`) that you
can use
+to write to the task log. This logger is automatically configured for all
operators
+derived from :class:`~airflow.sdk.BaseOperator`.
+
+Additionally, due to the root logger configuration during task execution, any
standard
+Python logger (using default settings) that propagates to the root logger will
also write to
+the task log.
So if you want to log to the task log from custom code of yours you can do any
of the following:
diff --git a/task-sdk/src/airflow/sdk/bases/hook.py
b/task-sdk/src/airflow/sdk/bases/hook.py
index 8aaba17ca78..9c70684fd80 100644
--- a/task-sdk/src/airflow/sdk/bases/hook.py
+++ b/task-sdk/src/airflow/sdk/bases/hook.py
@@ -19,7 +19,7 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
-from airflow.utils.log.logging_mixin import LoggingMixin
+from airflow.sdk.definitions._internal.logging_mixin import LoggingMixin
if TYPE_CHECKING:
from airflow.sdk.definitions.connection import Connection
diff --git a/task-sdk/src/airflow/sdk/bases/notifier.py
b/task-sdk/src/airflow/sdk/bases/notifier.py
index 7cfed6ae97e..73d6d5e41c1 100644
--- a/task-sdk/src/airflow/sdk/bases/notifier.py
+++ b/task-sdk/src/airflow/sdk/bases/notifier.py
@@ -20,9 +20,9 @@ from __future__ import annotations
from collections.abc import Generator, Sequence
from typing import TYPE_CHECKING
+from airflow.sdk.definitions._internal.logging_mixin import LoggingMixin
from airflow.sdk.definitions._internal.templater import Templater
from airflow.sdk.definitions.context import context_merge
-from airflow.utils.log.logging_mixin import LoggingMixin
if TYPE_CHECKING:
import jinja2
diff --git a/task-sdk/src/airflow/sdk/definitions/_internal/logging_mixin.py
b/task-sdk/src/airflow/sdk/definitions/_internal/logging_mixin.py
new file mode 100644
index 00000000000..f482dfede56
--- /dev/null
+++ b/task-sdk/src/airflow/sdk/definitions/_internal/logging_mixin.py
@@ -0,0 +1,89 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, TypeVar
+
+import structlog
+
+if TYPE_CHECKING:
+ from airflow.sdk.types import Logger
+
+_T = TypeVar("_T")
+
+
+class LoggingMixin:
+ """Convenience super-class to have a logger configured with the class
name."""
+
+ _log: Logger | None = None
+
+ # Parent logger used by this class. It should match one of the loggers
defined in the
+ # `logging_config_class`. By default, this attribute is used to create the
final name of the logger, and
+ # will prefix the `_logger_name` with a separating dot.
+ _log_config_logger_name: str | None = None
+
+ _logger_name: str | None = None
+
+ def __init__(self, context=None):
+ self._set_context(context)
+ super().__init__()
+
+ @staticmethod
+ def _create_logger_name(
+ logged_class: type[_T],
+ log_config_logger_name: str | None = None,
+ class_logger_name: str | None = None,
+ ) -> str:
+ """
+ Generate a logger name for the given `logged_class`.
+
+ By default, this function returns the `class_logger_name` as logger
name. If it is not provided,
+ the {class.__module__}.{class.__name__} is returned instead. When a
`parent_logger_name` is provided,
+ it will prefix the logger name with a separating dot.
+ """
+ logger_name: str = (
+ class_logger_name
+ if class_logger_name is not None
+ else f"{logged_class.__module__}.{logged_class.__name__}"
+ )
+
+ if log_config_logger_name:
+ return f"{log_config_logger_name}.{logger_name}" if logger_name
else log_config_logger_name
+ return logger_name
+
+ @classmethod
+ def _get_log(cls, obj: Any, clazz: type[_T]) -> Logger:
+ if obj._log is None:
+ logger_name: str = cls._create_logger_name(
+ logged_class=clazz,
+ log_config_logger_name=obj._log_config_logger_name,
+ class_logger_name=obj._logger_name,
+ )
+ obj._log = structlog.get_logger(logger_name)
+ return obj._log
+
+ @classmethod
+ def logger(cls) -> Logger:
+ """Return a logger."""
+ return LoggingMixin._get_log(cls, cls)
+
+ @property
+ def log(self) -> Logger:
+ """Return a logger."""
+ return LoggingMixin._get_log(self, self.__class__)
+
+ def _set_context(self, context): ...