mcp-mesh 0.4.1__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _mcp_mesh/__init__.py +14 -3
- _mcp_mesh/engine/async_mcp_client.py +6 -19
- _mcp_mesh/engine/dependency_injector.py +161 -74
- _mcp_mesh/engine/full_mcp_proxy.py +25 -20
- _mcp_mesh/engine/mcp_client_proxy.py +5 -19
- _mcp_mesh/generated/.openapi-generator/FILES +2 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +2 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +1 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +305 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +1 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +10 -1
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +4 -4
- _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +108 -0
- _mcp_mesh/pipeline/__init__.py +2 -2
- _mcp_mesh/pipeline/api_heartbeat/__init__.py +16 -0
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +515 -0
- _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +117 -0
- _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +140 -0
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +247 -0
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +309 -0
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +332 -0
- _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +147 -0
- _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +97 -0
- _mcp_mesh/pipeline/api_startup/__init__.py +20 -0
- _mcp_mesh/pipeline/api_startup/api_pipeline.py +61 -0
- _mcp_mesh/pipeline/api_startup/api_server_setup.py +292 -0
- _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +302 -0
- _mcp_mesh/pipeline/api_startup/route_collection.py +56 -0
- _mcp_mesh/pipeline/api_startup/route_integration.py +318 -0
- _mcp_mesh/pipeline/{startup → mcp_startup}/fastmcpserver_discovery.py +4 -4
- _mcp_mesh/pipeline/{startup → mcp_startup}/heartbeat_loop.py +1 -1
- _mcp_mesh/pipeline/{startup → mcp_startup}/startup_orchestrator.py +170 -5
- _mcp_mesh/shared/config_resolver.py +0 -3
- _mcp_mesh/shared/logging_config.py +2 -1
- _mcp_mesh/shared/sse_parser.py +217 -0
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/METADATA +1 -1
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/RECORD +55 -37
- mesh/__init__.py +6 -2
- mesh/decorators.py +143 -1
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/__init__.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/dependency_resolution.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/fast_heartbeat_check.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/heartbeat_orchestrator.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/heartbeat_pipeline.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/heartbeat_send.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/lifespan_integration.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/registry_connection.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/__init__.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/configuration.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/decorator_collection.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/fastapiserver_setup.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/heartbeat_preparation.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/startup_pipeline.py +0 -0
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API health check step for API heartbeat pipeline.
|
|
3
|
+
|
|
4
|
+
Validates FastAPI application health status and endpoint availability
|
|
5
|
+
for heartbeat reporting to the registry.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from ..shared.base_step import PipelineStep
|
|
12
|
+
from ..shared.pipeline_types import PipelineResult
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class APIHealthCheckStep(PipelineStep):
|
|
18
|
+
"""
|
|
19
|
+
Check FastAPI application health status.
|
|
20
|
+
|
|
21
|
+
Validates that the FastAPI application is running properly
|
|
22
|
+
and endpoints are accessible for dependency injection.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, required: bool = True):
|
|
26
|
+
super().__init__(
|
|
27
|
+
name="api-health-check",
|
|
28
|
+
required=required,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
async def execute(self, context: dict[str, Any]) -> PipelineResult:
|
|
32
|
+
"""
|
|
33
|
+
Check FastAPI application health status.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
context: Pipeline context containing fastapi_app and service info
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
PipelineResult with health_status in context
|
|
40
|
+
"""
|
|
41
|
+
self.logger.info("🏥 [DEBUG] Checking FastAPI application health status")
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
# Get FastAPI app from context
|
|
45
|
+
fastapi_app = context.get("fastapi_app")
|
|
46
|
+
service_id = context.get("service_id") or context.get("agent_id", "unknown")
|
|
47
|
+
|
|
48
|
+
if not fastapi_app:
|
|
49
|
+
error_msg = "No FastAPI application found in context for health check"
|
|
50
|
+
self.logger.error(f"❌ {error_msg}")
|
|
51
|
+
|
|
52
|
+
from ..shared.pipeline_types import PipelineStatus
|
|
53
|
+
result = PipelineResult(
|
|
54
|
+
status=PipelineStatus.FAILED,
|
|
55
|
+
message=error_msg,
|
|
56
|
+
context=context
|
|
57
|
+
)
|
|
58
|
+
result.add_error(error_msg)
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
# Check FastAPI app basic properties
|
|
62
|
+
app_title = getattr(fastapi_app, "title", "Unknown API")
|
|
63
|
+
app_version = getattr(fastapi_app, "version", "1.0.0")
|
|
64
|
+
|
|
65
|
+
# Count available routes with dependency injection
|
|
66
|
+
routes_with_mesh = self._count_mesh_routes(fastapi_app)
|
|
67
|
+
total_routes = len(getattr(fastapi_app, "routes", []))
|
|
68
|
+
|
|
69
|
+
self.logger.debug(
|
|
70
|
+
f"🔍 FastAPI app health: {app_title} v{app_version}, "
|
|
71
|
+
f"{routes_with_mesh}/{total_routes} routes with mesh injection"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Build health status for API service
|
|
75
|
+
# For API services, we create a simplified health status dict instead of using
|
|
76
|
+
# the strict HealthStatus model which requires capabilities (designed for MCP agents)
|
|
77
|
+
from datetime import UTC, datetime
|
|
78
|
+
|
|
79
|
+
health_status_dict = {
|
|
80
|
+
"agent_name": service_id,
|
|
81
|
+
"status": "healthy",
|
|
82
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
83
|
+
"version": app_version,
|
|
84
|
+
"metadata": {
|
|
85
|
+
"service_type": "api",
|
|
86
|
+
"app_title": app_title,
|
|
87
|
+
"app_version": app_version,
|
|
88
|
+
"routes_total": total_routes,
|
|
89
|
+
"routes_with_mesh": routes_with_mesh,
|
|
90
|
+
"health_check_timestamp": datetime.now(UTC).isoformat(),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
self.logger.info(
|
|
95
|
+
f"🏥 API health check passed: {app_title} v{app_version} "
|
|
96
|
+
f"({routes_with_mesh} mesh routes)"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return PipelineResult(
|
|
100
|
+
message=f"API health check passed for {app_title}",
|
|
101
|
+
context={
|
|
102
|
+
"health_status": health_status_dict,
|
|
103
|
+
"app_title": app_title,
|
|
104
|
+
"app_version": app_version,
|
|
105
|
+
"routes_total": total_routes,
|
|
106
|
+
"routes_with_mesh": routes_with_mesh,
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
error_msg = f"API health check failed: {e}"
|
|
112
|
+
self.logger.error(f"❌ {error_msg}")
|
|
113
|
+
|
|
114
|
+
from ..shared.pipeline_types import PipelineStatus
|
|
115
|
+
result = PipelineResult(
|
|
116
|
+
status=PipelineStatus.FAILED,
|
|
117
|
+
message=error_msg,
|
|
118
|
+
context=context
|
|
119
|
+
)
|
|
120
|
+
result.add_error(str(e))
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
def _count_mesh_routes(self, fastapi_app: Any) -> int:
|
|
124
|
+
"""Count routes that have mesh dependency injection applied."""
|
|
125
|
+
try:
|
|
126
|
+
mesh_routes = 0
|
|
127
|
+
routes = getattr(fastapi_app, "routes", [])
|
|
128
|
+
|
|
129
|
+
for route in routes:
|
|
130
|
+
# Check if route has dependency injection wrapper
|
|
131
|
+
endpoint = getattr(route, "endpoint", None)
|
|
132
|
+
if endpoint and hasattr(endpoint, "__wrapped__"):
|
|
133
|
+
# This indicates our dependency injection wrapper
|
|
134
|
+
mesh_routes += 1
|
|
135
|
+
|
|
136
|
+
return mesh_routes
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
self.logger.warning(f"⚠️ Could not count mesh routes: {e}")
|
|
140
|
+
return 0
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API heartbeat orchestrator for managing periodic pipeline execution.
|
|
3
|
+
|
|
4
|
+
Provides a high-level interface for executing API heartbeat pipelines
|
|
5
|
+
with proper context management and error handling for FastAPI services.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from .api_heartbeat_pipeline import APIHeartbeatPipeline
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class APIHeartbeatOrchestrator:
|
|
19
|
+
"""
|
|
20
|
+
Orchestrates API heartbeat pipeline execution for periodic registry communication.
|
|
21
|
+
|
|
22
|
+
Manages the context preparation, pipeline execution, and result processing
|
|
23
|
+
for the periodic heartbeat cycle of FastAPI services using @mesh.route decorators.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.logger = logging.getLogger(f"{__name__}.APIHeartbeatOrchestrator")
|
|
28
|
+
self.pipeline = APIHeartbeatPipeline()
|
|
29
|
+
self._heartbeat_count = 0
|
|
30
|
+
|
|
31
|
+
async def execute_api_heartbeat(
|
|
32
|
+
self, service_id: str, context: dict[str, Any]
|
|
33
|
+
) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
Execute a complete API heartbeat cycle with comprehensive error handling.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
service_id: Service identifier for the FastAPI application
|
|
39
|
+
context: Full pipeline context from API startup
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
bool: True if heartbeat succeeded, False if failed
|
|
43
|
+
"""
|
|
44
|
+
self._heartbeat_count += 1
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Prepare heartbeat context with validation
|
|
48
|
+
heartbeat_context = self._prepare_api_heartbeat_context(service_id, context)
|
|
49
|
+
|
|
50
|
+
# Validate required context before proceeding
|
|
51
|
+
if not self._validate_api_heartbeat_context(heartbeat_context):
|
|
52
|
+
self.logger.error(
|
|
53
|
+
f"❌ API heartbeat #{self._heartbeat_count} failed: invalid context"
|
|
54
|
+
)
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
# Log heartbeat request details for debugging
|
|
58
|
+
self._log_api_heartbeat_request(heartbeat_context, self._heartbeat_count)
|
|
59
|
+
|
|
60
|
+
# Execute API heartbeat pipeline with timeout protection
|
|
61
|
+
self.logger.info(f"💓 Executing API heartbeat #{self._heartbeat_count} for service '{service_id}'")
|
|
62
|
+
|
|
63
|
+
# Add timeout to prevent hanging heartbeats (30 seconds max)
|
|
64
|
+
import asyncio
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
self.logger.debug("Starting API heartbeat pipeline execution")
|
|
68
|
+
result = await asyncio.wait_for(
|
|
69
|
+
self.pipeline.execute_api_heartbeat_cycle(heartbeat_context),
|
|
70
|
+
timeout=30.0,
|
|
71
|
+
)
|
|
72
|
+
if result.is_success():
|
|
73
|
+
self.logger.debug("✅ API heartbeat pipeline completed successfully")
|
|
74
|
+
else:
|
|
75
|
+
self.logger.error(f"❌ API heartbeat pipeline failed: {result.message}")
|
|
76
|
+
except TimeoutError:
|
|
77
|
+
self.logger.error(
|
|
78
|
+
f"❌ API heartbeat #{self._heartbeat_count} timed out after 30 seconds"
|
|
79
|
+
)
|
|
80
|
+
return False
|
|
81
|
+
except Exception as e:
|
|
82
|
+
self.logger.error(f"❌ [DEBUG] Pipeline execution exception: {e}")
|
|
83
|
+
import traceback
|
|
84
|
+
self.logger.error(f"❌ [DEBUG] Traceback: {traceback.format_exc()}")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
# Process results
|
|
88
|
+
success = self._process_api_heartbeat_result(
|
|
89
|
+
result, service_id, self._heartbeat_count
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Log periodic status updates
|
|
93
|
+
if self._heartbeat_count % 10 == 0:
|
|
94
|
+
elapsed_time = self._heartbeat_count * 5 # Using 5s interval (MeshDefaults.HEALTH_INTERVAL)
|
|
95
|
+
self.logger.info(
|
|
96
|
+
f"💓 API heartbeat #{self._heartbeat_count} for service '{service_id}' - "
|
|
97
|
+
f"running for {elapsed_time} seconds"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return success
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
# Log detailed error information for debugging
|
|
104
|
+
import traceback
|
|
105
|
+
|
|
106
|
+
self.logger.error(
|
|
107
|
+
f"❌ API heartbeat #{self._heartbeat_count} failed for service '{service_id}': {e}\n"
|
|
108
|
+
f"Traceback: {traceback.format_exc()}"
|
|
109
|
+
)
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def _prepare_api_heartbeat_context(
|
|
113
|
+
self, service_id: str, startup_context: dict[str, Any]
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
"""Prepare context for API heartbeat pipeline execution."""
|
|
116
|
+
|
|
117
|
+
# Get FastAPI app and other essential components from startup context
|
|
118
|
+
fastapi_app = startup_context.get("fastapi_app")
|
|
119
|
+
display_config = startup_context.get("display_config", {})
|
|
120
|
+
|
|
121
|
+
# Get API service metadata from startup context
|
|
122
|
+
api_service_metadata = startup_context.get("api_service_metadata", {})
|
|
123
|
+
self.logger.debug(f"🔍 Startup context has api_service_metadata: {len(api_service_metadata) > 0}")
|
|
124
|
+
if api_service_metadata:
|
|
125
|
+
capabilities = api_service_metadata.get("capabilities", [])
|
|
126
|
+
self.logger.debug(f"🔍 API service has {len(capabilities)} route capabilities")
|
|
127
|
+
|
|
128
|
+
# Build heartbeat-specific context
|
|
129
|
+
heartbeat_context = {
|
|
130
|
+
"service_id": service_id,
|
|
131
|
+
"agent_id": service_id, # For compatibility with registry calls
|
|
132
|
+
"fastapi_app": fastapi_app,
|
|
133
|
+
"display_config": display_config,
|
|
134
|
+
# Include registry and configuration from startup
|
|
135
|
+
"agent_config": startup_context.get("agent_config", {}),
|
|
136
|
+
"registration_data": startup_context.get("registration_data", {}),
|
|
137
|
+
"registry_wrapper": startup_context.get("registry_wrapper"),
|
|
138
|
+
# CRITICAL: Include API service metadata with route dependencies
|
|
139
|
+
"api_service_metadata": api_service_metadata,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return heartbeat_context
|
|
143
|
+
|
|
144
|
+
def _validate_api_heartbeat_context(self, heartbeat_context: dict[str, Any]) -> bool:
|
|
145
|
+
"""Validate that API heartbeat context has all required components."""
|
|
146
|
+
|
|
147
|
+
required_fields = ["service_id", "fastapi_app"]
|
|
148
|
+
|
|
149
|
+
for field in required_fields:
|
|
150
|
+
if field not in heartbeat_context or heartbeat_context[field] is None:
|
|
151
|
+
self.logger.error(
|
|
152
|
+
f"❌ API heartbeat context validation failed: missing '{field}'"
|
|
153
|
+
)
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
# Additional validation for FastAPI app
|
|
157
|
+
fastapi_app = heartbeat_context.get("fastapi_app")
|
|
158
|
+
if not hasattr(fastapi_app, "routes"):
|
|
159
|
+
self.logger.error(
|
|
160
|
+
"❌ API heartbeat context validation failed: invalid FastAPI app object"
|
|
161
|
+
)
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
def _log_api_heartbeat_request(
|
|
167
|
+
self, heartbeat_context: dict[str, Any], heartbeat_count: int
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Log API heartbeat request details for debugging."""
|
|
170
|
+
|
|
171
|
+
service_id = heartbeat_context.get("service_id", "unknown")
|
|
172
|
+
fastapi_app = heartbeat_context.get("fastapi_app")
|
|
173
|
+
display_config = heartbeat_context.get("display_config", {})
|
|
174
|
+
|
|
175
|
+
# Extract app information for logging
|
|
176
|
+
app_info = {}
|
|
177
|
+
if fastapi_app:
|
|
178
|
+
app_info = {
|
|
179
|
+
"title": getattr(fastapi_app, "title", "Unknown API"),
|
|
180
|
+
"version": getattr(fastapi_app, "version", "1.0.0"),
|
|
181
|
+
"routes_count": len(getattr(fastapi_app, "routes", [])),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Log heartbeat details
|
|
185
|
+
self.logger.debug(
|
|
186
|
+
f"🔍 API Heartbeat #{heartbeat_count} for '{service_id}': "
|
|
187
|
+
f"app={app_info}, display={display_config}"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def _process_api_heartbeat_result(
|
|
191
|
+
self, result: Any, service_id: str, heartbeat_count: int
|
|
192
|
+
) -> bool:
|
|
193
|
+
"""Process API heartbeat pipeline result and log appropriately."""
|
|
194
|
+
|
|
195
|
+
if result.is_success():
|
|
196
|
+
# Check for heartbeat response in result context
|
|
197
|
+
heartbeat_response = result.context.get("heartbeat_response")
|
|
198
|
+
heartbeat_success = result.context.get("heartbeat_success", False)
|
|
199
|
+
|
|
200
|
+
self.logger.debug(f"API heartbeat result - success: {heartbeat_success}")
|
|
201
|
+
|
|
202
|
+
# Check if heartbeat was skipped due to optimization
|
|
203
|
+
heartbeat_skipped = result.context.get("heartbeat_skipped", False)
|
|
204
|
+
skip_reason = result.context.get("skip_reason")
|
|
205
|
+
|
|
206
|
+
if heartbeat_success and heartbeat_response:
|
|
207
|
+
# Log response details for debugging
|
|
208
|
+
try:
|
|
209
|
+
response_json = json.dumps(
|
|
210
|
+
heartbeat_response, indent=2, default=str
|
|
211
|
+
)
|
|
212
|
+
self.logger.debug(
|
|
213
|
+
f"🔍 API heartbeat response #{heartbeat_count}:\n{response_json}"
|
|
214
|
+
)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
self.logger.debug(
|
|
217
|
+
f"🔍 API heartbeat response #{heartbeat_count}: {heartbeat_response} "
|
|
218
|
+
f"(json serialization failed: {e})"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
self.logger.info(
|
|
222
|
+
f"💚 API heartbeat #{heartbeat_count} sent successfully for service '{service_id}'"
|
|
223
|
+
)
|
|
224
|
+
return True
|
|
225
|
+
elif heartbeat_success and heartbeat_skipped:
|
|
226
|
+
# Heartbeat was skipped for optimization - this is success
|
|
227
|
+
self.logger.debug(
|
|
228
|
+
f"🚀 API heartbeat #{heartbeat_count} skipped for service '{service_id}' - {skip_reason}"
|
|
229
|
+
)
|
|
230
|
+
return True
|
|
231
|
+
else:
|
|
232
|
+
self.logger.warning(
|
|
233
|
+
f"💔 [UPDATED] API heartbeat #{heartbeat_count} failed for service '{service_id}' - "
|
|
234
|
+
f"no response or unsuccessful (heartbeat_success={heartbeat_success}, heartbeat_response={heartbeat_response})"
|
|
235
|
+
)
|
|
236
|
+
return False
|
|
237
|
+
else:
|
|
238
|
+
self.logger.warning(
|
|
239
|
+
f"💔 [UPDATED-PIPELINE] API heartbeat #{heartbeat_count} pipeline failed for service '{service_id}': {result.message}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Log detailed errors
|
|
243
|
+
if hasattr(result, 'errors') and result.errors:
|
|
244
|
+
for error in result.errors:
|
|
245
|
+
self.logger.warning(f" - API heartbeat error: {error}")
|
|
246
|
+
|
|
247
|
+
return False
|