PatrickRedStar commited on
Commit
a590b71
·
1 Parent(s): 904689f

Cleanup: Remove old rule-based agent files, all agents now use DeepSeek via smolagents

Browse files
agents/__pycache__/__init__.cpython-314.pyc CHANGED
Binary files a/agents/__pycache__/__init__.cpython-314.pyc and b/agents/__pycache__/__init__.cpython-314.pyc differ
 
agents/anomaly_agent.py DELETED
@@ -1,415 +0,0 @@
1
- """
2
- Agent 2: Anomaly Detection Agent
3
- Выявляет аномалии и подозрительные паттерны в структурированных логах.
4
- """
5
-
6
- import re
7
- import json
8
- from typing import Dict, List, Any
9
- from collections import defaultdict, Counter
10
- from datetime import datetime
11
-
12
-
13
- class AnomalyDetectionAgent:
14
- """Обнаруживает аномалии в структурированных логах."""
15
-
16
- def __init__(self):
17
- """Инициализация агента."""
18
- self.burst_threshold = 5 # Минимальное количество ошибок для burst
19
- self.burst_time_window = 60 # Окно времени в секундах для burst
20
- self.repeat_threshold = 3 # Минимальное количество повторений
21
-
22
- def detect(self, structured_data: Dict[str, Any]) -> Dict[str, Any]:
23
- """
24
- Выявляет аномалии в структурированных данных.
25
-
26
- Args:
27
- structured_data: Структурированные данные от LogParserAgent
28
-
29
- Returns:
30
- JSON-отчёт об аномалиях с описанием и метаданными
31
- """
32
- if not structured_data or not structured_data.get('events'):
33
- return self._empty_report()
34
-
35
- events = structured_data.get('events', [])
36
- errors = structured_data.get('errors', [])
37
-
38
- anomalies = []
39
-
40
- # 1. Обнаружение burst errors
41
- burst_anomalies = self._detect_burst_errors(events, errors)
42
- anomalies.extend(burst_anomalies)
43
-
44
- # 2. Обнаружение повторяющихся ошибок
45
- repeat_anomalies = self._detect_repeated_errors(errors)
46
- anomalies.extend(repeat_anomalies)
47
-
48
- # 3. Обнаружение паттернов "ошибка перед крашем"
49
- crash_patterns = self._detect_error_before_crash(errors, events)
50
- anomalies.extend(crash_patterns)
51
-
52
- # 4. Обнаружение временных всплесков
53
- spike_anomalies = self._detect_temporal_spikes(events)
54
- anomalies.extend(spike_anomalies)
55
-
56
- # 5. Обнаружение повторяющихся stack traces
57
- stack_trace_anomalies = self._detect_repeated_stack_traces(events)
58
- anomalies.extend(stack_trace_anomalies)
59
-
60
- # Подсчёт статистики
61
- anomaly_stats = self._calculate_anomaly_statistics(anomalies)
62
-
63
- return {
64
- 'anomalies': anomalies,
65
- 'statistics': anomaly_stats,
66
- 'severity_summary': self._calculate_severity_summary(anomalies)
67
- }
68
-
69
- def _detect_burst_errors(self, events: List[Dict], errors: List[Dict]) -> List[Dict[str, Any]]:
70
- """Обнаруживает всплески ошибок (burst errors)."""
71
- anomalies = []
72
-
73
- if len(errors) < self.burst_threshold:
74
- return anomalies
75
-
76
- # Группировка ошибок по времени (если доступны временные метки)
77
- error_times = []
78
- for error in errors:
79
- timestamp_str = error.get('timestamp')
80
- if timestamp_str:
81
- try:
82
- timestamp = self._parse_timestamp_simple(timestamp_str)
83
- if timestamp:
84
- error_times.append((timestamp, error))
85
- except:
86
- pass
87
-
88
- # Если временные метки доступны, анализируем временные окна
89
- if error_times:
90
- error_times.sort(key=lambda x: x[0] if x[0] else datetime.min)
91
-
92
- # Поиск кластеров ошибок во временных окнах
93
- i = 0
94
- while i < len(error_times):
95
- cluster_start = error_times[i][0]
96
- cluster_errors = [error_times[i][1]]
97
-
98
- j = i + 1
99
- while j < len(error_times) and error_times[j][0]:
100
- time_diff = (error_times[j][0] - cluster_start).total_seconds()
101
- if time_diff <= self.burst_time_window:
102
- cluster_errors.append(error_times[j][1])
103
- j += 1
104
- else:
105
- break
106
-
107
- if len(cluster_errors) >= self.burst_threshold:
108
- messages = [e.get('message', '')[:100] for e in cluster_errors[:3]]
109
- anomalies.append({
110
- 'type': 'BURST_ERRORS',
111
- 'severity': 'HIGH',
112
- 'description': f'Обнаружен всплеск из {len(cluster_errors)} ошибок в течение {self.burst_time_window} секунд',
113
- 'count': len(cluster_errors),
114
- 'time_window_seconds': self.burst_time_window,
115
- 'sample_messages': messages,
116
- 'first_occurrence': cluster_start.isoformat() if cluster_start else None,
117
- 'metadata': {
118
- 'threshold': self.burst_threshold,
119
- 'affected_lines': [e.get('line_number') for e in cluster_errors[:10]]
120
- }
121
- })
122
-
123
- i = j
124
- else:
125
- # Если временных меток нет, проверяем последовательные ошибки
126
- consecutive_count = 0
127
- start_idx = 0
128
-
129
- for i, error in enumerate(errors):
130
- if i > 0:
131
- prev_error = errors[i - 1]
132
- # Проверяем, являются ли ошибки последовательными (по номерам строк)
133
- if error.get('line_number', 0) - prev_error.get('line_number', 0) <= 5:
134
- consecutive_count += 1
135
- else:
136
- if consecutive_count >= self.burst_threshold:
137
- anomalies.append(self._create_burst_anomaly(
138
- errors[start_idx:i], consecutive_count + 1
139
- ))
140
- consecutive_count = 0
141
- start_idx = i
142
- else:
143
- consecutive_count = 1
144
-
145
- # Проверка последнего кластера
146
- if consecutive_count >= self.burst_threshold:
147
- anomalies.append(self._create_burst_anomaly(
148
- errors[start_idx:], consecutive_count
149
- ))
150
-
151
- return anomalies
152
-
153
- def _create_burst_anomaly(self, errors: List[Dict], count: int) -> Dict[str, Any]:
154
- """Создаёт запись об аномалии burst errors."""
155
- messages = [e.get('message', '')[:100] for e in errors[:3]]
156
- return {
157
- 'type': 'BURST_ERRORS',
158
- 'severity': 'HIGH',
159
- 'description': f'Обнаружен всплеск из {count} последовательных ошибок',
160
- 'count': count,
161
- 'sample_messages': messages,
162
- 'metadata': {
163
- 'threshold': self.burst_threshold,
164
- 'affected_lines': [e.get('line_number') for e in errors[:10]]
165
- }
166
- }
167
-
168
- def _detect_repeated_errors(self, errors: List[Dict]) -> List[Dict[str, Any]]:
169
- """Обнаруживает повторяющиеся ошибки."""
170
- anomalies = []
171
-
172
- if not errors:
173
- return anomalies
174
-
175
- # Группировка ошибок по сообщениям (нормализованным)
176
- error_groups = defaultdict(list)
177
- for error in errors:
178
- message = self._normalize_message(error.get('message', ''))
179
- error_groups[message].append(error)
180
-
181
- # Поиск повторяющихся ошибок
182
- for message, error_list in error_groups.items():
183
- if len(error_list) >= self.repeat_threshold:
184
- line_numbers = [e.get('line_number') for e in error_list]
185
- timestamps = [e.get('timestamp') for e in error_list if e.get('timestamp')]
186
-
187
- anomalies.append({
188
- 'type': 'REPEATED_ERRORS',
189
- 'severity': 'MEDIUM',
190
- 'description': f'Одна и та же ошибка повторяется {len(error_list)} раз(а)',
191
- 'count': len(error_list),
192
- 'error_message': message[:200],
193
- 'first_occurrence': timestamps[0] if timestamps else None,
194
- 'last_occurrence': timestamps[-1] if timestamps else None,
195
- 'metadata': {
196
- 'threshold': self.repeat_threshold,
197
- 'affected_lines': line_numbers[:20]
198
- }
199
- })
200
-
201
- return anomalies
202
-
203
- def _detect_error_before_crash(self, errors: List[Dict], events: List[Dict]) -> List[Dict[str, Any]]:
204
- """Обнаруживает паттерны "ошибка перед крашем"."""
205
- anomalies = []
206
-
207
- if not errors:
208
- return anomalies
209
-
210
- # Ищем последовательности критических ошибок в конце логов
211
- # Или ошибки, за которыми следует остановка системы
212
- crash_keywords = ['crash', 'shutdown', 'fatal', 'terminate', 'abort', 'exit']
213
-
214
- # Проверяем последние события на наличие паттернов краша
215
- last_events = events[-50:] if len(events) > 50 else events
216
- last_errors = errors[-20:] if len(errors) > 20 else errors
217
-
218
- for i, error in enumerate(last_errors):
219
- error_msg_lower = error.get('message', '').lower()
220
- error_level = error.get('level', '').upper()
221
-
222
- # Проверяем, является ли это критической ошибкой
223
- if error_level in ['CRITICAL', 'ERROR']:
224
- # Проверяем последующие события на признаки краша
225
- error_line = error.get('line_number', 0)
226
- subsequent_events = [e for e in last_events if e.get('line_number', 0) > error_line][:10]
227
-
228
- crash_indicators = []
229
- for event in subsequent_events:
230
- event_msg_lower = event.get('message', '').lower()
231
- if any(keyword in event_msg_lower for keyword in crash_keywords):
232
- crash_indicators.append(event.get('message', '')[:100])
233
-
234
- if crash_indicators or i == len(last_errors) - 1:
235
- anomalies.append({
236
- 'type': 'ERROR_BEFORE_CRASH',
237
- 'severity': 'CRITICAL',
238
- 'description': 'Обнаружен паттерн: ошибка перед возможным крашем системы',
239
- 'error_message': error.get('message', '')[:200],
240
- 'error_level': error_level,
241
- 'crash_indicators': crash_indicators[:3],
242
- 'metadata': {
243
- 'error_line': error_line,
244
- 'is_last_error': i == len(last_errors) - 1
245
- }
246
- })
247
-
248
- return anomalies
249
-
250
- def _detect_temporal_spikes(self, events: List[Dict]) -> List[Dict[str, Any]]:
251
- """Обнаруживает временные всплески событий."""
252
- anomalies = []
253
-
254
- # Группировка событий по времени (если доступны временные метки)
255
- events_with_time = [(e.get('timestamp'), e) for e in events if e.get('timestamp')]
256
-
257
- if len(events_with_time) < 10:
258
- return anomalies
259
-
260
- # Группировка по минутам (или другим временным окнам)
261
- time_groups = defaultdict(list)
262
- for timestamp_str, event in events_with_time:
263
- try:
264
- timestamp = self._parse_timestamp_simple(timestamp_str)
265
- if timestamp:
266
- # Группируем по минутам
267
- time_key = timestamp.strftime('%Y-%m-%d %H:%M')
268
- time_groups[time_key].append(event)
269
- except:
270
- pass
271
-
272
- if not time_groups:
273
- return anomalies
274
-
275
- # Вычисляем среднее количество событий на временное окно
276
- event_counts = [len(events) for events in time_groups.values()]
277
- if not event_counts:
278
- return anomalies
279
-
280
- avg_count = sum(event_counts) / len(event_counts)
281
- threshold = avg_count * 2 # Всплеск - это превышение среднего в 2 раза
282
-
283
- # Поиск всплесков
284
- for time_key, events_in_window in time_groups.items():
285
- if len(events_in_window) > threshold:
286
- error_count = len([e for e in events_in_window if e.get('level', '').upper() in ['ERROR', 'CRITICAL']])
287
-
288
- anomalies.append({
289
- 'type': 'TEMPORAL_SPIKE',
290
- 'severity': 'MEDIUM',
291
- 'description': f'Обнаружен временной всплеск: {len(events_in_window)} событий за период {time_key} (среднее: {avg_count:.1f})',
292
- 'time_window': time_key,
293
- 'event_count': len(events_in_window),
294
- 'average_count': round(avg_count, 1),
295
- 'error_count': error_count,
296
- 'metadata': {
297
- 'threshold_multiplier': 2.0
298
- }
299
- })
300
-
301
- return anomalies
302
-
303
- def _detect_repeated_stack_traces(self, events: List[Dict]) -> List[Dict[str, Any]]:
304
- """Обнаруживает повторяющиеся stack traces."""
305
- anomalies = []
306
-
307
- # Ищем строки, похожие на stack traces
308
- stack_trace_keywords = ['traceback', 'stack trace', 'at ', 'exception', 'file "', 'line ', 'in ']
309
- potential_stacks = []
310
-
311
- for event in events:
312
- message = event.get('message', '').lower()
313
- if any(keyword in message for keyword in stack_trace_keywords):
314
- # Проверяем длину сообщения (stack traces обычно длинные)
315
- if len(event.get('message', '')) > 100:
316
- potential_stacks.append(event)
317
-
318
- if len(potential_stacks) < self.repeat_threshold:
319
- return anomalies
320
-
321
- # Группировка по нормализованным сообщениям
322
- stack_groups = defaultdict(list)
323
- for stack in potential_stacks:
324
- normalized = self._normalize_stack_trace(stack.get('message', ''))
325
- stack_groups[normalized].append(stack)
326
-
327
- # Поиск повторяющихся
328
- for normalized_stack, stack_list in stack_groups.items():
329
- if len(stack_list) >= self.repeat_threshold:
330
- anomalies.append({
331
- 'type': 'REPEATED_STACK_TRACES',
332
- 'severity': 'HIGH',
333
- 'description': f'Один и тот же stack trace повторяется {len(stack_list)} раз(а)',
334
- 'count': len(stack_list),
335
- 'stack_trace_preview': normalized_stack[:300],
336
- 'metadata': {
337
- 'threshold': self.repeat_threshold,
338
- 'affected_lines': [s.get('line_number') for s in stack_list[:10]]
339
- }
340
- })
341
-
342
- return anomalies
343
-
344
- def _normalize_message(self, message: str) -> str:
345
- """Нормализует сообщение для группировки (удаляет переменные части)."""
346
- # Удаляем числа и даты
347
- normalized = re.sub(r'\d+', 'N', message)
348
- # Удаляем пути к файлам
349
- normalized = re.sub(r'[A-Z]:\\[^\s]+|/[^\s]+', 'PATH', normalized)
350
- # Удаляем URL
351
- normalized = re.sub(r'https?://[^\s]+', 'URL', normalized)
352
- return normalized.strip()
353
-
354
- def _normalize_stack_trace(self, stack: str) -> str:
355
- """Нормализует stack trace для сравнения."""
356
- # Оставляем только ключевые части stack trace
357
- lines = stack.split('\n')[:5] # Первые 5 строк обычно достаточны
358
- normalized = '\n'.join([line.strip() for line in lines])
359
- # Удаляем пути и номера строк
360
- normalized = re.sub(r'File "[^"]+", line \d+', 'File "FILE", line N', normalized)
361
- return normalized
362
-
363
- def _parse_timestamp_simple(self, timestamp_str: str) -> datetime | None:
364
- """Простой парсер временных меток."""
365
- timestamp_str = timestamp_str.strip('[]')
366
- formats = [
367
- '%Y-%m-%d %H:%M:%S',
368
- '%Y-%m-%dT%H:%M:%S',
369
- '%Y-%m-%d %H:%M:%S.%f',
370
- '%Y-%m-%dT%H:%M:%S.%f',
371
- '%d/%m/%Y %H:%M:%S',
372
- ]
373
-
374
- for fmt in formats:
375
- try:
376
- return datetime.strptime(timestamp_str, fmt)
377
- except ValueError:
378
- continue
379
-
380
- return None
381
-
382
- def _calculate_anomaly_statistics(self, anomalies: List[Dict]) -> Dict[str, Any]:
383
- """Вычисляет статистику аномалий."""
384
- if not anomalies:
385
- return {
386
- 'total': 0,
387
- 'by_type': {},
388
- 'by_severity': {}
389
- }
390
-
391
- by_type = Counter(a.get('type') for a in anomalies)
392
- by_severity = Counter(a.get('severity') for a in anomalies)
393
-
394
- return {
395
- 'total': len(anomalies),
396
- 'by_type': dict(by_type),
397
- 'by_severity': dict(by_severity)
398
- }
399
-
400
- def _calculate_severity_summary(self, anomalies: List[Dict]) -> Dict[str, int]:
401
- """Вычисляет сводку по уровням серьёзности."""
402
- severity_counts = Counter(a.get('severity', 'UNKNOWN') for a in anomalies)
403
- return dict(severity_counts)
404
-
405
- def _empty_report(self) -> Dict[str, Any]:
406
- """Возвращает пустой отчёт при отсутствии данных."""
407
- return {
408
- 'anomalies': [],
409
- 'statistics': {
410
- 'total': 0,
411
- 'by_type': {},
412
- 'by_severity': {}
413
- },
414
- 'severity_summary': {}
415
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/parser_agent.py DELETED
@@ -1,217 +0,0 @@
1
- """
2
- Agent 1: Log Parser Agent
3
- Преобразует сырые логи в структурированное представление.
4
- """
5
-
6
- import re
7
- import json
8
- from datetime import datetime
9
- from typing import Dict, List, Any
10
- from collections import defaultdict
11
-
12
-
13
- class LogParserAgent:
14
- """Парсит сырые логи и преобразует их в структурированный JSON."""
15
-
16
- # Паттерны для распознавания уровней логирования
17
- LOG_LEVELS = ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'TRACE']
18
-
19
- # Паттерны для временных меток (поддержка различных форматов)
20
- TIMESTAMP_PATTERNS = [
21
- r'\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2}(?:\.\d+)?', # ISO 8601
22
- r'\d{2}/\d{2}/\d{4}[\s]\d{2}:\d{2}:\d{2}', # DD/MM/YYYY HH:MM:SS
23
- r'\[(?:[A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\]', # [Mon Jan 1 12:00:00]
24
- ]
25
-
26
- def __init__(self):
27
- """Инициализация агента."""
28
- self.compiled_patterns = [re.compile(pattern) for pattern in self.TIMESTAMP_PATTERNS]
29
-
30
- def parse(self, raw_logs: str) -> Dict[str, Any]:
31
- """
32
- Парсит сырые логи и возвращает структурированный JSON.
33
-
34
- Args:
35
- raw_logs: Строка с сырыми логами
36
-
37
- Returns:
38
- Структурированный JSON-объект с событиями, ошибками, предупреждениями и статистикой
39
- """
40
- if not raw_logs or not raw_logs.strip():
41
- return self._empty_result()
42
-
43
- lines = raw_logs.strip().split('\n')
44
- events = []
45
- errors = []
46
- warnings = []
47
-
48
- for line_num, line in enumerate(lines, start=1):
49
- if not line.strip():
50
- continue
51
-
52
- parsed_event = self._parse_line(line, line_num)
53
- if parsed_event:
54
- events.append(parsed_event)
55
-
56
- level = parsed_event.get('level', '').upper()
57
- if level == 'ERROR' or level == 'CRITICAL':
58
- errors.append(parsed_event)
59
- elif level == 'WARNING':
60
- warnings.append(parsed_event)
61
-
62
- # Группировка по типам событий
63
- event_types = defaultdict(int)
64
- for event in events:
65
- event_type = event.get('type', 'UNKNOWN')
66
- event_types[event_type] += 1
67
-
68
- # Статистика
69
- statistics = {
70
- 'total_lines': len(lines),
71
- 'parsed_events': len(events),
72
- 'errors': len(errors),
73
- 'warnings': len(warnings),
74
- 'info_messages': len([e for e in events if e.get('level', '').upper() == 'INFO']),
75
- 'event_types': dict(event_types),
76
- 'time_range': self._calculate_time_range(events),
77
- }
78
-
79
- return {
80
- 'events': events,
81
- 'errors': errors,
82
- 'warnings': warnings,
83
- 'statistics': statistics
84
- }
85
-
86
- def _parse_line(self, line: str, line_num: int) -> Dict[str, Any] | None:
87
- """
88
- Парсит одну строку лога.
89
-
90
- Args:
91
- line: Строка лога
92
- line_num: Номер строки
93
-
94
- Returns:
95
- Словарь с распарсенными данными или None
96
- """
97
- # Поиск временной метки
98
- timestamp = None
99
- timestamp_str = None
100
- for pattern in self.compiled_patterns:
101
- match = pattern.search(line)
102
- if match:
103
- timestamp_str = match.group(0)
104
- try:
105
- # Попытка парсинга различных форматов
106
- timestamp = self._parse_timestamp(timestamp_str)
107
- except:
108
- pass
109
- break
110
-
111
- # Поиск уровня логирования
112
- level = None
113
- for log_level in self.LOG_LEVELS:
114
- if log_level in line.upper():
115
- level = log_level
116
- break
117
-
118
- # Если уровень не найден, определяем по ключевым словам
119
- if not level:
120
- line_upper = line.upper()
121
- if any(word in line_upper for word in ['ERROR', 'EXCEPTION', 'FAILED', 'FAILURE']):
122
- level = 'ERROR'
123
- elif any(word in line_upper for word in ['WARN', 'WARNING']):
124
- level = 'WARNING'
125
- elif any(word in line_upper for word in ['INFO', 'INFORMATION']):
126
- level = 'INFO'
127
- elif any(word in line_upper for word in ['DEBUG']):
128
- level = 'DEBUG'
129
- else:
130
- level = 'INFO' # По умолчанию
131
-
132
- # Извлечение сообщения (часть после временной метки и уровня)
133
- message = line
134
- if timestamp_str:
135
- message = message.replace(timestamp_str, '', 1).strip()
136
-
137
- # Определение типа события
138
- event_type = self._detect_event_type(line)
139
-
140
- return {
141
- 'line_number': line_num,
142
- 'timestamp': timestamp_str if timestamp_str else None,
143
- 'level': level,
144
- 'message': message.strip(),
145
- 'type': event_type,
146
- 'raw': line
147
- }
148
-
149
- def _parse_timestamp(self, timestamp_str: str) -> datetime | None:
150
- """Парсит строку временной метки в объект datetime."""
151
- # Удаление скобок если есть
152
- timestamp_str = timestamp_str.strip('[]')
153
-
154
- # Попытка различных форматов
155
- formats = [
156
- '%Y-%m-%d %H:%M:%S',
157
- '%Y-%m-%dT%H:%M:%S',
158
- '%Y-%m-%d %H:%M:%S.%f',
159
- '%Y-%m-%dT%H:%M:%S.%f',
160
- '%d/%m/%Y %H:%M:%S',
161
- '%a %b %d %H:%M:%S %Y', # [Mon Jan 1 12:00:00 2024]
162
- ]
163
-
164
- for fmt in formats:
165
- try:
166
- return datetime.strptime(timestamp_str, fmt)
167
- except ValueError:
168
- continue
169
-
170
- return None
171
-
172
- def _detect_event_type(self, line: str) -> str:
173
- """Определяет тип события по содержимому строки."""
174
- line_lower = line.lower()
175
-
176
- if any(keyword in line_lower for keyword in ['connection', 'connect', 'disconnect']):
177
- return 'CONNECTION'
178
- elif any(keyword in line_lower for keyword in ['request', 'response', 'http', 'api']):
179
- return 'HTTP_REQUEST'
180
- elif any(keyword in line_lower for keyword in ['database', 'db', 'query', 'sql']):
181
- return 'DATABASE'
182
- elif any(keyword in line_lower for keyword in ['authentication', 'auth', 'login', 'logout']):
183
- return 'AUTHENTICATION'
184
- elif any(keyword in line_lower for keyword in ['exception', 'error', 'failure']):
185
- return 'EXCEPTION'
186
- elif any(keyword in line_lower for keyword in ['start', 'stop', 'shutdown', 'initialized']):
187
- return 'SYSTEM'
188
- else:
189
- return 'GENERAL'
190
-
191
- def _calculate_time_range(self, events: List[Dict[str, Any]]) -> Dict[str, str] | None:
192
- """Вычисляет временной диапазон событий."""
193
- timestamps = [e.get('timestamp') for e in events if e.get('timestamp')]
194
- if not timestamps:
195
- return None
196
-
197
- return {
198
- 'start': timestamps[0],
199
- 'end': timestamps[-1]
200
- }
201
-
202
- def _empty_result(self) -> Dict[str, Any]:
203
- """Возвращает пустой результат при отсутствии логов."""
204
- return {
205
- 'events': [],
206
- 'errors': [],
207
- 'warnings': [],
208
- 'statistics': {
209
- 'total_lines': 0,
210
- 'parsed_events': 0,
211
- 'errors': 0,
212
- 'warnings': 0,
213
- 'info_messages': 0,
214
- 'event_types': {},
215
- 'time_range': None
216
- }
217
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/rca_agent.py DELETED
@@ -1,316 +0,0 @@
1
- """
2
- Agent 3: Root Cause & Recommendation Agent
3
- Интерпретирует аномалии и формирует рекомендации.
4
- """
5
-
6
- from typing import Dict, List, Any
7
- import json
8
-
9
-
10
- class RootCauseAgent:
11
- """Анализирует аномалии и генерирует рекомендации."""
12
-
13
- def __init__(self):
14
- """Инициализация агента."""
15
- self.root_cause_templates = self._init_root_cause_templates()
16
- self.recommendation_templates = self._init_recommendation_templates()
17
-
18
- def analyze(self, anomaly_report: Dict[str, Any]) -> str:
19
- """
20
- Анализирует отчёт об аномалиях и генерирует рекомендации.
21
-
22
- Args:
23
- anomaly_report: Отчёт об аномалиях от AnomalyDetectionAgent
24
-
25
- Returns:
26
- Markdown-текст с анализом и рекомендациями
27
- """
28
- if not anomaly_report or not anomaly_report.get('anomalies'):
29
- return self._generate_no_anomalies_report()
30
-
31
- anomalies = anomaly_report.get('anomalies', [])
32
- statistics = anomaly_report.get('statistics', {})
33
- severity_summary = anomaly_report.get('severity_summary', {})
34
-
35
- # Генерация отчёта
36
- report_parts = []
37
-
38
- # Заголовок
39
- report_parts.append("# Анализ первопричин и рекомендации\n")
40
- report_parts.append(f"**Обнаружено аномалий:** {statistics.get('total', 0)}\n")
41
-
42
- # Сводка по серьёзности
43
- if severity_summary:
44
- report_parts.append("\n## Сводка по уровням серьёзности\n")
45
- severity_order = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']
46
- for severity in severity_order:
47
- count = severity_summary.get(severity, 0)
48
- if count > 0:
49
- emoji = self._get_severity_emoji(severity)
50
- report_parts.append(f"- {emoji} **{severity}:** {count}\n")
51
-
52
- # Группировка аномалий по типам
53
- anomalies_by_type = {}
54
- for anomaly in anomalies:
55
- anomaly_type = anomaly.get('type', 'UNKNOWN')
56
- if anomaly_type not in anomalies_by_type:
57
- anomalies_by_type[anomaly_type] = []
58
- anomalies_by_type[anomaly_type].append(anomaly)
59
-
60
- # Анализ каждого типа аномалий
61
- report_parts.append("\n## Детальный анализ аномалий\n")
62
-
63
- for anomaly_type, type_anomalies in anomalies_by_type.items():
64
- report_parts.append(f"\n### {self._get_anomaly_type_name(anomaly_type)}\n")
65
-
66
- # Анализ первопричин
67
- root_causes = self._identify_root_causes(anomaly_type, type_anomalies)
68
- if root_causes:
69
- report_parts.append("#### Возможные первопричины:\n")
70
- for i, cause in enumerate(root_causes, 1):
71
- report_parts.append(f"{i}. {cause}\n")
72
-
73
- # Детали аномалий
74
- report_parts.append("\n#### Детали:\n")
75
- for i, anomaly in enumerate(type_anomalies[:5], 1): # Показываем до 5 примеров
76
- severity = anomaly.get('severity', 'UNKNOWN')
77
- description = anomaly.get('description', 'Без описания')
78
- report_parts.append(f"**Аномалия {i}** ({severity}):\n")
79
- report_parts.append(f"- {description}\n")
80
-
81
- # Дополнительная информация
82
- if anomaly.get('count'):
83
- report_parts.append(f"- Количество: {anomaly.get('count')}\n")
84
- if anomaly.get('error_message'):
85
- error_msg = anomaly.get('error_message', '')[:150]
86
- report_parts.append(f"- Сообщение: `{error_msg}`\n")
87
- if anomaly.get('metadata'):
88
- metadata = anomaly.get('metadata', {})
89
- if metadata.get('affected_lines'):
90
- lines = metadata.get('affected_lines', [])[:5]
91
- report_parts.append(f"- Затронутые строки: {', '.join(map(str, lines))}\n")
92
-
93
- if len(type_anomalies) > 5:
94
- report_parts.append(f"\n*... и ещё {len(type_anomalies) - 5} аномалий этого типа*\n")
95
-
96
- # Рекомендации
97
- report_parts.append("\n## Рекомендации по устранению\n")
98
- recommendations = self._generate_recommendations(anomalies)
99
-
100
- for i, recommendation in enumerate(recommendations, 1):
101
- priority = recommendation.get('priority', 'MEDIUM')
102
- emoji = self._get_priority_emoji(priority)
103
- report_parts.append(f"\n### {emoji} Рекомендация {i} (Приоритет: {priority})\n")
104
- report_parts.append(f"{recommendation.get('text', '')}\n")
105
-
106
- if recommendation.get('actions'):
107
- report_parts.append("**Конкретные действия:**\n")
108
- for action in recommendation.get('actions', []):
109
- report_parts.append(f"- {action}\n")
110
-
111
- # Общие рекомендации
112
- report_parts.append("\n## Общие рекомендации\n")
113
- general_recommendations = self._generate_general_recommendations(anomalies, statistics)
114
- for rec in general_recommendations:
115
- report_parts.append(f"- {rec}\n")
116
-
117
- return ''.join(report_parts)
118
-
119
- def _identify_root_causes(self, anomaly_type: str, anomalies: List[Dict]) -> List[str]:
120
- """Определяет возможные первопричины для типа аномалий."""
121
- causes = []
122
-
123
- if anomaly_type == 'BURST_ERRORS':
124
- causes.extend([
125
- "Внезапная перегрузка системы или внешнего сервиса",
126
- "Сбой в инфраструктуре (сеть, база данных, диск)",
127
- "Проблемы с зависимыми сервисами или API",
128
- "Некорректное обновление или развертывание кода"
129
- ])
130
- elif anomaly_type == 'REPEATED_ERRORS':
131
- causes.extend([
132
- "Проблема в коде, которая воспроизводится при определённых условиях",
133
- "Недостаточная обработка ошибок в цикле или повторяющемся процессе",
134
- "Проблема конфигурации, влияющая на конкретную функциональность",
135
- "Ресурсные ограничения (память, диск, соединения)"
136
- ])
137
- elif anomaly_type == 'ERROR_BEFORE_CRASH':
138
- causes.extend([
139
- "Критическая ошибка, приводящая к падению процесса",
140
- "Исчерпание ресурсов (память, дескрипторы файлов)",
141
- "Некорректное состояние приложения после длительной работы",
142
- "Проблемы с внешними зависимостями"
143
- ])
144
- elif anomaly_type == 'TEMPORAL_SPIKE':
145
- causes.extend([
146
- "Плановые задачи (cron jobs, scheduled tasks)",
147
- "Резкое увеличение нагрузки от пользователей",
148
- "Внешние события, вызывающие массовые запросы",
149
- "Проблемы с кэшированием или сессиями"
150
- ])
151
- elif anomaly_type == 'REPEATED_STACK_TRACES':
152
- causes.extend([
153
- "Необработанное исключение в часто вызываемом коде",
154
- "Проблема в библиотеке или зависимостях",
155
- "Некорректные входные данные, вызывающие исключение",
156
- "Race condition или проблема конкурентности"
157
- ])
158
- else:
159
- causes.append("Требуется дополнительный анализ для определения первопричины")
160
-
161
- return causes
162
-
163
- def _generate_recommendations(self, anomalies: List[Dict]) -> List[Dict[str, Any]]:
164
- """Генерирует рекомендации на основе обнаруженных аномалий."""
165
- recommendations = []
166
-
167
- # Группировка по типам для приоритизации
168
- anomaly_types = [a.get('type') for a in anomalies]
169
- severities = [a.get('severity') for a in anomalies]
170
-
171
- has_critical = any(s == 'CRITICAL' for s in severities)
172
- has_high = any(s == 'HIGH' for s in severities)
173
- has_burst = 'BURST_ERRORS' in anomaly_types
174
- has_crash = 'ERROR_BEFORE_CRASH' in anomaly_types
175
-
176
- # Критические рекомендации
177
- if has_crash:
178
- recommendations.append({
179
- 'priority': 'CRITICAL',
180
- 'text': 'Обнаружены признаки возможн��го краша системы. Требуется немедленное внимание.',
181
- 'actions': [
182
- 'Проверить состояние системы и процессов',
183
- 'Проанализировать последние ошибки перед крашем',
184
- 'Убедиться, что мониторинг и алертинг настроены корректно',
185
- 'Рассмотреть возможность отката последних изменений'
186
- ]
187
- })
188
-
189
- if has_burst:
190
- recommendations.append({
191
- 'priority': 'HIGH',
192
- 'text': 'Обнаружены всплески ошибок. Необходимо определить источник нагрузки.',
193
- 'actions': [
194
- 'Проверить метрики нагрузки (CPU, память, сеть)',
195
- 'Изучить логи зависимых сервисов',
196
- 'Проверить состояние базы данных и внешних API',
197
- 'Рассмотреть возможность масштабирования или rate limiting'
198
- ]
199
- })
200
-
201
- # Рекомендации по повторяющимся ошибкам
202
- if 'REPEATED_ERRORS' in anomaly_types:
203
- recommendations.append({
204
- 'priority': 'HIGH',
205
- 'text': 'Обнаружены повторяющиеся ошибки. Требуется исправление в коде или конфигурации.',
206
- 'actions': [
207
- 'Идентифицировать конкретный участок кода, вызывающий ошибку',
208
- 'Добавить более детальное логирование для отладки',
209
- 'Улучшить обработку ошибок с логированием контекста',
210
- 'Провести code review проблемного участка'
211
- ]
212
- })
213
-
214
- # Рекомендации по stack traces
215
- if 'REPEATED_STACK_TRACES' in anomaly_types:
216
- recommendations.append({
217
- 'priority': 'MEDIUM',
218
- 'text': 'Обнаружены повторяющиеся stack traces. Необходимо исправить необработанные исключения.',
219
- 'actions': [
220
- 'Найти и исправить источник исключения',
221
- 'Добавить обработку исключений (try-except блоки)',
222
- 'Улучшить валидацию входных данных',
223
- 'Обновить проблемные библиотеки или зависимости'
224
- ]
225
- })
226
-
227
- # Общие рекомендации по мониторингу
228
- if has_high or has_critical:
229
- recommendations.append({
230
- 'priority': 'MEDIUM',
231
- 'text': 'Улучшить систему мониторинга и алертинга для раннего обнаружения проблем.',
232
- 'actions': [
233
- 'Настроить алерты на критические ошибки',
234
- 'Внедрить мониторинг метрик производительности',
235
- 'Настроить дашборды для визуализации состояния системы',
236
- 'Реализовать автоматические проверки здоровья (health checks)'
237
- ]
238
- })
239
-
240
- return recommendations
241
-
242
- def _generate_general_recommendations(self, anomalies: List[Dict], statistics: Dict) -> List[str]:
243
- """Генерирует общие рекомендации."""
244
- recommendations = []
245
-
246
- total = statistics.get('total', 0)
247
- if total == 0:
248
- return ["Логи не содержат аномалий. Система работает стабильно."]
249
-
250
- recommendations.append("Регулярно проверяйте логи на наличие паттернов и трендов")
251
- recommendations.append("Настройте автоматическое уведомление о критических ошибках")
252
-
253
- if total > 10:
254
- recommendations.append("Обнаружено значительное количество аномалий - рекомендуется провести комплексный анализ системы")
255
-
256
- recommendations.append("Ведите документацию по известным проблемам и их решениям")
257
- recommendations.append("Рассмотрите возможность внедрения централизованного логирования (ELK, Splunk и т.д.)")
258
-
259
- return recommendations
260
-
261
- def _get_anomaly_type_name(self, anomaly_type: str) -> str:
262
- """Возвращает читаемое название типа аномалии."""
263
- names = {
264
- 'BURST_ERRORS': 'Всплески ошибок',
265
- 'REPEATED_ERRORS': 'Повторяющиеся ошибки',
266
- 'ERROR_BEFORE_CRASH': 'Ошибки перед крашем',
267
- 'TEMPORAL_SPIKE': 'Временные всплески',
268
- 'REPEATED_STACK_TRACES': 'Повторяющиеся stack traces'
269
- }
270
- return names.get(anomaly_type, anomaly_type)
271
-
272
- def _get_severity_emoji(self, severity: str) -> str:
273
- """Возвращает emoji для уровня серьёзности."""
274
- emoji_map = {
275
- 'CRITICAL': '🔴',
276
- 'HIGH': '🟠',
277
- 'MEDIUM': '🟡',
278
- 'LOW': '🟢'
279
- }
280
- return emoji_map.get(severity, '⚪')
281
-
282
- def _get_priority_emoji(self, priority: str) -> str:
283
- """Возвращает emoji для приоритета."""
284
- emoji_map = {
285
- 'CRITICAL': '🔴',
286
- 'HIGH': '🟠',
287
- 'MEDIUM': '🟡',
288
- 'LOW': '🟢'
289
- }
290
- return emoji_map.get(priority, '⚪')
291
-
292
- def _init_root_cause_templates(self) -> Dict[str, List[str]]:
293
- """Инициализирует шаблоны первопричин."""
294
- return {}
295
-
296
- def _init_recommendation_templates(self) -> Dict[str, List[str]]:
297
- """Инициализирует шаблоны рекомендаций."""
298
- return {}
299
-
300
- def _generate_no_anomalies_report(self) -> str:
301
- """Генерирует отчёт, когда аномалий не обнаружено."""
302
- return """# Анализ первопричин и рекомендации
303
-
304
- ## Результаты анализа
305
-
306
- **Обнаружено аномалий:** 0
307
-
308
- ✅ **Система работает стабильно.** В логах не обнаружено значительных аномалий или паттернов, указывающих на проблемы.
309
-
310
- ### Общие рекомендации
311
-
312
- - Продолжайте регулярный мониторинг логов
313
- - Поддерживайте текущий уровень логирования
314
- - Настройте автоматические проверки для раннего обнаружения проблем
315
- - Регулярно просматривайте метрики производительности
316
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
schemas/__pycache__/schemas.cpython-314.pyc CHANGED
Binary files a/schemas/__pycache__/schemas.cpython-314.pyc and b/schemas/__pycache__/schemas.cpython-314.pyc differ