1#
2# Licensed to the Apache Software Foundation (ASF) under one
3# or more contributor license agreements. See the NOTICE file
4# distributed with this work for additional information
5# regarding copyright ownership. The ASF licenses this file
6# to you under the Apache License, Version 2.0 (the
7# "License"); you may not use this file except in compliance
8# with the License. You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing,
13# software distributed under the License is distributed on an
14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15# KIND, either express or implied. See the License for the
16# specific language governing permissions and limitations
17# under the License.
18from __future__ import annotations
19
20import logging
21import socket
22from collections.abc import Callable
23from typing import TYPE_CHECKING
24
25from .base_stats_logger import NoStatsLogger
26
27if TYPE_CHECKING:
28 from .base_stats_logger import StatsLogger
29
30log = logging.getLogger(__name__)
31
32
33class _Stats(type):
34 factory: Callable[[], StatsLogger | NoStatsLogger] | None = None
35 instance: StatsLogger | NoStatsLogger | None = None
36
37 def __getattr__(cls, name: str) -> str:
38 factory = type.__getattribute__(cls, "factory")
39 instance = type.__getattribute__(cls, "instance")
40
41 if instance is None:
42 if factory is None:
43 factory = NoStatsLogger
44 type.__setattr__(cls, "factory", factory)
45
46 try:
47 instance = factory()
48 except (socket.gaierror, ImportError) as e:
49 log.error("Could not configure StatsClient: %s, using NoStatsLogger instead.", e)
50 instance = NoStatsLogger()
51
52 type.__setattr__(cls, "instance", instance)
53
54 return getattr(instance, name)
55
56 def initialize(cls, *, is_statsd_datadog_enabled: bool, is_statsd_on: bool, is_otel_on: bool) -> None:
57 type.__setattr__(cls, "factory", None)
58 type.__setattr__(cls, "instance", None)
59
60 if is_statsd_datadog_enabled:
61 from airflow.observability.metrics import datadog_logger
62
63 # Datadog needs the cls param, so wrap it into a 0-arg factory.
64 factory = lambda: datadog_logger.get_dogstatsd_logger(cls)
65 elif is_statsd_on:
66 from airflow.observability.metrics import statsd_logger
67
68 factory = statsd_logger.get_statsd_logger
69 elif is_otel_on:
70 from airflow.observability.metrics import otel_logger
71
72 factory = otel_logger.get_otel_logger
73 else:
74 factory = NoStatsLogger
75
76 type.__setattr__(cls, "factory", factory)
77
78 @classmethod
79 def get_constant_tags(cls, *, tags_in_string: str | None) -> list[str]:
80 """Get constant DataDog tags to add to all stats."""
81 if not tags_in_string:
82 return []
83 return tags_in_string.split(",")
84
85
86if TYPE_CHECKING:
87 Stats: StatsLogger
88else:
89
90 class Stats(metaclass=_Stats):
91 """Empty class for Stats - we use metaclass to inject the right one."""