This is an automated email from the ASF dual-hosted git repository.

potiuk 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 8e4b7500e55 Explicitly initialize Stats in API server lifespan (#68514)
8e4b7500e55 is described below

commit 8e4b7500e55f823770ff77663dd1739e85fd329e
Author: Jens Scheffler <[email protected]>
AuthorDate: Sun Jun 14 02:01:53 2026 +0200

    Explicitly initialize Stats in API server lifespan (#68514)
---
 airflow-core/src/airflow/api_fastapi/app.py     | 24 ++++++++++++++
 airflow-core/tests/unit/api_fastapi/test_app.py | 44 +++++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/airflow-core/src/airflow/api_fastapi/app.py 
b/airflow-core/src/airflow/api_fastapi/app.py
index 8931840c880..a4dbc3c9b72 100644
--- a/airflow-core/src/airflow/api_fastapi/app.py
+++ b/airflow-core/src/airflow/api_fastapi/app.py
@@ -71,8 +71,32 @@ class _AuthManagerState:
     _lock = threading.Lock()
 
 
+def _initialize_api_server_stats() -> None:
+    """
+    Initialize the ``Stats`` singleton in the API server process.
+
+    Initialization is guarded so a metrics misconfiguration can never prevent 
the API server
+    from starting.
+    """
+    try:
+        from airflow._shared.observability.metrics import stats
+        from airflow.observability.metrics import stats_utils
+
+        stats.initialize(
+            factory=stats_utils.get_stats_factory(),
+            export_legacy_names=conf.getboolean("metrics", "legacy_names_on"),
+        )
+    except Exception:
+        log.warning(
+            "Failed to initialize API server Stats in the API server; metrics 
emitted through the "
+            "API server Stats singleton will not be recorded.",
+            exc_info=True,
+        )
+
+
 @asynccontextmanager
 async def lifespan(app: FastAPI):
+    _initialize_api_server_stats()
     async with AsyncExitStack() as stack:
         for route in app.routes:
             if isinstance(route, Mount) and isinstance(route.app, FastAPI):
diff --git a/airflow-core/tests/unit/api_fastapi/test_app.py 
b/airflow-core/tests/unit/api_fastapi/test_app.py
index 1e8817ef243..9fd9a3edad1 100644
--- a/airflow-core/tests/unit/api_fastapi/test_app.py
+++ b/airflow-core/tests/unit/api_fastapi/test_app.py
@@ -168,3 +168,47 @@ def test_create_auth_manager_thread_safety():
     assert call_count == 1
 
     app_module.purge_cached_app()
+
+
+class TestInitializeApiServerStats:
+    """
+    Ensure that stats subsystem is properly initialized in API server.
+    """
+
+    def test_initializes_api_server_stats_with_factory(self):
+        """It initializes the Stats singleton in the API server using the 
configured factory."""
+        sentinel_factory = object()
+        with (
+            mock.patch("airflow._shared.observability.metrics.stats") as 
mock_stats,
+            mock.patch(
+                "airflow.observability.metrics.stats_utils.get_stats_factory",
+                return_value=sentinel_factory,
+            ) as mock_get_factory,
+        ):
+            app_module._initialize_api_server_stats()
+
+            mock_get_factory.assert_called_once_with()
+            mock_stats.initialize.assert_called_once()
+            _, kwargs = mock_stats.initialize.call_args
+            assert kwargs["factory"] is sentinel_factory
+            assert isinstance(kwargs["export_legacy_names"], bool)
+
+    def test_stats_failure_does_not_block_startup(self):
+        """A metrics misconfiguration must not prevent the API server from 
starting."""
+        with (
+            mock.patch("airflow._shared.observability.metrics.stats") as 
mock_stats,
+            
mock.patch("airflow.observability.metrics.stats_utils.get_stats_factory"),
+            mock.patch("airflow.api_fastapi.app.log") as mock_logger,
+        ):
+            mock_stats.initialize.side_effect = RuntimeError("boom")
+
+            # Must not raise.
+            app_module._initialize_api_server_stats()
+
+        mock_logger.warning.assert_called_once()
+
+    def test_stats_initialized_during_lifespan(self, client):
+        """_initialize_api_server_stats must be called as part of the app 
lifespan, not just defined."""
+        with mock.patch.object(app_module, "_initialize_api_server_stats") as 
mock_init:
+            with client():
+                mock_init.assert_called_once()

Reply via email to