"""Main application.""" from dataclasses import dataclass from typing import Any import sentry_sdk from datadog_api_client import Configuration from datadog_api_client.v2.model.state import State from lambdacommon.common_config import logger from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration import config from common.datadog_utils import ( list_services_with_details, update_scorecard_service_outcomes_batch, ) from src import constants from src.aggregate_logs import AggregateLogsConfig, aggregate_logs @dataclass(frozen=True) class AppConfig: """Application configuration values consumed by the handler.""" dd_api_key: str | None dd_app_key: str | None sentry_dsn: str | None environment: str scorecard_rule_id: str | None aggregate_logs_from: str aggregate_logs_to: str _SENTRY_INITIALIZED = False def load_app_config() -> AppConfig: """Load runtime configuration from the config module.""" return AppConfig( dd_api_key=config.DD_API_KEY, dd_app_key=config.DD_APP_KEY, sentry_dsn=config.SENTRY_DSN, environment=config.ENVIRONMENT, scorecard_rule_id=config.SCORECARD_RULE_ID, aggregate_logs_from=config.AGGREGATE_LOGS_FROM, aggregate_logs_to=config.AGGREGATE_LOGS_TO, ) def get_logger() -> Any: """Load the shared lambda logger lazily.""" return logger def init_sentry_if_configured(app_config: AppConfig) -> None: """Initialize sentry once when DSN is provided.""" global _SENTRY_INITIALIZED if _SENTRY_INITIALIZED or not app_config.sentry_dsn: return sentry_sdk.init( dsn=app_config.sentry_dsn, environment=app_config.environment, integrations=[AwsLambdaIntegration(timeout_warning=True)], ) _SENTRY_INITIALIZED = True def handler( _event: dict[str, Any], _context: Any, *, app_config: AppConfig | None = None, app_logger: Any | None = None, ) -> None: """Lambda handler.""" resolved_config = app_config or load_app_config() resolved_logger = app_logger or get_logger() init_sentry_if_configured(resolved_config) if not resolved_config.dd_api_key or not resolved_config.dd_app_key: resolved_logger.error("Missing DD_API_KEY or DD_APP_KEY") raise ValueError("Missing DD_API_KEY or DD_APP_KEY") resolved_logger.info("Fetching services from catalog...") services = list_services_with_details( resolved_config.dd_api_key, resolved_config.dd_app_key, ) resolved_logger.info(f"Found {len(services)} services.") # Convert list of services to dict of service names / outcomes services = {service["name"]: State.PASS for service in services} resolved_logger.info( "Aggregating logs by @apollographql-client-name / graphql-gateway", ) aggregates_by_facet = aggregate_logs( configuration=Configuration( api_key={ "apiKeyAuth": resolved_config.dd_api_key, "appKeyAuth": resolved_config.dd_app_key, } ), aggregate_config=AggregateLogsConfig( query=constants.LOG_QUERY, facet=constants.LOG_FACET, group_by_limit=constants.LOG_GROUP_BY_LIMIT, logs_from=resolved_config.aggregate_logs_from, logs_to=resolved_config.aggregate_logs_to, ), ) resolved_logger.info( f"Updating {len(aggregates_by_facet)} services with FAIL", ) for aggregate in aggregates_by_facet.keys(): if aggregate not in services: resolved_logger.info( f"{aggregate} is not in the scorecard rule", ) service_outcomes: list[dict[str, Any]] = [ { "rule_id": resolved_config.scorecard_rule_id, "state": State.FAIL if aggregates_by_facet.get(service) else State.PASS, "service_name": service, } for service in services.keys() ] update_scorecard_service_outcomes_batch( resolved_config.dd_api_key, resolved_config.dd_app_key, service_outcomes, ) if __name__ == "__main__": handler({}, {})