mcp-instana 0.1.0__py3-none-any.whl → 0.2.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_instana-0.2.0.dist-info/METADATA +1229 -0
- mcp_instana-0.2.0.dist-info/RECORD +59 -0
- {mcp_instana-0.1.0.dist-info → mcp_instana-0.2.0.dist-info}/WHEEL +1 -1
- mcp_instana-0.2.0.dist-info/entry_points.txt +4 -0
- mcp_instana-0.1.0.dist-info/LICENSE → mcp_instana-0.2.0.dist-info/licenses/LICENSE.md +3 -3
- src/application/__init__.py +1 -0
- src/{client/application_alert_config_mcp_tools.py → application/application_alert_config.py} +251 -273
- src/application/application_analyze.py +628 -0
- src/application/application_catalog.py +155 -0
- src/application/application_global_alert_config.py +653 -0
- src/{client/application_metrics_mcp_tools.py → application/application_metrics.py} +113 -131
- src/{client/application_resources_mcp_tools.py → application/application_resources.py} +131 -151
- src/application/application_settings.py +1731 -0
- src/application/application_topology.py +111 -0
- src/automation/action_catalog.py +416 -0
- src/automation/action_history.py +338 -0
- src/core/__init__.py +1 -0
- src/core/server.py +586 -0
- src/core/utils.py +213 -0
- src/event/__init__.py +1 -0
- src/event/events_tools.py +850 -0
- src/infrastructure/__init__.py +1 -0
- src/{client/infrastructure_analyze_mcp_tools.py → infrastructure/infrastructure_analyze.py} +207 -206
- src/{client/infrastructure_catalog_mcp_tools.py → infrastructure/infrastructure_catalog.py} +197 -265
- src/infrastructure/infrastructure_metrics.py +171 -0
- src/{client/infrastructure_resources_mcp_tools.py → infrastructure/infrastructure_resources.py} +198 -227
- src/{client/infrastructure_topology_mcp_tools.py → infrastructure/infrastructure_topology.py} +110 -109
- src/log/__init__.py +1 -0
- src/log/log_alert_configuration.py +331 -0
- src/prompts/__init__.py +16 -0
- src/prompts/application/__init__.py +1 -0
- src/prompts/application/application_alerts.py +54 -0
- src/prompts/application/application_catalog.py +26 -0
- src/prompts/application/application_metrics.py +57 -0
- src/prompts/application/application_resources.py +26 -0
- src/prompts/application/application_settings.py +75 -0
- src/prompts/application/application_topology.py +30 -0
- src/prompts/events/__init__.py +1 -0
- src/prompts/events/events_tools.py +161 -0
- src/prompts/infrastructure/infrastructure_analyze.py +72 -0
- src/prompts/infrastructure/infrastructure_catalog.py +53 -0
- src/prompts/infrastructure/infrastructure_metrics.py +45 -0
- src/prompts/infrastructure/infrastructure_resources.py +74 -0
- src/prompts/infrastructure/infrastructure_topology.py +38 -0
- src/prompts/settings/__init__.py +0 -0
- src/prompts/settings/custom_dashboard.py +157 -0
- src/prompts/website/__init__.py +1 -0
- src/prompts/website/website_analyze.py +35 -0
- src/prompts/website/website_catalog.py +40 -0
- src/prompts/website/website_configuration.py +105 -0
- src/prompts/website/website_metrics.py +34 -0
- src/settings/__init__.py +1 -0
- src/settings/custom_dashboard_tools.py +417 -0
- src/website/__init__.py +0 -0
- src/website/website_analyze.py +433 -0
- src/website/website_catalog.py +171 -0
- src/website/website_configuration.py +770 -0
- src/website/website_metrics.py +241 -0
- mcp_instana-0.1.0.dist-info/METADATA +0 -649
- mcp_instana-0.1.0.dist-info/RECORD +0 -19
- mcp_instana-0.1.0.dist-info/entry_points.txt +0 -3
- src/client/What is the sum of queue depth for all q +0 -55
- src/client/events_mcp_tools.py +0 -531
- src/client/instana_client_base.py +0 -93
- src/client/log_alert_configuration_mcp_tools.py +0 -316
- src/client/show the top 5 services with the highest +0 -28
- src/mcp_server.py +0 -343
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Agent Monitoring Events MCP Tools Module
|
|
4
|
+
|
|
5
|
+
This module provides agent monitoring events-specific MCP tools for Instana monitoring.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Any, Dict, List, Optional, Union
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from instana_client.api.events_api import (
|
|
16
|
+
EventsApi,
|
|
17
|
+
)
|
|
18
|
+
try:
|
|
19
|
+
has_get_events_id_query = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
has_get_events_id_query = False
|
|
22
|
+
except ImportError:
|
|
23
|
+
import logging
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
logger.error("Failed to import event resources API", exc_info=True)
|
|
26
|
+
raise
|
|
27
|
+
|
|
28
|
+
from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
33
|
+
|
|
34
|
+
def __init__(self, read_token: str, base_url: str):
|
|
35
|
+
super().__init__(read_token=read_token, base_url=base_url)
|
|
36
|
+
|
|
37
|
+
def _process_time_range(self, time_range=None, from_time=None, to_time=None):
|
|
38
|
+
"""
|
|
39
|
+
Process time range parameters to get standardized from_time and to_time values.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
time_range: Natural language time range like "last 24 hours"
|
|
43
|
+
from_time: Start timestamp in milliseconds (optional)
|
|
44
|
+
to_time: End timestamp in milliseconds (optional)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Tuple of (from_time, to_time) in milliseconds
|
|
48
|
+
"""
|
|
49
|
+
# Current time in milliseconds
|
|
50
|
+
current_time_ms = int(datetime.now().timestamp() * 1000)
|
|
51
|
+
|
|
52
|
+
# Process natural language time range if provided
|
|
53
|
+
if time_range:
|
|
54
|
+
logger.debug(f"Processing natural language time range: '{time_range}'")
|
|
55
|
+
|
|
56
|
+
# Default to 24 hours if just "last few hours" is specified
|
|
57
|
+
if time_range.lower() in ["last few hours", "last hours", "few hours"]:
|
|
58
|
+
hours = 24
|
|
59
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
60
|
+
to_time = current_time_ms
|
|
61
|
+
# Extract hours if specified
|
|
62
|
+
elif "hour" in time_range.lower():
|
|
63
|
+
hour_match = re.search(r'(\d+)\s*hour', time_range.lower())
|
|
64
|
+
hours = int(hour_match.group(1)) if hour_match else 24
|
|
65
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
66
|
+
to_time = current_time_ms
|
|
67
|
+
# Extract days if specified
|
|
68
|
+
elif "day" in time_range.lower():
|
|
69
|
+
day_match = re.search(r'(\d+)\s*day', time_range.lower())
|
|
70
|
+
days = int(day_match.group(1)) if day_match else 1
|
|
71
|
+
from_time = current_time_ms - (days * 24 * 60 * 60 * 1000)
|
|
72
|
+
to_time = current_time_ms
|
|
73
|
+
# Handle "last week"
|
|
74
|
+
elif "week" in time_range.lower():
|
|
75
|
+
week_match = re.search(r'(\d+)\s*week', time_range.lower())
|
|
76
|
+
weeks = int(week_match.group(1)) if week_match else 1
|
|
77
|
+
from_time = current_time_ms - (weeks * 7 * 24 * 60 * 60 * 1000)
|
|
78
|
+
to_time = current_time_ms
|
|
79
|
+
# Handle "last month"
|
|
80
|
+
elif "month" in time_range.lower():
|
|
81
|
+
month_match = re.search(r'(\d+)\s*month', time_range.lower())
|
|
82
|
+
months = int(month_match.group(1)) if month_match else 1
|
|
83
|
+
from_time = current_time_ms - (months * 30 * 24 * 60 * 60 * 1000)
|
|
84
|
+
to_time = current_time_ms
|
|
85
|
+
# Default to 24 hours for any other time range
|
|
86
|
+
else:
|
|
87
|
+
hours = 24
|
|
88
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
89
|
+
to_time = current_time_ms
|
|
90
|
+
|
|
91
|
+
# Set default time range if not provided
|
|
92
|
+
if not to_time:
|
|
93
|
+
to_time = current_time_ms
|
|
94
|
+
if not from_time:
|
|
95
|
+
from_time = to_time - (24 * 60 * 60 * 1000) # Default to 24 hours
|
|
96
|
+
|
|
97
|
+
return from_time, to_time
|
|
98
|
+
|
|
99
|
+
def _process_result(self, result):
|
|
100
|
+
|
|
101
|
+
# Convert the result to a dictionary
|
|
102
|
+
if hasattr(result, 'to_dict'):
|
|
103
|
+
result_dict = result.to_dict()
|
|
104
|
+
elif isinstance(result, list):
|
|
105
|
+
# Convert list items if they have to_dict method
|
|
106
|
+
items = []
|
|
107
|
+
for item in result:
|
|
108
|
+
if hasattr(item, 'to_dict'):
|
|
109
|
+
items.append(item.to_dict())
|
|
110
|
+
else:
|
|
111
|
+
items.append(item)
|
|
112
|
+
# Wrap list in a dictionary
|
|
113
|
+
result_dict = {"items": items, "count": len(items)}
|
|
114
|
+
elif isinstance(result, dict):
|
|
115
|
+
# If it's already a dict, use it as is
|
|
116
|
+
result_dict = result
|
|
117
|
+
else:
|
|
118
|
+
# For any other format, convert to string and wrap in dict
|
|
119
|
+
result_dict = {"data": str(result)}
|
|
120
|
+
|
|
121
|
+
return result_dict
|
|
122
|
+
|
|
123
|
+
def _summarize_events_result(self, events, total_count=None, max_events=None):
|
|
124
|
+
|
|
125
|
+
if not events:
|
|
126
|
+
return {"events_count": 0, "summary": "No events found"}
|
|
127
|
+
|
|
128
|
+
# Use provided total count or length of events list
|
|
129
|
+
total_events_count = total_count or len(events)
|
|
130
|
+
|
|
131
|
+
# Limit events if max_events is specified
|
|
132
|
+
if max_events and len(events) > max_events:
|
|
133
|
+
events = events[:max_events]
|
|
134
|
+
|
|
135
|
+
# Group events by type
|
|
136
|
+
event_types = {}
|
|
137
|
+
for event in events:
|
|
138
|
+
event_type = event.get("eventType", "Unknown")
|
|
139
|
+
if event_type not in event_types:
|
|
140
|
+
event_types[event_type] = 0
|
|
141
|
+
event_types[event_type] += 1
|
|
142
|
+
|
|
143
|
+
# Sort event types by count
|
|
144
|
+
sorted_types = sorted(event_types.items(), key=lambda x: x[1], reverse=True)
|
|
145
|
+
|
|
146
|
+
# Create summary
|
|
147
|
+
summary = {
|
|
148
|
+
"events_count": total_events_count,
|
|
149
|
+
"events_analyzed": len(events),
|
|
150
|
+
"event_types": dict(sorted_types),
|
|
151
|
+
"top_event_types": sorted_types[:5] if len(sorted_types) > 5 else sorted_types
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return summary
|
|
155
|
+
|
|
156
|
+
@register_as_tool
|
|
157
|
+
@with_header_auth(EventsApi)
|
|
158
|
+
async def get_event(self, event_id: str, ctx=None, api_client=None) -> Dict[str, Any]:
|
|
159
|
+
"""
|
|
160
|
+
Get a specific event by ID.
|
|
161
|
+
|
|
162
|
+
This tool retrieves detailed information about a specific event using its unique ID.
|
|
163
|
+
Use this when you need to examine a particular event's details, severity, or related entities.
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
Get details of a specific incident:
|
|
167
|
+
- event_id: "1a2b3c4d5e6f"
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
event_id: The ID of the event to retrieve
|
|
171
|
+
ctx: The MCP context (optional)
|
|
172
|
+
api_client: API client for testing (optional)
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Dictionary containing the event data or error information
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
logger.debug(f"get_event called with event_id={event_id}")
|
|
179
|
+
|
|
180
|
+
if not event_id:
|
|
181
|
+
return {"error": "event_id parameter is required"}
|
|
182
|
+
|
|
183
|
+
# Try standard API call first
|
|
184
|
+
try:
|
|
185
|
+
result = api_client.get_event(event_id=event_id)
|
|
186
|
+
|
|
187
|
+
# New robust conversion to dict
|
|
188
|
+
if hasattr(result, "to_dict"):
|
|
189
|
+
result_dict = result.to_dict()
|
|
190
|
+
elif isinstance(result, dict):
|
|
191
|
+
result_dict = result
|
|
192
|
+
else:
|
|
193
|
+
# Convert to dictionary using __dict__ or as a fallback, create a new dict with string representation
|
|
194
|
+
result_dict = getattr(result, "__dict__", {"data": str(result)})
|
|
195
|
+
|
|
196
|
+
logger.debug(f"Successfully retrieved event with ID {event_id}")
|
|
197
|
+
return result_dict
|
|
198
|
+
|
|
199
|
+
except Exception as api_error:
|
|
200
|
+
# Check for specific error types
|
|
201
|
+
if hasattr(api_error, 'status'):
|
|
202
|
+
if api_error.status == 404:
|
|
203
|
+
return {"error": f"Event with ID {event_id} not found", "event_id": event_id}
|
|
204
|
+
elif api_error.status in (401, 403):
|
|
205
|
+
return {"error": "Authentication failed. Please check your API token and permissions."}
|
|
206
|
+
|
|
207
|
+
# Try fallback approach
|
|
208
|
+
logger.warning(f"Standard API call failed: {api_error}, trying fallback approach")
|
|
209
|
+
|
|
210
|
+
# Use the without_preload_content version to get the raw response
|
|
211
|
+
try:
|
|
212
|
+
response_data = api_client.get_event_without_preload_content(event_id=event_id)
|
|
213
|
+
|
|
214
|
+
# Check if the response was successful
|
|
215
|
+
if response_data.status != 200:
|
|
216
|
+
error_message = f"Failed to get event: HTTP {response_data.status}"
|
|
217
|
+
logger.error(error_message)
|
|
218
|
+
return {"error": error_message, "event_id": event_id}
|
|
219
|
+
|
|
220
|
+
# Read the response content
|
|
221
|
+
response_text = response_data.data.decode('utf-8')
|
|
222
|
+
|
|
223
|
+
# Parse the JSON manually
|
|
224
|
+
try:
|
|
225
|
+
result_dict = json.loads(response_text)
|
|
226
|
+
logger.debug(f"Successfully retrieved event with ID {event_id} using fallback")
|
|
227
|
+
return result_dict
|
|
228
|
+
except json.JSONDecodeError as json_err:
|
|
229
|
+
error_message = f"Failed to parse JSON response: {json_err}"
|
|
230
|
+
logger.error(error_message)
|
|
231
|
+
return {"error": error_message, "event_id": event_id}
|
|
232
|
+
|
|
233
|
+
except Exception as fallback_error:
|
|
234
|
+
logger.error(f"Fallback approach failed: {fallback_error}")
|
|
235
|
+
raise
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.error(f"Error in get_event: {e}", exc_info=True)
|
|
239
|
+
return {"error": f"Failed to get event: {e!s}", "event_id": event_id}
|
|
240
|
+
|
|
241
|
+
@register_as_tool
|
|
242
|
+
@with_header_auth(EventsApi)
|
|
243
|
+
async def get_kubernetes_info_events(self,
|
|
244
|
+
from_time: Optional[int] = None,
|
|
245
|
+
to_time: Optional[int] = None,
|
|
246
|
+
time_range: Optional[str] = None,
|
|
247
|
+
max_events: Optional[int] = 50,
|
|
248
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
249
|
+
"""
|
|
250
|
+
Get Kubernetes info events based on the provided parameters and return a detailed analysis.
|
|
251
|
+
|
|
252
|
+
This tool retrieves Kubernetes events from Instana and provides a detailed analysis focusing on top problems,
|
|
253
|
+
their details, and actionable fix suggestions. You can specify a time range using timestamps or natural language
|
|
254
|
+
like "last 24 hours" or "last 2 days".
|
|
255
|
+
|
|
256
|
+
Examples:
|
|
257
|
+
Get Kubernetes events from the last 24 hours:
|
|
258
|
+
- time_range: "last 24 hours"
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
from_time: Start timestamp in milliseconds since epoch (optional)
|
|
262
|
+
to_time: End timestamp in milliseconds since epoch (optional)
|
|
263
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
264
|
+
max_events: Maximum number of events to process (default: 50)
|
|
265
|
+
ctx: The MCP context (optional)
|
|
266
|
+
api_client: API client for testing (optional)
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Dictionary containing detailed Kubernetes events analysis or error information
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
logger.debug(f"get_kubernetes_info_events called with time_range={time_range}, from_time={from_time}, to_time={to_time}, max_events={max_events}")
|
|
273
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
274
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
275
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
276
|
+
try:
|
|
277
|
+
result = api_client.kubernetes_info_events(
|
|
278
|
+
var_from=from_time,
|
|
279
|
+
to=to_time,
|
|
280
|
+
window_size=max_events,
|
|
281
|
+
filter_event_updates=None,
|
|
282
|
+
exclude_triggered_before=None
|
|
283
|
+
)
|
|
284
|
+
logger.debug(f"Raw API result type: {type(result)}")
|
|
285
|
+
logger.debug(f"Raw API result length: {len(result) if isinstance(result, list) else 'not a list'}")
|
|
286
|
+
except Exception as api_error:
|
|
287
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
288
|
+
return {
|
|
289
|
+
"error": f"Failed to get Kubernetes info events: {api_error}",
|
|
290
|
+
"details": str(api_error)
|
|
291
|
+
}
|
|
292
|
+
events = result if isinstance(result, list) else ([result] if result else [])
|
|
293
|
+
total_events_count = len(events)
|
|
294
|
+
events = events[:max_events]
|
|
295
|
+
event_dicts = []
|
|
296
|
+
for event in events:
|
|
297
|
+
if hasattr(event, 'to_dict'):
|
|
298
|
+
event_dicts.append(event.to_dict())
|
|
299
|
+
else:
|
|
300
|
+
event_dicts.append(event)
|
|
301
|
+
if not event_dicts:
|
|
302
|
+
return {
|
|
303
|
+
"events": [],
|
|
304
|
+
"events_count": 0,
|
|
305
|
+
"time_range": f"{from_date} to {to_date}",
|
|
306
|
+
"analysis": f"No Kubernetes events found between {from_date} and {to_date}."
|
|
307
|
+
}
|
|
308
|
+
problem_groups = {}
|
|
309
|
+
for event in event_dicts:
|
|
310
|
+
problem = event.get("problem", "Unknown")
|
|
311
|
+
if problem not in problem_groups:
|
|
312
|
+
problem_groups[problem] = {
|
|
313
|
+
"count": 0,
|
|
314
|
+
"affected_namespaces": set(),
|
|
315
|
+
"affected_entities": set(),
|
|
316
|
+
"details": set(),
|
|
317
|
+
"fix_suggestions": set(),
|
|
318
|
+
"sample_events": []
|
|
319
|
+
}
|
|
320
|
+
problem_groups[problem]["count"] += 1
|
|
321
|
+
entity_label = event.get("entityLabel", "")
|
|
322
|
+
if "/" in entity_label:
|
|
323
|
+
namespace, entity = entity_label.split("/", 1)
|
|
324
|
+
problem_groups[problem]["affected_namespaces"].add(namespace)
|
|
325
|
+
problem_groups[problem]["affected_entities"].add(entity)
|
|
326
|
+
detail = event.get("detail", "")
|
|
327
|
+
if detail:
|
|
328
|
+
problem_groups[problem]["details"].add(detail)
|
|
329
|
+
fix_suggestion = event.get("fixSuggestion", "")
|
|
330
|
+
if fix_suggestion:
|
|
331
|
+
problem_groups[problem]["fix_suggestions"].add(fix_suggestion)
|
|
332
|
+
if len(problem_groups[problem]["sample_events"]) < 3:
|
|
333
|
+
simple_event = {
|
|
334
|
+
"eventId": event.get("eventId", ""),
|
|
335
|
+
"start": event.get("start", 0),
|
|
336
|
+
"entityLabel": event.get("entityLabel", ""),
|
|
337
|
+
"detail": detail
|
|
338
|
+
}
|
|
339
|
+
problem_groups[problem]["sample_events"].append(simple_event)
|
|
340
|
+
sorted_problems = sorted(problem_groups.items(), key=lambda x: x[1]["count"], reverse=True)
|
|
341
|
+
problem_analyses = []
|
|
342
|
+
for problem_name, problem_data in sorted_problems:
|
|
343
|
+
problem_analysis = {
|
|
344
|
+
"problem": problem_name,
|
|
345
|
+
"count": problem_data["count"],
|
|
346
|
+
"affected_namespaces": list(problem_data["affected_namespaces"]),
|
|
347
|
+
"details": list(problem_data["details"]),
|
|
348
|
+
"fix_suggestions": list(problem_data["fix_suggestions"]),
|
|
349
|
+
"sample_events": problem_data["sample_events"]
|
|
350
|
+
}
|
|
351
|
+
problem_analyses.append(problem_analysis)
|
|
352
|
+
analysis_result = {
|
|
353
|
+
"summary": f"Analysis based on {len(events)} of {total_events_count} Kubernetes events between {from_date} and {to_date}.",
|
|
354
|
+
"time_range": f"{from_date} to {to_date}",
|
|
355
|
+
"events_count": total_events_count,
|
|
356
|
+
"events_analyzed": len(events),
|
|
357
|
+
"problem_analyses": problem_analyses[:10]
|
|
358
|
+
}
|
|
359
|
+
markdown_summary = "# Kubernetes Events Analysis\n\n"
|
|
360
|
+
markdown_summary += f"Analysis based on {len(events)} of {total_events_count} Kubernetes events between {from_date} and {to_date}.\n\n"
|
|
361
|
+
markdown_summary += "## Top Problems\n\n"
|
|
362
|
+
for problem_analysis in problem_analyses[:5]:
|
|
363
|
+
problem_name = problem_analysis["problem"]
|
|
364
|
+
count = problem_analysis["count"]
|
|
365
|
+
markdown_summary += f"### {problem_name} ({count} events)\n\n"
|
|
366
|
+
if problem_analysis.get("affected_namespaces"):
|
|
367
|
+
namespaces = ", ".join(problem_analysis["affected_namespaces"][:5])
|
|
368
|
+
if len(problem_analysis["affected_namespaces"]) > 5:
|
|
369
|
+
namespaces += f" and {len(problem_analysis['affected_namespaces']) - 5} more"
|
|
370
|
+
markdown_summary += f"**Affected Namespaces:** {namespaces}\n\n"
|
|
371
|
+
if problem_analysis.get("fix_suggestions"):
|
|
372
|
+
markdown_summary += "**Fix Suggestions:**\n\n"
|
|
373
|
+
for suggestion in list(problem_analysis["fix_suggestions"])[:3]:
|
|
374
|
+
markdown_summary += f"- {suggestion}\n"
|
|
375
|
+
markdown_summary += "\n"
|
|
376
|
+
analysis_result["markdown_summary"] = markdown_summary
|
|
377
|
+
analysis_result["events"] = event_dicts
|
|
378
|
+
return analysis_result
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.error(f"Error in get_kubernetes_info_events: {e}", exc_info=True)
|
|
381
|
+
return {
|
|
382
|
+
"error": f"Failed to get Kubernetes info events: {e!s}",
|
|
383
|
+
"details": str(e)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
@register_as_tool
|
|
387
|
+
@with_header_auth(EventsApi)
|
|
388
|
+
async def get_agent_monitoring_events(self,
|
|
389
|
+
query: Optional[str] = None,
|
|
390
|
+
from_time: Optional[int] = None,
|
|
391
|
+
to_time: Optional[int] = None,
|
|
392
|
+
size: Optional[int] = 100,
|
|
393
|
+
max_events: Optional[int] = 50,
|
|
394
|
+
time_range: Optional[str] = None,
|
|
395
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
396
|
+
"""
|
|
397
|
+
Get agent monitoring events from Instana and return a detailed analysis.
|
|
398
|
+
|
|
399
|
+
This tool retrieves agent monitoring events from Instana and provides a detailed analysis focusing on
|
|
400
|
+
monitoring issues, their frequency, and affected entities. You can specify a time range using timestamps
|
|
401
|
+
or natural language like "last 24 hours" or "last 2 days".
|
|
402
|
+
|
|
403
|
+
Examples:
|
|
404
|
+
Get agent monitoring events from the last 24 hours:
|
|
405
|
+
- time_range: "last 24 hours"
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
query: Query string to filter events (optional)
|
|
409
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
410
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
411
|
+
size: Maximum number of events to return from API (optional, default 100)
|
|
412
|
+
max_events: Maximum number of events to process for analysis (optional, default 50)
|
|
413
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
414
|
+
ctx: The MCP context (optional)
|
|
415
|
+
api_client: API client for testing (optional)
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Dictionary containing summarized agent monitoring events data or error information
|
|
419
|
+
"""
|
|
420
|
+
try:
|
|
421
|
+
logger.debug(f"get_agent_monitoring_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
422
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
423
|
+
if not from_time:
|
|
424
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
425
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
426
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
427
|
+
try:
|
|
428
|
+
result = api_client.agent_monitoring_events(
|
|
429
|
+
var_from=from_time,
|
|
430
|
+
to=to_time,
|
|
431
|
+
window_size=max_events,
|
|
432
|
+
filter_event_updates=None,
|
|
433
|
+
exclude_triggered_before=None
|
|
434
|
+
)
|
|
435
|
+
logger.debug(f"Raw API result type: {type(result)}")
|
|
436
|
+
logger.debug(f"Raw API result length: {len(result) if isinstance(result, list) else 'not a list'}")
|
|
437
|
+
except Exception as api_error:
|
|
438
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
439
|
+
return {
|
|
440
|
+
"error": f"Failed to get agent monitoring events: {api_error}",
|
|
441
|
+
"details": str(api_error)
|
|
442
|
+
}
|
|
443
|
+
events = result if isinstance(result, list) else ([result] if result else [])
|
|
444
|
+
total_events_count = len(events)
|
|
445
|
+
events = events[:max_events]
|
|
446
|
+
event_dicts = []
|
|
447
|
+
for event in events:
|
|
448
|
+
if hasattr(event, 'to_dict'):
|
|
449
|
+
event_dicts.append(event.to_dict())
|
|
450
|
+
else:
|
|
451
|
+
event_dicts.append(event)
|
|
452
|
+
if not event_dicts:
|
|
453
|
+
return {
|
|
454
|
+
"events": [],
|
|
455
|
+
"events_count": 0,
|
|
456
|
+
"time_range": f"{from_date} to {to_date}",
|
|
457
|
+
"analysis": f"No agent monitoring events found between {from_date} and {to_date}."
|
|
458
|
+
}
|
|
459
|
+
problem_groups = {}
|
|
460
|
+
for event in event_dicts:
|
|
461
|
+
full_problem = event.get("problem", "Unknown")
|
|
462
|
+
problem = full_problem.replace("Monitoring issue: ", "") if "Monitoring issue: " in full_problem else full_problem
|
|
463
|
+
if problem not in problem_groups:
|
|
464
|
+
problem_groups[problem] = {
|
|
465
|
+
"count": 0,
|
|
466
|
+
"affected_entities": set(),
|
|
467
|
+
"entity_types": set(),
|
|
468
|
+
"sample_events": []
|
|
469
|
+
}
|
|
470
|
+
problem_groups[problem]["count"] += 1
|
|
471
|
+
entity_name = event.get("entityName", "Unknown")
|
|
472
|
+
entity_label = event.get("entityLabel", "Unknown")
|
|
473
|
+
entity_type = event.get("entityType", "Unknown")
|
|
474
|
+
entity_info = f"{entity_name} ({entity_label})"
|
|
475
|
+
problem_groups[problem]["affected_entities"].add(entity_info)
|
|
476
|
+
problem_groups[problem]["entity_types"].add(entity_type)
|
|
477
|
+
if len(problem_groups[problem]["sample_events"]) < 3:
|
|
478
|
+
simple_event = {
|
|
479
|
+
"eventId": event.get("eventId", ""),
|
|
480
|
+
"start": event.get("start", 0),
|
|
481
|
+
"entityName": entity_name,
|
|
482
|
+
"entityLabel": entity_label,
|
|
483
|
+
"severity": event.get("severity", 0)
|
|
484
|
+
}
|
|
485
|
+
problem_groups[problem]["sample_events"].append(simple_event)
|
|
486
|
+
sorted_problems = sorted(problem_groups.items(), key=lambda x: x[1]["count"], reverse=True)
|
|
487
|
+
problem_analyses = []
|
|
488
|
+
for problem_name, problem_data in sorted_problems:
|
|
489
|
+
problem_analysis = {
|
|
490
|
+
"problem": problem_name,
|
|
491
|
+
"count": problem_data["count"],
|
|
492
|
+
"affected_entities": list(problem_data["affected_entities"]),
|
|
493
|
+
"entity_types": list(problem_data["entity_types"]),
|
|
494
|
+
"sample_events": problem_data["sample_events"]
|
|
495
|
+
}
|
|
496
|
+
problem_analyses.append(problem_analysis)
|
|
497
|
+
analysis_result = {
|
|
498
|
+
"summary": f"Analysis based on {len(events)} of {total_events_count} agent monitoring events between {from_date} and {to_date}.",
|
|
499
|
+
"time_range": f"{from_date} to {to_date}",
|
|
500
|
+
"events_count": total_events_count,
|
|
501
|
+
"events_analyzed": len(events),
|
|
502
|
+
"problem_analyses": problem_analyses[:10]
|
|
503
|
+
}
|
|
504
|
+
markdown_summary = "# Agent Monitoring Events Analysis\n\n"
|
|
505
|
+
markdown_summary += f"Analysis based on {len(events)} of {total_events_count} agent monitoring events between {from_date} and {to_date}.\n\n"
|
|
506
|
+
markdown_summary += "## Top Monitoring Issues\n\n"
|
|
507
|
+
for problem_analysis in problem_analyses[:5]:
|
|
508
|
+
problem_name = problem_analysis["problem"]
|
|
509
|
+
count = problem_analysis["count"]
|
|
510
|
+
markdown_summary += f"### {problem_name} ({count} events)\n\n"
|
|
511
|
+
if problem_analysis.get("affected_entities"):
|
|
512
|
+
entities = ", ".join(problem_analysis["affected_entities"][:5])
|
|
513
|
+
if len(problem_analysis["affected_entities"]) > 5:
|
|
514
|
+
entities += f" and {len(problem_analysis['affected_entities']) - 5} more"
|
|
515
|
+
markdown_summary += f"**Affected Entities:** {entities}\n\n"
|
|
516
|
+
markdown_summary += "\n"
|
|
517
|
+
analysis_result["markdown_summary"] = markdown_summary
|
|
518
|
+
analysis_result["events"] = event_dicts
|
|
519
|
+
return analysis_result
|
|
520
|
+
except Exception as e:
|
|
521
|
+
logger.error(f"Error in get_agent_monitoring_events: {e}", exc_info=True)
|
|
522
|
+
return {
|
|
523
|
+
"error": f"Failed to get agent monitoring events: {e!s}",
|
|
524
|
+
"details": str(e)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
@register_as_tool
|
|
529
|
+
@with_header_auth(EventsApi)
|
|
530
|
+
async def get_issues(self,
|
|
531
|
+
query: Optional[str] = None,
|
|
532
|
+
from_time: Optional[int] = None,
|
|
533
|
+
to_time: Optional[int] = None,
|
|
534
|
+
filter_event_updates: Optional[bool] = None,
|
|
535
|
+
exclude_triggered_before: Optional[int] = None,
|
|
536
|
+
max_events: Optional[int] = 50,
|
|
537
|
+
size: Optional[int] = 100,
|
|
538
|
+
time_range: Optional[str] = None,
|
|
539
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
540
|
+
"""
|
|
541
|
+
Get issue events from Instana based on the provided parameters.
|
|
542
|
+
|
|
543
|
+
This tool retrieves issue events from Instana based on specified filters and time range.
|
|
544
|
+
Issues are events that represent problems that need attention but are not critical.
|
|
545
|
+
|
|
546
|
+
Examples:
|
|
547
|
+
Get all issue events from the last 24 hours:
|
|
548
|
+
- time_range: "last 24 hours"
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
query: Query string to filter events (optional)
|
|
552
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
553
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
554
|
+
filter_event_updates: Whether to filter event updates (optional)
|
|
555
|
+
exclude_triggered_before: Exclude events triggered before this timestamp (optional)
|
|
556
|
+
max_events: Maximum number of events to process (default: 50)
|
|
557
|
+
size: Maximum number of events to return from API (default: 100)
|
|
558
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
559
|
+
ctx: The MCP context (optional)
|
|
560
|
+
api_client: API client for testing (optional)
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Dictionary containing the list of issue events or error information
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
try:
|
|
567
|
+
logger.debug(f"get_issue_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
568
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
569
|
+
if not from_time:
|
|
570
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
571
|
+
try:
|
|
572
|
+
response_data = api_client.get_events_without_preload_content(
|
|
573
|
+
var_from=from_time,
|
|
574
|
+
to=to_time,
|
|
575
|
+
window_size=size,
|
|
576
|
+
filter_event_updates=filter_event_updates,
|
|
577
|
+
exclude_triggered_before=exclude_triggered_before,
|
|
578
|
+
event_type_filters=["issue"]
|
|
579
|
+
)
|
|
580
|
+
if response_data.status != 200:
|
|
581
|
+
return {"error": f"Failed to get issue events: HTTP {response_data.status}"}
|
|
582
|
+
response_text = response_data.data.decode('utf-8')
|
|
583
|
+
result = json.loads(response_text)
|
|
584
|
+
if isinstance(result, list):
|
|
585
|
+
result_dict = {"events": result, "events_count": len(result)}
|
|
586
|
+
else:
|
|
587
|
+
result_dict = result
|
|
588
|
+
return result_dict
|
|
589
|
+
except Exception as api_error:
|
|
590
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
591
|
+
return {"error": f"Failed to get issue events: {api_error}"}
|
|
592
|
+
except Exception as e:
|
|
593
|
+
logger.error(f"Error in get_issue_events: {e}", exc_info=True)
|
|
594
|
+
return {"error": f"Failed to get issue events: {e!s}"}
|
|
595
|
+
|
|
596
|
+
@register_as_tool
|
|
597
|
+
@with_header_auth(EventsApi)
|
|
598
|
+
async def get_incidents(self,
|
|
599
|
+
query: Optional[str] = None,
|
|
600
|
+
from_time: Optional[int] = None,
|
|
601
|
+
to_time: Optional[int] = None,
|
|
602
|
+
filter_event_updates: Optional[bool] = None,
|
|
603
|
+
exclude_triggered_before: Optional[int] = None,
|
|
604
|
+
max_events: Optional[int] = 50,
|
|
605
|
+
size: Optional[int] = 100,
|
|
606
|
+
time_range: Optional[str] = None,
|
|
607
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
608
|
+
"""
|
|
609
|
+
Get incident events from Instana based on the provided parameters.
|
|
610
|
+
|
|
611
|
+
This tool retrieves incident events from Instana based on specified filters and time range.
|
|
612
|
+
Incidents are critical events that require immediate attention.
|
|
613
|
+
|
|
614
|
+
Examples:
|
|
615
|
+
Get all incident events from the last 24 hours:
|
|
616
|
+
- time_range: "last 24 hours"
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
query: Query string to filter events (optional)
|
|
620
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
621
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
622
|
+
filter_event_updates: Whether to filter event updates (optional)
|
|
623
|
+
exclude_triggered_before: Exclude events triggered before this timestamp (optional)
|
|
624
|
+
max_events: Maximum number of events to process (default: 50)
|
|
625
|
+
size: Maximum number of events to return from API (default: 100)
|
|
626
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
627
|
+
ctx: The MCP context (optional)
|
|
628
|
+
api_client: API client for testing (optional)
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Dictionary containing the list of incident events or error information
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
try:
|
|
635
|
+
logger.debug(f"get_incident_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
636
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
637
|
+
if not from_time:
|
|
638
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
639
|
+
try:
|
|
640
|
+
response_data = api_client.get_events_without_preload_content(
|
|
641
|
+
var_from=from_time,
|
|
642
|
+
to=to_time,
|
|
643
|
+
window_size=size,
|
|
644
|
+
filter_event_updates=filter_event_updates,
|
|
645
|
+
exclude_triggered_before=exclude_triggered_before,
|
|
646
|
+
event_type_filters=["incident"]
|
|
647
|
+
)
|
|
648
|
+
if response_data.status != 200:
|
|
649
|
+
return {"error": f"Failed to get incident events: HTTP {response_data.status}"}
|
|
650
|
+
response_text = response_data.data.decode('utf-8')
|
|
651
|
+
result = json.loads(response_text)
|
|
652
|
+
if isinstance(result, list):
|
|
653
|
+
result_dict = {"events": result, "events_count": len(result)}
|
|
654
|
+
else:
|
|
655
|
+
result_dict = result
|
|
656
|
+
return result_dict
|
|
657
|
+
except Exception as api_error:
|
|
658
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
659
|
+
return {"error": f"Failed to get incident events: {api_error}"}
|
|
660
|
+
except Exception as e:
|
|
661
|
+
logger.error(f"Error in get_incident_events: {e}", exc_info=True)
|
|
662
|
+
return {"error": f"Failed to get incident events: {e!s}"}
|
|
663
|
+
|
|
664
|
+
@register_as_tool
|
|
665
|
+
@with_header_auth(EventsApi)
|
|
666
|
+
async def get_changes(self,
|
|
667
|
+
query: Optional[str] = None,
|
|
668
|
+
from_time: Optional[int] = None,
|
|
669
|
+
to_time: Optional[int] = None,
|
|
670
|
+
filter_event_updates: Optional[bool] = None,
|
|
671
|
+
exclude_triggered_before: Optional[int] = None,
|
|
672
|
+
max_events: Optional[int] = 50,
|
|
673
|
+
size: Optional[int] = 100,
|
|
674
|
+
time_range: Optional[str] = None,
|
|
675
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
676
|
+
"""
|
|
677
|
+
Get change events from Instana based on the provided parameters.
|
|
678
|
+
|
|
679
|
+
This tool retrieves change events from Instana based on specified filters and time range.
|
|
680
|
+
Change events represent modifications to the system, such as deployments or configuration changes.
|
|
681
|
+
|
|
682
|
+
Examples:
|
|
683
|
+
Get all change events from the last 24 hours:
|
|
684
|
+
- time_range: "last 24 hours"
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
query: Query string to filter events (optional)
|
|
688
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
689
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
690
|
+
filter_event_updates: Whether to filter event updates (optional)
|
|
691
|
+
exclude_triggered_before: Exclude events triggered before this timestamp (optional)
|
|
692
|
+
max_events: Maximum number of events to process (default: 50)
|
|
693
|
+
size: Maximum number of events to return from API (default: 100)
|
|
694
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
695
|
+
ctx: The MCP context (optional)
|
|
696
|
+
api_client: API client for testing (optional)
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
Dictionary containing the list of change events or error information
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
try:
|
|
703
|
+
logger.debug(f"get_change_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
704
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
705
|
+
if not from_time:
|
|
706
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
707
|
+
try:
|
|
708
|
+
response_data = api_client.get_events_without_preload_content(
|
|
709
|
+
var_from=from_time,
|
|
710
|
+
to=to_time,
|
|
711
|
+
window_size=size,
|
|
712
|
+
filter_event_updates=filter_event_updates,
|
|
713
|
+
exclude_triggered_before=exclude_triggered_before,
|
|
714
|
+
event_type_filters=["change"]
|
|
715
|
+
)
|
|
716
|
+
if response_data.status != 200:
|
|
717
|
+
return {"error": f"Failed to get change events: HTTP {response_data.status}"}
|
|
718
|
+
response_text = response_data.data.decode('utf-8')
|
|
719
|
+
result = json.loads(response_text)
|
|
720
|
+
if isinstance(result, list):
|
|
721
|
+
result_dict = {"events": result, "events_count": len(result)}
|
|
722
|
+
else:
|
|
723
|
+
result_dict = result
|
|
724
|
+
return result_dict
|
|
725
|
+
except Exception as api_error:
|
|
726
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
727
|
+
return {"error": f"Failed to get change events: {api_error}"}
|
|
728
|
+
except Exception as e:
|
|
729
|
+
logger.error(f"Error in get_change_events: {e}", exc_info=True)
|
|
730
|
+
return {"error": f"Failed to get change events: {e!s}"}
|
|
731
|
+
|
|
732
|
+
@register_as_tool
|
|
733
|
+
@with_header_auth(EventsApi)
|
|
734
|
+
async def get_events_by_ids(
|
|
735
|
+
self,
|
|
736
|
+
event_ids: Union[List[str], str],
|
|
737
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
738
|
+
"""
|
|
739
|
+
Get events by their IDs.
|
|
740
|
+
This tool retrieves multiple events at once using their unique IDs.
|
|
741
|
+
It supports both batch retrieval and individual fallback requests if the batch API fails.
|
|
742
|
+
|
|
743
|
+
Examples:
|
|
744
|
+
Get events using a list of IDs:
|
|
745
|
+
- event_ids: ["1a2b3c4d5e6f", "7g8h9i0j1k2l"]
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
event_ids: List of event IDs to retrieve or a comma-separated string of IDs
|
|
749
|
+
ctx: The MCP context (optional)
|
|
750
|
+
api_client: API client for testing (optional)
|
|
751
|
+
|
|
752
|
+
Returns:
|
|
753
|
+
Dictionary containing the list of events or error information
|
|
754
|
+
"""
|
|
755
|
+
|
|
756
|
+
try:
|
|
757
|
+
logger.debug(f"get_events_by_ids called with event_ids={event_ids}")
|
|
758
|
+
|
|
759
|
+
# Handle string input conversion
|
|
760
|
+
if isinstance(event_ids, str):
|
|
761
|
+
if event_ids.startswith('[') and event_ids.endswith(']'):
|
|
762
|
+
import ast
|
|
763
|
+
try:
|
|
764
|
+
event_ids = ast.literal_eval(event_ids)
|
|
765
|
+
except (SyntaxError, ValueError) as e:
|
|
766
|
+
logger.error(f"Failed to parse event_ids as list: {e}")
|
|
767
|
+
return {"error": f"Invalid event_ids format: {e}"}
|
|
768
|
+
else:
|
|
769
|
+
event_ids = [id.strip() for id in event_ids.split(',')]
|
|
770
|
+
|
|
771
|
+
# Validate input
|
|
772
|
+
if not event_ids:
|
|
773
|
+
return {"error": "No event IDs provided"}
|
|
774
|
+
|
|
775
|
+
logger.debug(f"Processing {len(event_ids)} event IDs")
|
|
776
|
+
|
|
777
|
+
# Use the batch API to retrieve all events at once
|
|
778
|
+
try:
|
|
779
|
+
logger.debug("Retrieving events using batch API")
|
|
780
|
+
events_result = api_client.get_events_by_ids(request_body=event_ids)
|
|
781
|
+
|
|
782
|
+
all_events = []
|
|
783
|
+
for event in events_result:
|
|
784
|
+
if hasattr(event, 'to_dict'):
|
|
785
|
+
event_dict = event.to_dict()
|
|
786
|
+
else:
|
|
787
|
+
event_dict = event
|
|
788
|
+
all_events.append(event_dict)
|
|
789
|
+
|
|
790
|
+
result = {
|
|
791
|
+
"events": all_events,
|
|
792
|
+
"events_count": len(all_events),
|
|
793
|
+
"successful_retrievals": len(all_events),
|
|
794
|
+
"failed_retrievals": 0,
|
|
795
|
+
"summary": self._summarize_events_result(all_events)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
logger.debug(f"Retrieved {result['successful_retrievals']} events successfully using batch API")
|
|
799
|
+
return result
|
|
800
|
+
|
|
801
|
+
except Exception as batch_error:
|
|
802
|
+
logger.warning(f"Batch API failed: {batch_error}. Falling back to individual requests.")
|
|
803
|
+
|
|
804
|
+
# Fallback to individual requests using without_preload_content
|
|
805
|
+
all_events = []
|
|
806
|
+
for event_id in event_ids:
|
|
807
|
+
try:
|
|
808
|
+
logger.debug(f"Retrieving event ID: {event_id}")
|
|
809
|
+
response_data = api_client.get_events_by_ids_without_preload_content(request_body=[event_id])
|
|
810
|
+
|
|
811
|
+
# Check if the response was successful
|
|
812
|
+
if response_data.status != 200:
|
|
813
|
+
error_message = f"Failed to get event {event_id}: HTTP {response_data.status}"
|
|
814
|
+
logger.error(error_message)
|
|
815
|
+
all_events.append({"eventId": event_id, "error": error_message})
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
# Read and parse the response content
|
|
819
|
+
response_text = response_data.data.decode('utf-8')
|
|
820
|
+
try:
|
|
821
|
+
event_dict = json.loads(response_text)
|
|
822
|
+
if isinstance(event_dict, list) and event_dict:
|
|
823
|
+
all_events.append(event_dict[0])
|
|
824
|
+
else:
|
|
825
|
+
all_events.append({"eventId": event_id, "error": "No event data returned"})
|
|
826
|
+
except json.JSONDecodeError as json_err:
|
|
827
|
+
error_message = f"Failed to parse JSON for event {event_id}: {json_err}"
|
|
828
|
+
logger.error(error_message)
|
|
829
|
+
all_events.append({"eventId": event_id, "error": error_message})
|
|
830
|
+
|
|
831
|
+
except Exception as e:
|
|
832
|
+
logger.error(f"Error retrieving event ID {event_id}: {e}", exc_info=True)
|
|
833
|
+
all_events.append({"eventId": event_id, "error": f"Failed to retrieve: {e!s}"})
|
|
834
|
+
|
|
835
|
+
result = {
|
|
836
|
+
"events": all_events,
|
|
837
|
+
"events_count": len(all_events),
|
|
838
|
+
"successful_retrievals": sum(1 for event in all_events if "error" not in event),
|
|
839
|
+
"failed_retrievals": sum(1 for event in all_events if "error" in event),
|
|
840
|
+
"summary": self._summarize_events_result([e for e in all_events if "error" not in e])
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
logger.debug(f"Retrieved {result['successful_retrievals']} events successfully, {result['failed_retrievals']} failed using individual requests")
|
|
844
|
+
return result
|
|
845
|
+
except Exception as e:
|
|
846
|
+
logger.error(f"Error in get_events_by_ids: {e}", exc_info=True)
|
|
847
|
+
return {
|
|
848
|
+
"error": f"Failed to get events by IDs: {e!s}",
|
|
849
|
+
"details": str(e)
|
|
850
|
+
}
|