Commit
·
d6f4b44
1
Parent(s):
30e257a
add
Browse files- .gitattributes +35 -35
- README.md +231 -12
- __pycache__/app.cpython-314.pyc +0 -0
- agents/__pycache__/__init__.cpython-314.pyc +0 -0
- agents/__pycache__/anomaly_agent.cpython-314.pyc +0 -0
- agents/__pycache__/parser_agent.cpython-314.pyc +0 -0
- agents/__pycache__/rca_agent.cpython-314.pyc +0 -0
- agents/anomaly_agent.py +415 -0
- agents/parser_agent.py +217 -0
- agents/rca_agent.py +316 -0
- app.py +218 -0
- requirements.txt +2 -0
- run.bat +5 -0
- space_config.yaml +11 -0
- test_large_logs.py +468 -0
- test_logs/application_metadata.log +0 -0
- test_logs/burst_errors.log +0 -0
- test_logs/error_before_crash.log +0 -0
- test_logs/mixed_formats.log +0 -0
- test_logs/mixed_patterns.log +0 -0
- test_logs/normal_logs.log +0 -0
- test_logs/repeated_errors.log +0 -0
- test_logs/stack_traces.log +0 -0
- test_logs/temporal_spikes.log +0 -0
- test_logs/web_server.log +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,35 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,12 +1,231 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: MultiAgentLogsAnalyze
|
| 3 |
-
emoji: 🔥
|
| 4 |
-
colorFrom: gray
|
| 5 |
-
colorTo: indigo
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MultiAgentLogsAnalyze
|
| 3 |
+
emoji: 🔥
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# 🔍 Мультиагентная система анализа логов
|
| 13 |
+
|
| 14 |
+
Интерактивное веб-приложение для автоматизированного анализа лог-файлов с использованием трёх логически разделённых агентов.
|
| 15 |
+
|
| 16 |
+
## 📋 Описание
|
| 17 |
+
|
| 18 |
+
Система обеспечивает автоматизированный анализ логов с:
|
| 19 |
+
- Структурированием данных
|
| 20 |
+
- Выявлением аномалий
|
| 21 |
+
- Интерпретацией причин и выдачей рекомендаций
|
| 22 |
+
|
| 23 |
+
## 🏗️ Архитектура
|
| 24 |
+
|
| 25 |
+
Проект реализован как мультиагентная система с **строго 3 независимыми агентами**, каждый из которых выполняет определённую функцию в цепочке анализа.
|
| 26 |
+
|
| 27 |
+
### Агент 1: Log Parser Agent (`agents/parser_agent.py`)
|
| 28 |
+
|
| 29 |
+
**Назначение:** Преобразование сырых логов в структурированное представление.
|
| 30 |
+
|
| 31 |
+
**Функциональность:**
|
| 32 |
+
- Разбор строк логов
|
| 33 |
+
- Извлечение временных меток
|
| 34 |
+
- Определение уровней логирования (INFO, WARNING, ERROR, CRITICAL, DEBUG)
|
| 35 |
+
- Извлечение сообщений
|
| 36 |
+
- Группировка по типам событий (CONNECTION, HTTP_REQUEST, DATABASE, AUTHENTICATION, EXCEPTION, SYSTEM, GENERAL)
|
| 37 |
+
|
| 38 |
+
**Выход:** Структурированный JSON-объект с полями:
|
| 39 |
+
- `events` - список всех распарсенных событий
|
| 40 |
+
- `errors` - список ошибок
|
| 41 |
+
- `warnings` - список предупреждений
|
| 42 |
+
- `statistics` - статистика по логам
|
| 43 |
+
|
| 44 |
+
### Агент 2: Anomaly Detection Agent (`agents/anomaly_agent.py`)
|
| 45 |
+
|
| 46 |
+
**Назначение:** Выявление аномалий и подозрительных паттернов в структурированных логах.
|
| 47 |
+
|
| 48 |
+
**Функциональность:**
|
| 49 |
+
- Подсчёт частоты событий
|
| 50 |
+
- Поиск повторяющихся ошибок
|
| 51 |
+
- Обнаружение временных всплесков
|
| 52 |
+
- Эвристический анализ последовательностей событий
|
| 53 |
+
|
| 54 |
+
**Типы обнаруживаемых аномалий:**
|
| 55 |
+
- **BURST_ERRORS** - всплески ошибок (более 5 ошибок в короткий промежуток времени)
|
| 56 |
+
- **REPEATED_ERRORS** - повторяющиеся ошибки (одна и та же ошибка более 3 раз)
|
| 57 |
+
- **ERROR_BEFORE_CRASH** - паттерны "ошибка перед крашем"
|
| 58 |
+
- **TEMPORAL_SPIKE** - временные всплески событий (превышение среднего в 2 раза)
|
| 59 |
+
- **REPEATED_STACK_TRACES** - повторяющиеся stack traces
|
| 60 |
+
|
| 61 |
+
**Выход:** JSON-отчёт об аномалиях с описанием, метаданными и статистикой.
|
| 62 |
+
|
| 63 |
+
### Агент 3: Root Cause & Recommendation Agent (`agents/rca_agent.py`)
|
| 64 |
+
|
| 65 |
+
**Назначение:** Интерпретация аномалий и формирование рекомендаций.
|
| 66 |
+
|
| 67 |
+
**Функциональность:**
|
| 68 |
+
- Определение возможных первопричин на основе типа аномалий
|
| 69 |
+
- Формирование человеко-читаемого отчёта в формате Markdown
|
| 70 |
+
- Генерация рекомендаций по устранению проблем с приоритетами
|
| 71 |
+
- Предоставление конкретных действий для решения проблем
|
| 72 |
+
|
| 73 |
+
**Выход:** Markdown-текст с анализом первопричин и рекомендациями.
|
| 74 |
+
|
| 75 |
+
## 🔄 Процесс анализа
|
| 76 |
+
|
| 77 |
+
Анализ выполняется последовательно:
|
| 78 |
+
|
| 79 |
+
1. **Пользователь** загружает или вставляет логи в интерфейс
|
| 80 |
+
2. **Agent 1** (Log Parser) обрабатывает сырые логи → структурированный JSON
|
| 81 |
+
3. **Agent 2** (Anomaly Detection) анализирует структурированные данные → отчёт об аномалиях
|
| 82 |
+
4. **Agent 3** (Root Cause) интерпретирует аномалии → Markdown с рекомендациями
|
| 83 |
+
5. **Результаты** отображаются в интерфейсе в трёх вкладках
|
| 84 |
+
|
| 85 |
+
## 🚀 Использование
|
| 86 |
+
|
| 87 |
+
### Локальный запуск
|
| 88 |
+
|
| 89 |
+
1. Установите зависимости:
|
| 90 |
+
```bash
|
| 91 |
+
pip install -r requirements.txt
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
2. Запустите приложение:
|
| 95 |
+
```bash
|
| 96 |
+
python app.py
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
3. Откройте браузер по адресу `http://localhost:7860`
|
| 100 |
+
|
| 101 |
+
### Использование в Hugging Face Spaces
|
| 102 |
+
|
| 103 |
+
Приложение автоматически развернётся при загрузке на Hugging Face Spaces.
|
| 104 |
+
|
| 105 |
+
## 📁 Структура проекта
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
MultiAgentLogsAnalyze/
|
| 109 |
+
├── agents/
|
| 110 |
+
│ ├── __init__.py # Экспорт агентов
|
| 111 |
+
│ ├── parser_agent.py # Agent 1: Log Parser Agent
|
| 112 |
+
│ ├── anomaly_agent.py # Agent 2: Anomaly Detection Agent
|
| 113 |
+
│ └── rca_agent.py # Agent 3: Root Cause Agent
|
| 114 |
+
├── app.py # Gradio приложение и оркестрация
|
| 115 |
+
├── requirements.txt # Зависимости Python
|
| 116 |
+
├── README.md # Документация проекта
|
| 117 |
+
└── space_config.yaml # Конфигурация для Hugging Face Spaces
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
## 🔧 Технические детали
|
| 121 |
+
|
| 122 |
+
### Зависимости
|
| 123 |
+
|
| 124 |
+
- `gradio>=4.0.0,<5.0.0` - веб-интерфейс
|
| 125 |
+
|
| 126 |
+
Все агенты реализованы на чистом Python 3.10+ без использования LLM или трансформеров.
|
| 127 |
+
|
| 128 |
+
### Производительность
|
| 129 |
+
|
| 130 |
+
- Поддержка анализа до 10,000 строк логов
|
| 131 |
+
- Время обработки ≤ 10 секунд для типичных логов
|
| 132 |
+
|
| 133 |
+
### Обработка ошибок
|
| 134 |
+
|
| 135 |
+
- Валидация входных данных
|
| 136 |
+
- Обработка некорректных логов без падения приложения
|
| 137 |
+
- Информативные сообщения об ошибках
|
| 138 |
+
|
| 139 |
+
## 🎯 Пример использования
|
| 140 |
+
|
| 141 |
+
### Пример входных логов:
|
| 142 |
+
|
| 143 |
+
```
|
| 144 |
+
2024-01-15 10:00:00 INFO Application started
|
| 145 |
+
2024-01-15 10:00:05 INFO Database connection established
|
| 146 |
+
2024-01-15 10:01:00 ERROR Connection timeout to external API
|
| 147 |
+
2024-01-15 10:01:05 ERROR Connection timeout to external API
|
| 148 |
+
2024-01-15 10:01:10 ERROR Connection timeout to external API
|
| 149 |
+
2024-01-15 10:01:15 WARNING High memory usage detected: 85%
|
| 150 |
+
2024-01-15 10:02:00 CRITICAL System crash detected
|
| 151 |
+
2024-01-15 10:02:01 INFO Application shutdown
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
### Пример выхода Agent 1 (структурированные данные):
|
| 155 |
+
|
| 156 |
+
```json
|
| 157 |
+
{
|
| 158 |
+
"events": [
|
| 159 |
+
{
|
| 160 |
+
"line_number": 1,
|
| 161 |
+
"timestamp": "2024-01-15 10:00:00",
|
| 162 |
+
"level": "INFO",
|
| 163 |
+
"message": "Application started",
|
| 164 |
+
"type": "SYSTEM"
|
| 165 |
+
},
|
| 166 |
+
...
|
| 167 |
+
],
|
| 168 |
+
"errors": [...],
|
| 169 |
+
"warnings": [...],
|
| 170 |
+
"statistics": {
|
| 171 |
+
"total_lines": 8,
|
| 172 |
+
"parsed_events": 8,
|
| 173 |
+
"errors": 3,
|
| 174 |
+
"warnings": 1,
|
| 175 |
+
...
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
### Пример выхода Agent 2 (аномалии):
|
| 181 |
+
|
| 182 |
+
```json
|
| 183 |
+
{
|
| 184 |
+
"anomalies": [
|
| 185 |
+
{
|
| 186 |
+
"type": "BURST_ERRORS",
|
| 187 |
+
"severity": "HIGH",
|
| 188 |
+
"description": "Обнаружен всплеск из 3 последовательных ошибок",
|
| 189 |
+
"count": 3,
|
| 190 |
+
...
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"type": "ERROR_BEFORE_CRASH",
|
| 194 |
+
"severity": "CRITICAL",
|
| 195 |
+
"description": "Обнаружен паттерн: ошибка перед возможным крашем системы",
|
| 196 |
+
...
|
| 197 |
+
}
|
| 198 |
+
],
|
| 199 |
+
...
|
| 200 |
+
}
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
### Пример выхода Agent 3 (рекомендации):
|
| 204 |
+
|
| 205 |
+
Markdown-отчёт с:
|
| 206 |
+
- Анализом первопричин
|
| 207 |
+
- Детальным описанием аномалий
|
| 208 |
+
- Приоритизированными рекомендациями
|
| 209 |
+
- Конкретными действиями для решения проблем
|
| 210 |
+
|
| 211 |
+
## 🔌 Расширяемость
|
| 212 |
+
|
| 213 |
+
Система разработана с учётом расширяемости:
|
| 214 |
+
|
| 215 |
+
- **Независимые агенты:** Каждый агент реализован как отдельный класс и может быть заменён без изменения остальных
|
| 216 |
+
- **Чёткий интерфейс:** Агенты взаимодействуют через стандартизированные форматы данных (JSON)
|
| 217 |
+
- **Добавление новых правил:** Легко добавить новые типы аномалий в `AnomalyDetectionAgent`
|
| 218 |
+
- **Кастомные парсеры:** Можно расширить `LogParserAgent` для поддержки новых форматов логов
|
| 219 |
+
|
| 220 |
+
## 📝 Лицензия
|
| 221 |
+
|
| 222 |
+
Этот проект создан в рамках технического задания для демонстрации мультиагентной архитектуры.
|
| 223 |
+
|
| 224 |
+
## 🤝 Вклад
|
| 225 |
+
|
| 226 |
+
Проект готов к расширению и улучшению. Возможные направления:
|
| 227 |
+
- Поддержка дополнительных форматов логов
|
| 228 |
+
- Интеграция с LLM для более глубокого анализа
|
| 229 |
+
- Поддержка потоковой обработки больших файлов
|
| 230 |
+
- Экспорт результатов в различные форматы
|
| 231 |
+
- Интеграция с системами мониторинга
|
__pycache__/app.cpython-314.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|
agents/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (428 Bytes). View file
|
|
|
agents/__pycache__/anomaly_agent.cpython-314.pyc
ADDED
|
Binary file (22.6 kB). View file
|
|
|
agents/__pycache__/parser_agent.cpython-314.pyc
ADDED
|
Binary file (12.1 kB). View file
|
|
|
agents/__pycache__/rca_agent.cpython-314.pyc
ADDED
|
Binary file (20.5 kB). View file
|
|
|
agents/anomaly_agent.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"""
|
app.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gradio приложение для мультиагентной системы анализа логов.
|
| 3 |
+
Оркестрирует работу трёх агентов и предоставляет веб-интерфейс.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import json
|
| 8 |
+
from typing import Tuple, Dict, Any
|
| 9 |
+
|
| 10 |
+
from agents import LogParserAgent, AnomalyDetectionAgent, RootCauseAgent
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class LogAnalysisOrchestrator:
|
| 14 |
+
"""Оркестратор для последовательного выполнения агентов."""
|
| 15 |
+
|
| 16 |
+
def __init__(self):
|
| 17 |
+
"""Инициализация оркестратора и агентов."""
|
| 18 |
+
self.parser_agent = LogParserAgent()
|
| 19 |
+
self.anomaly_agent = AnomalyDetectionAgent()
|
| 20 |
+
self.rca_agent = RootCauseAgent()
|
| 21 |
+
|
| 22 |
+
def analyze(self, raw_logs: str) -> Tuple[str, str, str]:
|
| 23 |
+
"""
|
| 24 |
+
Выполняет полный цикл анализа логов через всех агентов.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
raw_logs: Сырые логи в виде строки
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
Кортеж из трёх строк:
|
| 31 |
+
- JSON с распарсенными логами
|
| 32 |
+
- JSON с обнаруженными аномалиями
|
| 33 |
+
- Markdown с рекомендациями
|
| 34 |
+
"""
|
| 35 |
+
try:
|
| 36 |
+
# Валидация входных данных
|
| 37 |
+
if not raw_logs or not raw_logs.strip():
|
| 38 |
+
empty_json = json.dumps({"error": "Логи не предоставлены"}, ensure_ascii=False, indent=2)
|
| 39 |
+
return empty_json, empty_json, "# Ошибка\n\nЛоги не предоставлены для анализа."
|
| 40 |
+
|
| 41 |
+
# Agent 1: Парсинг логов
|
| 42 |
+
try:
|
| 43 |
+
structured_data = self.parser_agent.parse(raw_logs)
|
| 44 |
+
parsed_json = json.dumps(structured_data, ensure_ascii=False, indent=2)
|
| 45 |
+
except Exception as e:
|
| 46 |
+
error_msg = {"error": f"Ошибка парсинга логов: {str(e)}"}
|
| 47 |
+
parsed_json = json.dumps(error_msg, ensure_ascii=False, indent=2)
|
| 48 |
+
return parsed_json, parsed_json, f"# Ошибка\n\nОшибка на этапе парсинга: {str(e)}"
|
| 49 |
+
|
| 50 |
+
# Agent 2: Обнаружение аномалий
|
| 51 |
+
try:
|
| 52 |
+
anomaly_report = self.anomaly_agent.detect(structured_data)
|
| 53 |
+
anomalies_json = json.dumps(anomaly_report, ensure_ascii=False, indent=2)
|
| 54 |
+
except Exception as e:
|
| 55 |
+
error_msg = {"error": f"Ошибка обнаружения аномалий: {str(e)}"}
|
| 56 |
+
anomalies_json = json.dumps(error_msg, ensure_ascii=False, indent=2)
|
| 57 |
+
return parsed_json, anomalies_json, f"# Ошибка\n\nОшибка на этапе обнаружения аномалий: {str(e)}"
|
| 58 |
+
|
| 59 |
+
# Agent 3: Анализ первопричин и рекомендации
|
| 60 |
+
try:
|
| 61 |
+
recommendations_md = self.rca_agent.analyze(anomaly_report)
|
| 62 |
+
except Exception as e:
|
| 63 |
+
recommendations_md = f"# Ошибка\n\nОшибка на этапе анализа первопричин: {str(e)}"
|
| 64 |
+
|
| 65 |
+
return parsed_json, anomalies_json, recommendations_md
|
| 66 |
+
|
| 67 |
+
except Exception as e:
|
| 68 |
+
error_json = json.dumps({"error": f"Критическая ошибка: {str(e)}"}, ensure_ascii=False, indent=2)
|
| 69 |
+
return error_json, error_json, f"# Критическая ошибка\n\n{str(e)}"
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# Глобальный экземпляр оркестратора
|
| 73 |
+
orchestrator = LogAnalysisOrchestrator()
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def analyze_logs(raw_logs: str) -> Tuple[str, str, str]:
|
| 77 |
+
"""
|
| 78 |
+
Обёртка для Gradio интерфейса.
|
| 79 |
+
|
| 80 |
+
Args:
|
| 81 |
+
raw_logs: Сырые логи из интерфейса
|
| 82 |
+
|
| 83 |
+
Returns:
|
| 84 |
+
Кортеж результатов для отображения
|
| 85 |
+
"""
|
| 86 |
+
return orchestrator.analyze(raw_logs)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def create_interface():
|
| 90 |
+
"""Создаёт и настраивает Gradio интерфейс."""
|
| 91 |
+
|
| 92 |
+
# Описание интерфейса
|
| 93 |
+
description = """
|
| 94 |
+
# 🔍 Мультиагентная система анализа логов
|
| 95 |
+
|
| 96 |
+
Система использует трёх независимых агентов для анализа логов:
|
| 97 |
+
1. **Log Parser Agent** - парсит и структурирует логи
|
| 98 |
+
2. **Anomaly Detection Agent** - обнаруживает аномалии и паттерны
|
| 99 |
+
3. **Root Cause Agent** - анализирует первопричины и генерирует рекомендации
|
| 100 |
+
|
| 101 |
+
Вставьте логи в поле ниже или загрузите лог-файл, затем нажм��те "Анализировать".
|
| 102 |
+
"""
|
| 103 |
+
|
| 104 |
+
# Создание интерфейса
|
| 105 |
+
with gr.Blocks(title="Multi-Agent Log Analysis") as app:
|
| 106 |
+
gr.Markdown(description)
|
| 107 |
+
|
| 108 |
+
with gr.Row():
|
| 109 |
+
with gr.Column(scale=1):
|
| 110 |
+
log_input = gr.Textbox(
|
| 111 |
+
label="Логи для анализа",
|
| 112 |
+
placeholder="Вставьте логи здесь или используйте кнопку загрузки ниже...",
|
| 113 |
+
lines=15,
|
| 114 |
+
max_lines=30
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
upload_btn = gr.UploadButton(
|
| 118 |
+
"📁 Загрузить лог-файл",
|
| 119 |
+
file_types=[".log", ".txt"],
|
| 120 |
+
file_count="single"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
analyze_btn = gr.Button("🔍 Анализировать", variant="primary", size="lg")
|
| 124 |
+
|
| 125 |
+
# Обработчик загрузки файла
|
| 126 |
+
def load_file(file):
|
| 127 |
+
if file is None:
|
| 128 |
+
return ""
|
| 129 |
+
try:
|
| 130 |
+
with open(file.name, 'r', encoding='utf-8') as f:
|
| 131 |
+
content = f.read()
|
| 132 |
+
return content
|
| 133 |
+
except Exception as e:
|
| 134 |
+
return f"Ошибка чтения файла: {str(e)}"
|
| 135 |
+
|
| 136 |
+
upload_btn.upload(load_file, inputs=upload_btn, outputs=log_input)
|
| 137 |
+
|
| 138 |
+
with gr.Row():
|
| 139 |
+
with gr.Tabs():
|
| 140 |
+
with gr.Tab("📊 Распарсенные логи (JSON)"):
|
| 141 |
+
parsed_output = gr.JSON(
|
| 142 |
+
label="Структурированные данные"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
with gr.Tab("⚠️ Обнаруженные аномалии (JSON)"):
|
| 146 |
+
anomalies_output = gr.JSON(
|
| 147 |
+
label="Отчёт об аномалиях"
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
with gr.Tab("💡 Анализ и рекомендации (Markdown)"):
|
| 151 |
+
recommendations_output = gr.Markdown(
|
| 152 |
+
label="Рекомендации по устранению проблем"
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
# Примеры логов для тестирования
|
| 156 |
+
gr.Markdown("### 📝 Примеры логов для тестирования")
|
| 157 |
+
with gr.Row():
|
| 158 |
+
example_logs = [
|
| 159 |
+
"""2024-01-15 10:00:00 INFO Application started
|
| 160 |
+
2024-01-15 10:00:05 INFO Database connection established
|
| 161 |
+
2024-01-15 10:01:00 ERROR Connection timeout to external API
|
| 162 |
+
2024-01-15 10:01:05 ERROR Connection timeout to external API
|
| 163 |
+
2024-01-15 10:01:10 ERROR Connection timeout to external API
|
| 164 |
+
2024-01-15 10:01:15 WARNING High memory usage detected: 85%
|
| 165 |
+
2024-01-15 10:02:00 CRITICAL System crash detected
|
| 166 |
+
2024-01-15 10:02:01 INFO Application shutdown""",
|
| 167 |
+
|
| 168 |
+
"""[2024-01-15 14:30:00] INFO User authentication successful
|
| 169 |
+
[2024-01-15 14:30:01] DEBUG Request received: GET /api/users
|
| 170 |
+
[2024-01-15 14:30:02] ERROR Database query failed: connection lost
|
| 171 |
+
[2024-01-15 14:30:03] ERROR Database query failed: connection lost
|
| 172 |
+
[2024-01-15 14:30:04] ERROR Database query failed: connection lost
|
| 173 |
+
[2024-01-15 14:30:05] ERROR Database query failed: connection lost
|
| 174 |
+
[2024-01-15 14:30:06] WARNING Retrying database connection
|
| 175 |
+
[2024-01-15 14:30:10] INFO Database connection restored"""
|
| 176 |
+
]
|
| 177 |
+
|
| 178 |
+
example_btn1 = gr.Button("Загрузить пример 1", size="sm")
|
| 179 |
+
example_btn2 = gr.Button("Загрузить пример 2", size="sm")
|
| 180 |
+
|
| 181 |
+
example_btn1.click(
|
| 182 |
+
lambda: example_logs[0],
|
| 183 |
+
outputs=log_input
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
example_btn2.click(
|
| 187 |
+
lambda: example_logs[1],
|
| 188 |
+
outputs=log_input
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
# Связывание кнопки анализа с обработчиком
|
| 192 |
+
analyze_btn.click(
|
| 193 |
+
fn=analyze_logs,
|
| 194 |
+
inputs=log_input,
|
| 195 |
+
outputs=[parsed_output, anomalies_output, recommendations_output]
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
# Информация о системе
|
| 199 |
+
gr.Markdown("""
|
| 200 |
+
---
|
| 201 |
+
### ℹ️ Информация о системе
|
| 202 |
+
|
| 203 |
+
- **Архитектура:** Мультиагентная система (3 независимых агента)
|
| 204 |
+
- **Платформа:** Hugging Face Spaces
|
| 205 |
+
- **Интерфейс:** Gradio
|
| 206 |
+
- **Поддержка:** До 10,000 строк логов
|
| 207 |
+
""")
|
| 208 |
+
|
| 209 |
+
return app
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
# Создание и запуск приложения
|
| 213 |
+
if __name__ == "__main__":
|
| 214 |
+
app = create_interface()
|
| 215 |
+
app.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft())
|
| 216 |
+
else:
|
| 217 |
+
# Для Hugging Face Spaces
|
| 218 |
+
app = create_interface()
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0,<5.0.0
|
| 2 |
+
pillow>=11.0
|
run.bat
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo Starting Multi-Agent Log Analysis System...
|
| 3 |
+
echo.
|
| 4 |
+
python app.py
|
| 5 |
+
pause
|
space_config.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Конфигурация для Hugging Face Spaces
|
| 2 |
+
# Эта конфигурация также может быть указана в README.md через фронт-матер
|
| 3 |
+
|
| 4 |
+
sdk: gradio
|
| 5 |
+
sdk_version: 4.44.0
|
| 6 |
+
app_file: app.py
|
| 7 |
+
title: Multi-Agent Log Analysis System
|
| 8 |
+
emoji: 🔥
|
| 9 |
+
colorFrom: gray
|
| 10 |
+
colorTo: indigo
|
| 11 |
+
pinned: false
|
test_large_logs.py
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Генератор больших тестовых лог-файлов и скрипт для тестирования системы.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import random
|
| 6 |
+
import os
|
| 7 |
+
from datetime import datetime, timedelta
|
| 8 |
+
from agents import LogParserAgent, AnomalyDetectionAgent, RootCauseAgent
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
def generate_log_entry(timestamp, level, message_template, **kwargs):
|
| 12 |
+
"""Генерирует одну запись лога."""
|
| 13 |
+
message = message_template.format(**kwargs)
|
| 14 |
+
return f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n"
|
| 15 |
+
|
| 16 |
+
def generate_log_file_1():
|
| 17 |
+
"""Лог-файл 1: Обычные логи с редкими ошибками (3000 строк)"""
|
| 18 |
+
lines = []
|
| 19 |
+
base_time = datetime(2024, 1, 15, 10, 0, 0)
|
| 20 |
+
|
| 21 |
+
messages = [
|
| 22 |
+
"User {user_id} logged in from IP {ip}",
|
| 23 |
+
"Request GET /api/users/{user_id} processed successfully",
|
| 24 |
+
"Database query executed in {time}ms",
|
| 25 |
+
"Cache hit for key: {key}",
|
| 26 |
+
"Request POST /api/data processed in {time}ms",
|
| 27 |
+
"Session {session_id} created",
|
| 28 |
+
"File {filename} uploaded successfully",
|
| 29 |
+
"Processing job {job_id} started",
|
| 30 |
+
"Background task {task_id} completed",
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
error_messages = [
|
| 34 |
+
"Connection timeout to external API: {api_url}",
|
| 35 |
+
"Database connection lost, retrying...",
|
| 36 |
+
"Invalid token received from user {user_id}",
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
for i in range(3000):
|
| 40 |
+
timestamp = base_time + timedelta(seconds=i * 2)
|
| 41 |
+
|
| 42 |
+
if i % 100 == 0: # Каждая 100-я строка - ошибка
|
| 43 |
+
level = random.choice(["ERROR", "WARNING"])
|
| 44 |
+
template = random.choice(error_messages)
|
| 45 |
+
message = template.format(
|
| 46 |
+
api_url=f"api-{random.randint(1,5)}.example.com",
|
| 47 |
+
user_id=random.randint(1000, 9999),
|
| 48 |
+
)
|
| 49 |
+
else:
|
| 50 |
+
level = "INFO"
|
| 51 |
+
template = random.choice(messages)
|
| 52 |
+
message = template.format(
|
| 53 |
+
user_id=random.randint(1000, 9999),
|
| 54 |
+
ip=f"192.168.{random.randint(1,255)}.{random.randint(1,255)}",
|
| 55 |
+
time=random.randint(10, 500),
|
| 56 |
+
key=f"cache_key_{random.randint(1,100)}",
|
| 57 |
+
session_id=f"session_{random.randint(10000,99999)}",
|
| 58 |
+
filename=f"file_{random.randint(1,1000)}.txt",
|
| 59 |
+
job_id=random.randint(1000, 9999),
|
| 60 |
+
task_id=random.randint(10000, 99999),
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 64 |
+
|
| 65 |
+
return ''.join(lines)
|
| 66 |
+
|
| 67 |
+
def generate_log_file_2():
|
| 68 |
+
"""Лог-файл 2: Burst errors (5000 строк с всплеском ошибок)"""
|
| 69 |
+
lines = []
|
| 70 |
+
base_time = datetime(2024, 1, 15, 14, 0, 0)
|
| 71 |
+
|
| 72 |
+
for i in range(5000):
|
| 73 |
+
timestamp = base_time + timedelta(seconds=i)
|
| 74 |
+
|
| 75 |
+
# Всплеск ошибок между 2000-2050 строками
|
| 76 |
+
if 2000 <= i < 2050:
|
| 77 |
+
level = random.choice(["ERROR", "ERROR", "ERROR", "CRITICAL"])
|
| 78 |
+
message = f"Database connection failed: unable to connect to host db-{random.randint(1,3)}.internal"
|
| 79 |
+
elif 2050 <= i < 2060:
|
| 80 |
+
level = "WARNING"
|
| 81 |
+
message = f"High latency detected: {random.randint(5000, 15000)}ms response time"
|
| 82 |
+
else:
|
| 83 |
+
level = "INFO"
|
| 84 |
+
message = f"Request processed: {random.choice(['GET', 'POST', 'PUT'])} /api/v1/{random.choice(['users', 'data', 'files'])}"
|
| 85 |
+
|
| 86 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 87 |
+
|
| 88 |
+
return ''.join(lines)
|
| 89 |
+
|
| 90 |
+
def generate_log_file_3():
|
| 91 |
+
"""Лог-файл 3: Повторяющиеся ошибки (4000 строк)"""
|
| 92 |
+
lines = []
|
| 93 |
+
base_time = datetime(2024, 1, 15, 16, 0, 0)
|
| 94 |
+
|
| 95 |
+
repeated_error = "Authentication failed for user admin@example.com: invalid credentials"
|
| 96 |
+
|
| 97 |
+
for i in range(4000):
|
| 98 |
+
timestamp = base_time + timedelta(seconds=i * 3)
|
| 99 |
+
|
| 100 |
+
# Одна и та же ошибка повторяется каждые 50 строк
|
| 101 |
+
if i % 50 == 0:
|
| 102 |
+
level = "ERROR"
|
| 103 |
+
message = repeated_error
|
| 104 |
+
elif i % 75 == 0:
|
| 105 |
+
level = "WARNING"
|
| 106 |
+
message = f"Rate limit approaching: {random.randint(80, 95)}% of limit used"
|
| 107 |
+
else:
|
| 108 |
+
level = "INFO"
|
| 109 |
+
message = f"HTTP {random.choice([200, 200, 200, 201, 304])} {random.choice(['GET', 'POST'])} /api/{random.choice(['users', 'orders', 'products'])}"
|
| 110 |
+
|
| 111 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 112 |
+
|
| 113 |
+
return ''.join(lines)
|
| 114 |
+
|
| 115 |
+
def generate_log_file_4():
|
| 116 |
+
"""Лог-файл 4: Stack traces (3500 строк)"""
|
| 117 |
+
lines = []
|
| 118 |
+
base_time = datetime(2024, 1, 15, 18, 0, 0)
|
| 119 |
+
|
| 120 |
+
stack_trace = """Traceback (most recent call last):
|
| 121 |
+
File "/app/services/api.py", line {line}, in process_request
|
| 122 |
+
result = external_api.call(data)
|
| 123 |
+
File "/app/lib/external_api.py", line {line2}, in call
|
| 124 |
+
raise ConnectionError("Service unavailable")
|
| 125 |
+
ConnectionError: Service unavailable"""
|
| 126 |
+
|
| 127 |
+
for i in range(3500):
|
| 128 |
+
timestamp = base_time + timedelta(seconds=i * 2)
|
| 129 |
+
|
| 130 |
+
if i % 200 == 0:
|
| 131 |
+
level = "ERROR"
|
| 132 |
+
message = stack_trace.format(
|
| 133 |
+
line=random.randint(100, 500),
|
| 134 |
+
line2=random.randint(50, 300)
|
| 135 |
+
)
|
| 136 |
+
else:
|
| 137 |
+
level = random.choice(["INFO", "DEBUG"])
|
| 138 |
+
message = f"Processing request {random.randint(10000, 99999)}"
|
| 139 |
+
|
| 140 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 141 |
+
|
| 142 |
+
return ''.join(lines)
|
| 143 |
+
|
| 144 |
+
def generate_log_file_5():
|
| 145 |
+
"""Лог-файл 5: Временные всплески (4500 строк)"""
|
| 146 |
+
lines = []
|
| 147 |
+
base_time = datetime(2024, 1, 16, 8, 0, 0)
|
| 148 |
+
|
| 149 |
+
for i in range(4500):
|
| 150 |
+
# Группируем по минутам для создания всплесков
|
| 151 |
+
timestamp = base_time + timedelta(minutes=i // 60, seconds=i % 60)
|
| 152 |
+
|
| 153 |
+
# Всплески в определённые минуты
|
| 154 |
+
minute = (i // 60) % 60
|
| 155 |
+
if minute in [5, 15, 25, 35, 45]:
|
| 156 |
+
# Много событий в эти минуты
|
| 157 |
+
level = random.choice(["INFO", "INFO", "INFO", "WARNING", "ERROR"])
|
| 158 |
+
message = f"High traffic: {random.randint(100, 1000)} requests/min"
|
| 159 |
+
else:
|
| 160 |
+
level = "INFO"
|
| 161 |
+
message = f"Normal traffic: {random.randint(10, 50)} requests/min"
|
| 162 |
+
|
| 163 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 164 |
+
|
| 165 |
+
return ''.join(lines)
|
| 166 |
+
|
| 167 |
+
def generate_log_file_6():
|
| 168 |
+
"""Лог-файл 6: Ошибка перед крашем (3000 строк)"""
|
| 169 |
+
lines = []
|
| 170 |
+
base_time = datetime(2024, 1, 16, 12, 0, 0)
|
| 171 |
+
|
| 172 |
+
for i in range(3000):
|
| 173 |
+
timestamp = base_time + timedelta(seconds=i)
|
| 174 |
+
|
| 175 |
+
# Последние 50 строк - критические ошибки
|
| 176 |
+
if i >= 2950:
|
| 177 |
+
level = random.choice(["CRITICAL", "ERROR"])
|
| 178 |
+
messages = [
|
| 179 |
+
"Out of memory: cannot allocate additional resources",
|
| 180 |
+
"Fatal error: database connection pool exhausted",
|
| 181 |
+
"Critical: unable to process requests, system overloaded",
|
| 182 |
+
"ERROR: Service unavailable, shutting down",
|
| 183 |
+
]
|
| 184 |
+
message = random.choice(messages)
|
| 185 |
+
elif i >= 2900:
|
| 186 |
+
level = "ERROR"
|
| 187 |
+
message = f"System resource exhaustion detected: memory usage {random.randint(95, 99)}%"
|
| 188 |
+
else:
|
| 189 |
+
level = random.choice(["INFO", "DEBUG"])
|
| 190 |
+
message = f"System operation: {random.choice(['cache_update', 'db_query', 'api_call'])}"
|
| 191 |
+
|
| 192 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 193 |
+
|
| 194 |
+
return ''.join(lines)
|
| 195 |
+
|
| 196 |
+
def generate_log_file_7():
|
| 197 |
+
"""Лог-файл 7: Разнообразные форматы логов (4000 строк)"""
|
| 198 |
+
lines = []
|
| 199 |
+
base_time = datetime(2024, 1, 16, 14, 30, 0)
|
| 200 |
+
|
| 201 |
+
formats = [
|
| 202 |
+
"{timestamp} [{level}] {message}",
|
| 203 |
+
"[{timestamp}] {level}: {message}",
|
| 204 |
+
"{timestamp} {level} - {message}",
|
| 205 |
+
]
|
| 206 |
+
|
| 207 |
+
for i in range(4000):
|
| 208 |
+
timestamp = base_time + timedelta(seconds=i * 2)
|
| 209 |
+
level = random.choice(["INFO", "WARNING", "ERROR", "DEBUG"])
|
| 210 |
+
|
| 211 |
+
if level == "ERROR" and i % 100 == 0:
|
| 212 |
+
message = f"Error processing transaction {random.randint(100000, 999999)}"
|
| 213 |
+
else:
|
| 214 |
+
message = f"Event {i}: {random.choice(['user_action', 'system_check', 'data_sync'])}"
|
| 215 |
+
|
| 216 |
+
fmt = random.choice(formats)
|
| 217 |
+
if fmt.startswith("["):
|
| 218 |
+
lines.append(fmt.format(
|
| 219 |
+
timestamp=timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
| 220 |
+
level=level,
|
| 221 |
+
message=message
|
| 222 |
+
) + "\n")
|
| 223 |
+
else:
|
| 224 |
+
lines.append(fmt.format(
|
| 225 |
+
timestamp=timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
| 226 |
+
level=level,
|
| 227 |
+
message=message
|
| 228 |
+
) + "\n")
|
| 229 |
+
|
| 230 |
+
return ''.join(lines)
|
| 231 |
+
|
| 232 |
+
def generate_log_file_8():
|
| 233 |
+
"""Лог-файл 8: Смешанные паттерны (5000 строк)"""
|
| 234 |
+
lines = []
|
| 235 |
+
base_time = datetime(2024, 1, 17, 9, 0, 0)
|
| 236 |
+
|
| 237 |
+
for i in range(5000):
|
| 238 |
+
timestamp = base_time + timedelta(seconds=i)
|
| 239 |
+
|
| 240 |
+
# Разные паттерны в разных секциях
|
| 241 |
+
if 1000 <= i < 1100:
|
| 242 |
+
# Burst errors
|
| 243 |
+
level = "ERROR"
|
| 244 |
+
message = f"API endpoint /api/data failed: {random.choice(['timeout', '500', 'connection refused'])}"
|
| 245 |
+
elif 2000 <= i < 2100 and i % 10 == 0:
|
| 246 |
+
# Repeated errors
|
| 247 |
+
level = "ERROR"
|
| 248 |
+
message = "Validation error: email format is invalid"
|
| 249 |
+
elif 3000 <= i < 3050:
|
| 250 |
+
# Stack traces
|
| 251 |
+
level = "ERROR"
|
| 252 |
+
message = f"Exception in handler: ValueError at line {random.randint(1, 500)}"
|
| 253 |
+
elif i >= 4900:
|
| 254 |
+
# Error before crash
|
| 255 |
+
level = random.choice(["CRITICAL", "ERROR"])
|
| 256 |
+
message = "System failure: critical service unavailable"
|
| 257 |
+
else:
|
| 258 |
+
level = "INFO"
|
| 259 |
+
message = f"Normal operation: {random.choice(['request', 'response', 'cache', 'db'])} processed"
|
| 260 |
+
|
| 261 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 262 |
+
|
| 263 |
+
return ''.join(lines)
|
| 264 |
+
|
| 265 |
+
def generate_log_file_9():
|
| 266 |
+
"""Лог-файл 9: Web server logs format (4500 строк)"""
|
| 267 |
+
lines = []
|
| 268 |
+
base_time = datetime(2024, 1, 17, 15, 0, 0)
|
| 269 |
+
|
| 270 |
+
ips = [f"192.168.{x}.{y}" for x in range(1, 10) for y in range(1, 50)]
|
| 271 |
+
|
| 272 |
+
for i in range(4500):
|
| 273 |
+
timestamp = base_time + timedelta(seconds=i)
|
| 274 |
+
ip = random.choice(ips)
|
| 275 |
+
method = random.choice(["GET", "POST", "PUT", "DELETE"])
|
| 276 |
+
endpoint = random.choice(["/api/users", "/api/orders", "/api/products", "/static/css", "/static/js"])
|
| 277 |
+
status = random.choice([200, 200, 200, 201, 404, 500, 503])
|
| 278 |
+
|
| 279 |
+
if status >= 500:
|
| 280 |
+
level = "ERROR"
|
| 281 |
+
elif status >= 400:
|
| 282 |
+
level = "WARNING"
|
| 283 |
+
else:
|
| 284 |
+
level = "INFO"
|
| 285 |
+
|
| 286 |
+
message = f'{ip} - - [{timestamp.strftime("%d/%b/%Y:%H:%M:%S")}] "{method} {endpoint} HTTP/1.1" {status} {random.randint(100, 5000)}'
|
| 287 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 288 |
+
|
| 289 |
+
return ''.join(lines)
|
| 290 |
+
|
| 291 |
+
def generate_log_file_10():
|
| 292 |
+
"""Лог-файл 10: Application logs с метаданными (4000 строк)"""
|
| 293 |
+
lines = []
|
| 294 |
+
base_time = datetime(2024, 1, 18, 10, 0, 0)
|
| 295 |
+
|
| 296 |
+
for i in range(4000):
|
| 297 |
+
timestamp = base_time + timedelta(seconds=i * 2)
|
| 298 |
+
|
| 299 |
+
# Периодические проблемы
|
| 300 |
+
if i % 300 == 0:
|
| 301 |
+
level = "ERROR"
|
| 302 |
+
message = f"Service health check failed: service-{random.randint(1, 5)}.internal is down"
|
| 303 |
+
elif i % 150 == 0:
|
| 304 |
+
level = "WARNING"
|
| 305 |
+
message = f"Performance degradation: p95 latency increased to {random.randint(1000, 5000)}ms"
|
| 306 |
+
elif 3500 <= i < 3600:
|
| 307 |
+
# Проблемы перед концом
|
| 308 |
+
level = random.choice(["ERROR", "WARNING"])
|
| 309 |
+
message = f"Resource constraint: {random.choice(['CPU', 'Memory', 'Disk'])} usage critical"
|
| 310 |
+
else:
|
| 311 |
+
level = "INFO"
|
| 312 |
+
message = f"[thread-{random.randint(1, 20)}] Processing job {random.randint(10000, 99999)}: status={random.choice(['completed', 'in_progress'])}"
|
| 313 |
+
|
| 314 |
+
lines.append(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} {level} {message}\n")
|
| 315 |
+
|
| 316 |
+
return ''.join(lines)
|
| 317 |
+
|
| 318 |
+
def test_log_file(content, file_num):
|
| 319 |
+
"""Тестирует обработку одного лог-файла."""
|
| 320 |
+
print(f"\n{'='*60}")
|
| 321 |
+
print(f"Testing log file {file_num}")
|
| 322 |
+
print(f"{'='*60}")
|
| 323 |
+
|
| 324 |
+
# Подсчёт строк
|
| 325 |
+
line_count = len(content.split('\n'))
|
| 326 |
+
print(f"Lines in file: {line_count}")
|
| 327 |
+
|
| 328 |
+
# Замер времени
|
| 329 |
+
start_time = time.time()
|
| 330 |
+
|
| 331 |
+
# Agent 1: Парсинг
|
| 332 |
+
parser = LogParserAgent()
|
| 333 |
+
parsed_start = time.time()
|
| 334 |
+
structured_data = parser.parse(content)
|
| 335 |
+
parsed_time = time.time() - parsed_start
|
| 336 |
+
|
| 337 |
+
events_count = len(structured_data.get('events', []))
|
| 338 |
+
errors_count = len(structured_data.get('errors', []))
|
| 339 |
+
warnings_count = len(structured_data.get('warnings', []))
|
| 340 |
+
|
| 341 |
+
print(f"\n[OK] Agent 1 (Parser): {parsed_time:.2f} sec")
|
| 342 |
+
print(f" - Events: {events_count}")
|
| 343 |
+
print(f" - Errors: {errors_count}")
|
| 344 |
+
print(f" - Warnings: {warnings_count}")
|
| 345 |
+
|
| 346 |
+
# Agent 2: Обнаружение аномалий
|
| 347 |
+
anomaly_start = time.time()
|
| 348 |
+
anomaly_agent = AnomalyDetectionAgent()
|
| 349 |
+
anomaly_report = anomaly_agent.detect(structured_data)
|
| 350 |
+
anomaly_time = time.time() - anomaly_start
|
| 351 |
+
|
| 352 |
+
anomalies_count = len(anomaly_report.get('anomalies', []))
|
| 353 |
+
print(f"\n[OK] Agent 2 (Anomaly Detection): {anomaly_time:.2f} sec")
|
| 354 |
+
print(f" - Anomalies detected: {anomalies_count}")
|
| 355 |
+
|
| 356 |
+
if anomalies_count > 0:
|
| 357 |
+
by_type = anomaly_report.get('statistics', {}).get('by_type', {})
|
| 358 |
+
for anomaly_type, count in by_type.items():
|
| 359 |
+
print(f" - {anomaly_type}: {count}")
|
| 360 |
+
|
| 361 |
+
# Agent 3: Анализ первопричин
|
| 362 |
+
rca_start = time.time()
|
| 363 |
+
rca_agent = RootCauseAgent()
|
| 364 |
+
recommendations = rca_agent.analyze(anomaly_report)
|
| 365 |
+
rca_time = time.time() - rca_start
|
| 366 |
+
|
| 367 |
+
print(f"\n[OK] Agent 3 (Root Cause Analysis): {rca_time:.2f} sec")
|
| 368 |
+
print(f" - Report size: {len(recommendations)} characters")
|
| 369 |
+
|
| 370 |
+
total_time = time.time() - start_time
|
| 371 |
+
print(f"\n[TIME] Total processing time: {total_time:.2f} sec")
|
| 372 |
+
print(f" Speed: {line_count / total_time:.0f} lines/sec")
|
| 373 |
+
|
| 374 |
+
return {
|
| 375 |
+
'file_num': file_num,
|
| 376 |
+
'lines': line_count,
|
| 377 |
+
'events': events_count,
|
| 378 |
+
'errors': errors_count,
|
| 379 |
+
'warnings': warnings_count,
|
| 380 |
+
'anomalies': anomalies_count,
|
| 381 |
+
'parsed_time': parsed_time,
|
| 382 |
+
'anomaly_time': anomaly_time,
|
| 383 |
+
'rca_time': rca_time,
|
| 384 |
+
'total_time': total_time
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
def main():
|
| 388 |
+
"""Главная функция для генерации и тестирования."""
|
| 389 |
+
print("=" * 60)
|
| 390 |
+
print("ГЕНЕРАЦИЯ И ТЕСТИРОВАНИЕ БОЛЬШИХ ЛОГ-ФАЙЛОВ")
|
| 391 |
+
print("=" * 60)
|
| 392 |
+
|
| 393 |
+
# Создаём папку для тестовых файлов
|
| 394 |
+
test_dir = "test_logs"
|
| 395 |
+
os.makedirs(test_dir, exist_ok=True)
|
| 396 |
+
|
| 397 |
+
# Генераторы лог-файлов
|
| 398 |
+
generators = [
|
| 399 |
+
("normal_logs.log", generate_log_file_1),
|
| 400 |
+
("burst_errors.log", generate_log_file_2),
|
| 401 |
+
("repeated_errors.log", generate_log_file_3),
|
| 402 |
+
("stack_traces.log", generate_log_file_4),
|
| 403 |
+
("temporal_spikes.log", generate_log_file_5),
|
| 404 |
+
("error_before_crash.log", generate_log_file_6),
|
| 405 |
+
("mixed_formats.log", generate_log_file_7),
|
| 406 |
+
("mixed_patterns.log", generate_log_file_8),
|
| 407 |
+
("web_server.log", generate_log_file_9),
|
| 408 |
+
("application_metadata.log", generate_log_file_10),
|
| 409 |
+
]
|
| 410 |
+
|
| 411 |
+
# Генерируем файлы
|
| 412 |
+
print(f"\n[GENERATING] Generating {len(generators)} test files...")
|
| 413 |
+
files_data = []
|
| 414 |
+
|
| 415 |
+
for filename, generator in generators:
|
| 416 |
+
filepath = os.path.join(test_dir, filename)
|
| 417 |
+
print(f" Generating: {filename}...", end=" ")
|
| 418 |
+
content = generator()
|
| 419 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 420 |
+
f.write(content)
|
| 421 |
+
|
| 422 |
+
line_count = len(content.split('\n'))
|
| 423 |
+
file_size = len(content.encode('utf-8')) / 1024 # KB
|
| 424 |
+
print(f"OK ({line_count} lines, {file_size:.1f} KB)")
|
| 425 |
+
files_data.append((filepath, content))
|
| 426 |
+
|
| 427 |
+
print(f"\n[SUCCESS] All files created in '{test_dir}' folder")
|
| 428 |
+
|
| 429 |
+
# Тестируем каждый файл
|
| 430 |
+
print(f"\n[TESTING] Starting tests...")
|
| 431 |
+
results = []
|
| 432 |
+
|
| 433 |
+
for i, (filepath, content) in enumerate(files_data, 1):
|
| 434 |
+
result = test_log_file(content, i)
|
| 435 |
+
results.append(result)
|
| 436 |
+
|
| 437 |
+
# Итоговая статистика
|
| 438 |
+
print(f"\n\n{'='*60}")
|
| 439 |
+
print("SUMMARY STATISTICS")
|
| 440 |
+
print(f"{'='*60}")
|
| 441 |
+
print(f"\n{'#':<3} {'Lines':<8} {'Time (sec)':<12} {'Lines/sec':<12} {'Anomalies':<10}")
|
| 442 |
+
print("-" * 60)
|
| 443 |
+
|
| 444 |
+
total_lines = 0
|
| 445 |
+
total_time = 0
|
| 446 |
+
|
| 447 |
+
for result in results:
|
| 448 |
+
speed = result['lines'] / result['total_time'] if result['total_time'] > 0 else 0
|
| 449 |
+
print(f"{result['file_num']:<3} {result['lines']:<8} {result['total_time']:<12.2f} {speed:<12.0f} {result['anomalies']:<10}")
|
| 450 |
+
total_lines += result['lines']
|
| 451 |
+
total_time += result['total_time']
|
| 452 |
+
|
| 453 |
+
print("-" * 60)
|
| 454 |
+
avg_speed = total_lines / total_time if total_time > 0 else 0
|
| 455 |
+
print(f"{'TOTAL':<3} {total_lines:<8} {total_time:<12.2f} {avg_speed:<12.0f}")
|
| 456 |
+
|
| 457 |
+
print(f"\n[SUCCESS] Testing completed!")
|
| 458 |
+
print(f" Total processed: {total_lines} lines in {total_time:.2f} seconds")
|
| 459 |
+
print(f" Average speed: {avg_speed:.0f} lines/sec")
|
| 460 |
+
|
| 461 |
+
# Проверка производительности
|
| 462 |
+
if total_time > 100: # Если больше 100 секунд для всех файлов
|
| 463 |
+
print(f"\n[WARNING] Total processing time exceeds 100 seconds")
|
| 464 |
+
else:
|
| 465 |
+
print(f"\n[OK] Performance is within normal range (<100 sec for all files)")
|
| 466 |
+
|
| 467 |
+
if __name__ == "__main__":
|
| 468 |
+
main()
|
test_logs/application_metadata.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/burst_errors.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/error_before_crash.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/mixed_formats.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/mixed_patterns.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/normal_logs.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/repeated_errors.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/stack_traces.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/temporal_spikes.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test_logs/web_server.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|