""" Loki URL resolution service. Uses cluster_id, env, deploy_type to obtain Loki URL from GS API. Loki URL is constructed from internalReadUri, token uses get_server_auth0_token(). """ from __future__ import annotations import logging import re from src.models.cloud_access import CloudAccessEntry from src.services.auth import get_server_auth0_token from src.services.loki import LokiClient from src.services.observability_resolve import get_or_resolve_metrics_url from src.utils.cloud_access_store import get_entry, set_entry logger = logging.getLogger(__name__) def _build_loki_url_from_internal_read_uri(internal_read_uri: str) -> tuple[str, str]: """ Construct Loki URL from internalReadUri and extract region from URL. internalReadUri format: https://www.ds.{region}.{vendor}.observability.tidbcloud.com/internal/v2/metrics/.../api/v1/ Loki URL format: https://www.ds.{region}.{vendor}.observability.tidbcloud.com/loki/self-monitoring Simply removes the trailing path and appends /loki/self-monitoring. Returns: (loki_url, region) where region is extracted from the host """ base = internal_read_uri if "/internal" in base: base = base.split("/internal", 1)[0] loki_url = f"{base}/loki/self-monitoring" region = _extract_region_from_url(loki_url) logger.debug(f"Built Loki URL: {loki_url} (region: {region})") return loki_url, region def _extract_region_from_url(url: str) -> str: """ Extract region from Loki URL. Example: https://www.ds.ap-southeast-2-ng.aws.observability.tidbcloud.com/loki/self-monitoring -> ap-southeast-2-ng """ match = re.search(r"\.ds\.([^.]+)\.", url) if match: return match.group(1) return "unknown" async def get_loki_client_for_cluster( cluster_id: str, env: str, deploy_type: str, timeout: float = 30.0, ) -> tuple[LokiClient, str]: """ Return Loki client for the cluster, using cached or resolved URL. Args: cluster_id: Cluster identifier env: Environment (prod, staging, dev) deploy_type: Deploy type (dedicated, premium, etc) timeout: Request timeout in seconds Returns: Tuple of (LokiClient, region) where region is extracted from the resolved Loki URL Raises: AuthServiceError: If observability URL is not set or resolve fails """ cluster_id = cluster_id.strip() env = env.strip().lower() deploy_type = deploy_type.strip().lower() cached_entry = get_entry( cluster_id=cluster_id, env=env, deploy_type=deploy_type, ) if cached_entry is not None and cached_entry.logs_url: logger.info(f"Using cached Loki URL for cluster {cluster_id}: {cached_entry.logs_url}") region = _extract_region_from_url(cached_entry.logs_url) return ( LokiClient( base_url=cached_entry.logs_url, timeout=timeout, token=await get_server_auth0_token(), ), region, ) resolved = await get_or_resolve_metrics_url( cluster_id=cluster_id, env=env, deploy_type=deploy_type, ) loki_url, region = _build_loki_url_from_internal_read_uri(resolved.url) logger.info(f"Resolved Loki URL for cluster {cluster_id}: {loki_url}") if cached_entry is not None: updated_entry = CloudAccessEntry( s3_access=cached_entry.s3_access, metrics_url=cached_entry.metrics_url, metrics_token=cached_entry.metrics_token, logs_url=loki_url, s3_url=cached_entry.s3_url, ) else: updated_entry = CloudAccessEntry( s3_access=None, metrics_url=None, metrics_token=None, logs_url=loki_url, s3_url=None, ) set_entry( cluster_id=cluster_id, env=env, deploy_type=deploy_type, entry=updated_entry, ) return ( LokiClient( base_url=loki_url, timeout=timeout, token=await get_server_auth0_token(), ), region, ) def add_env_to_query(query: str, env: str) -> str: """ Add cluster_env as a label filter to the query. Example: query = '{app="mysql"}', env = 'prod' -> '{app="mysql", cluster_env="prod"}' """ env_value = env.strip().lower() start_brace = query.find("{") end_brace = query.find("}") if start_brace == -1 or end_brace == -1: return f'{{cluster_env="{env_value}"}} {query}' labels = query[start_brace + 1:end_brace] if labels: if "cluster_env=" in labels.lower(): return query return '{%s, cluster_env="%s"}%s' % (labels, env_value, query[end_brace + 1:]) else: return '{cluster_env="%s"}%s' % (env_value, query[end_brace + 1:])