bb50639ed4
- Создан основной README.md с описанием проекта - Добавлена подробная документация в папке docs/ - Создан файл LICENSE (MIT) - Обновлен .gitignore - Добавлена документация по безопасности с генерацией ключей - Включены примеры конфигураций и устранение неполадок Автор: Сергей Антропов Сайт: https://devops.org.ru
914 lines
31 KiB
Markdown
914 lines
31 KiB
Markdown
# WebSocket API LogBoard+
|
|
|
|
**Автор:** Сергей Антропов
|
|
**Сайт:** https://devops.org.ru
|
|
|
|
## Содержание
|
|
|
|
1. [Обзор WebSocket API](#обзор-websocket-api)
|
|
2. [Аутентификация](#аутентификация)
|
|
3. [Endpoints](#endpoints)
|
|
4. [Примеры использования](#примеры-использования)
|
|
5. [Обработка ошибок](#обработка-ошибок)
|
|
6. [Лучшие практики](#лучшие-практики)
|
|
|
|
## Обзор WebSocket API
|
|
|
|
WebSocket API LogBoard+ предоставляет возможность получения логов Docker контейнеров в реальном времени. Это позволяет создавать интерактивные приложения для мониторинга и отладки микросервисов.
|
|
|
|
### Преимущества WebSocket
|
|
|
|
- **Real-time данные** - мгновенное получение новых логов
|
|
- **Эффективность** - меньше накладных расходов по сравнению с polling
|
|
- **Масштабируемость** - поддержка множественных соединений
|
|
- **Фильтрация** - получение логов только нужных контейнеров
|
|
|
|
### Поддерживаемые протоколы
|
|
|
|
- **ws://** - для HTTP соединений
|
|
- **wss://** - для HTTPS соединений (рекомендуется для продакшена)
|
|
|
|
## Аутентификация
|
|
|
|
Все WebSocket endpoints требуют JWT токен для аутентификации. Токен передается как параметр запроса `token`.
|
|
|
|
### Получение токена
|
|
|
|
```bash
|
|
# Получение JWT токена через REST API
|
|
curl -X POST "http://localhost:9001/api/auth/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username":"admin","password":"your-password"}'
|
|
```
|
|
|
|
### Использование токена
|
|
|
|
```javascript
|
|
const token = "your-jwt-token";
|
|
const ws = new WebSocket(`ws://localhost:9001/ws/logs/container-id?token=${token}`);
|
|
```
|
|
|
|
## Endpoints
|
|
|
|
### ws://host:port/ws/logs/{container_id}
|
|
|
|
Получение логов отдельного контейнера в реальном времени.
|
|
|
|
#### Параметры
|
|
|
|
| Параметр | Тип | Обязательный | Описание |
|
|
|----------|-----|--------------|----------|
|
|
| `container_id` | string | Да | ID контейнера (первые 12 символов) |
|
|
| `token` | string | Да | JWT токен аутентификации |
|
|
| `tail` | number | Нет | Количество начальных строк (по умолчанию 500) |
|
|
| `service` | string | Нет | Имя сервиса (для информации) |
|
|
| `project` | string | Нет | Имя проекта (для информации) |
|
|
|
|
#### Пример подключения
|
|
|
|
```javascript
|
|
const containerId = "abc123def456";
|
|
const token = "your-jwt-token";
|
|
|
|
const ws = new WebSocket(`ws://localhost:9001/ws/logs/${containerId}?token=${token}&tail=100`);
|
|
|
|
ws.onopen = function() {
|
|
console.log('WebSocket соединение установлено');
|
|
};
|
|
|
|
ws.onmessage = function(event) {
|
|
console.log('Получены логи:', event.data);
|
|
};
|
|
|
|
ws.onerror = function(error) {
|
|
console.error('WebSocket ошибка:', error);
|
|
};
|
|
|
|
ws.onclose = function(event) {
|
|
console.log('WebSocket соединение закрыто:', event.code, event.reason);
|
|
};
|
|
```
|
|
|
|
#### Формат сообщений
|
|
|
|
При подключении сервер отправляет приветственное сообщение:
|
|
|
|
```
|
|
Connected to container: myproject_web_1
|
|
```
|
|
|
|
Затем отправляются логи в формате:
|
|
|
|
```
|
|
2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker processes
|
|
2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker process 1234
|
|
```
|
|
|
|
### ws://host:port/ws/fan/{service_name}
|
|
|
|
Получение логов всех реплик сервиса Docker Compose (fan-in).
|
|
|
|
#### Параметры
|
|
|
|
| Параметр | Тип | Обязательный | Описание |
|
|
|----------|-----|--------------|----------|
|
|
| `service_name` | string | Да | Имя сервиса Docker Compose |
|
|
| `token` | string | Да | JWT токен аутентификации |
|
|
| `tail` | number | Нет | Количество начальных строк (по умолчанию 500) |
|
|
| `project` | string | Нет | Имя проекта (для фильтрации) |
|
|
|
|
#### Пример подключения
|
|
|
|
```javascript
|
|
const serviceName = "web";
|
|
const token = "your-jwt-token";
|
|
|
|
const ws = new WebSocket(`ws://localhost:9001/ws/fan/${serviceName}?token=${token}&tail=100`);
|
|
|
|
ws.onmessage = function(event) {
|
|
// Логи приходят с префиксом ID контейнера
|
|
console.log('Логи сервиса:', event.data);
|
|
};
|
|
```
|
|
|
|
#### Формат сообщений
|
|
|
|
Логи приходят с префиксом короткого ID контейнера:
|
|
|
|
```
|
|
[abc123de] 2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker processes
|
|
[def456gh] 2024-01-15T10:30:16.123456789Z 2024/01/15 10:30:16 [notice] 1#1: start worker processes
|
|
```
|
|
|
|
### ws://host:port/ws/fan_group
|
|
|
|
Получение логов группы сервисов одновременно.
|
|
|
|
#### Параметры
|
|
|
|
| Параметр | Тип | Обязательный | Описание |
|
|
|----------|-----|--------------|----------|
|
|
| `services` | string | Да | Имена сервисов через запятую |
|
|
| `token` | string | Да | JWT токен аутентификации |
|
|
| `tail` | number | Нет | Количество начальных строк (по умолчанию 500) |
|
|
| `project` | string | Нет | Имя проекта (для фильтрации) |
|
|
|
|
#### Пример подключения
|
|
|
|
```javascript
|
|
const services = "web,db,redis";
|
|
const token = "your-jwt-token";
|
|
|
|
const ws = new WebSocket(`ws://localhost:9001/ws/fan_group?services=${services}&token=${token}&tail=100`);
|
|
|
|
ws.onmessage = function(event) {
|
|
// Логи приходят с префиксом ID контейнера и имени сервиса
|
|
console.log('Логи группы сервисов:', event.data);
|
|
};
|
|
```
|
|
|
|
#### Формат сообщений
|
|
|
|
Логи приходят с префиксом ID контейнера и имени сервиса:
|
|
|
|
```
|
|
[abc123de web] 2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker processes
|
|
[def456gh db] 2024-01-15T10:30:16.123456789Z 2024/01/15 10:30:16 [info] database system is ready to accept connections
|
|
[ghi789jk redis] 2024-01-15T10:30:17.123456789Z 2024/01/15 10:30:17 [notice] Ready to accept connections
|
|
```
|
|
|
|
## Примеры использования
|
|
|
|
### JavaScript (Browser)
|
|
|
|
```javascript
|
|
class LogBoardWebSocket {
|
|
constructor(baseUrl, token) {
|
|
this.baseUrl = baseUrl;
|
|
this.token = token;
|
|
this.connections = new Map();
|
|
}
|
|
|
|
// Подключение к логам контейнера
|
|
connectToContainer(containerId, options = {}) {
|
|
const { tail = 100, onMessage, onError, onClose } = options;
|
|
|
|
const url = `ws://${this.baseUrl.replace('http://', '')}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`;
|
|
const ws = new WebSocket(url);
|
|
|
|
ws.onopen = () => {
|
|
console.log(`Подключение к контейнеру ${containerId} установлено`);
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
if (onMessage) onMessage(event.data);
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error(`Ошибка WebSocket для контейнера ${containerId}:`, error);
|
|
if (onError) onError(error);
|
|
};
|
|
|
|
ws.onclose = (event) => {
|
|
console.log(`Соединение с контейнером ${containerId} закрыто:`, event.code, event.reason);
|
|
if (onClose) onClose(event);
|
|
};
|
|
|
|
this.connections.set(containerId, ws);
|
|
return ws;
|
|
}
|
|
|
|
// Подключение к логам сервиса
|
|
connectToService(serviceName, options = {}) {
|
|
const { tail = 100, project, onMessage, onError, onClose } = options;
|
|
|
|
let url = `ws://${this.baseUrl.replace('http://', '')}/ws/fan/${serviceName}?token=${this.token}&tail=${tail}`;
|
|
if (project) url += `&project=${project}`;
|
|
|
|
const ws = new WebSocket(url);
|
|
|
|
ws.onopen = () => {
|
|
console.log(`Подключение к сервису ${serviceName} установлено`);
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
if (onMessage) onMessage(event.data);
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error(`Ошибка WebSocket для сервиса ${serviceName}:`, error);
|
|
if (onError) onError(error);
|
|
};
|
|
|
|
ws.onclose = (event) => {
|
|
console.log(`Соединение с сервисом ${serviceName} закрыто:`, event.code, event.reason);
|
|
if (onClose) onClose(event);
|
|
};
|
|
|
|
this.connections.set(serviceName, ws);
|
|
return ws;
|
|
}
|
|
|
|
// Подключение к группе сервисов
|
|
connectToServiceGroup(services, options = {}) {
|
|
const { tail = 100, project, onMessage, onError, onClose } = options;
|
|
|
|
let url = `ws://${this.baseUrl.replace('http://', '')}/ws/fan_group?services=${services}&token=${this.token}&tail=${tail}`;
|
|
if (project) url += `&project=${project}`;
|
|
|
|
const ws = new WebSocket(url);
|
|
|
|
ws.onopen = () => {
|
|
console.log(`Подключение к группе сервисов ${services} установлено`);
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
if (onMessage) onMessage(event.data);
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error(`Ошибка WebSocket для группы сервисов ${services}:`, error);
|
|
if (onError) onError(error);
|
|
};
|
|
|
|
ws.onclose = (event) => {
|
|
console.log(`Соединение с группой сервисов ${services} закрыто:`, event.code, event.reason);
|
|
if (onClose) onClose(event);
|
|
};
|
|
|
|
this.connections.set(`group:${services}`, ws);
|
|
return ws;
|
|
}
|
|
|
|
// Закрытие соединения
|
|
disconnect(identifier) {
|
|
const ws = this.connections.get(identifier);
|
|
if (ws) {
|
|
ws.close();
|
|
this.connections.delete(identifier);
|
|
}
|
|
}
|
|
|
|
// Закрытие всех соединений
|
|
disconnectAll() {
|
|
for (const [identifier, ws] of this.connections) {
|
|
ws.close();
|
|
}
|
|
this.connections.clear();
|
|
}
|
|
}
|
|
|
|
// Использование
|
|
async function main() {
|
|
// Получение токена
|
|
const loginResponse = await fetch('http://localhost:9001/api/auth/login', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
username: 'admin',
|
|
password: 'your-password'
|
|
})
|
|
});
|
|
const {access_token} = await loginResponse.json();
|
|
|
|
// Создание WebSocket клиента
|
|
const wsClient = new LogBoardWebSocket('http://localhost:9001', access_token);
|
|
|
|
// Подключение к контейнеру
|
|
wsClient.connectToContainer('abc123def456', {
|
|
tail: 100,
|
|
onMessage: (data) => {
|
|
console.log('Логи контейнера:', data);
|
|
// Добавление в UI
|
|
appendToLogView(data);
|
|
},
|
|
onError: (error) => {
|
|
console.error('Ошибка:', error);
|
|
}
|
|
});
|
|
|
|
// Подключение к сервису
|
|
wsClient.connectToService('web', {
|
|
tail: 100,
|
|
onMessage: (data) => {
|
|
console.log('Логи сервиса:', data);
|
|
// Парсинг префикса для группировки
|
|
const match = data.match(/^\[([a-f0-9]+)\]\s+(.+)$/);
|
|
if (match) {
|
|
const [, containerId, logMessage] = match;
|
|
appendToServiceLogView(containerId, logMessage);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Подключение к группе сервисов
|
|
wsClient.connectToServiceGroup('web,db,redis', {
|
|
tail: 100,
|
|
onMessage: (data) => {
|
|
console.log('Логи группы:', data);
|
|
// Парсинг префикса для группировки
|
|
const match = data.match(/^\[([a-f0-9]+)\s+(\w+)\]\s+(.+)$/);
|
|
if (match) {
|
|
const [, containerId, serviceName, logMessage] = match;
|
|
appendToGroupLogView(serviceName, containerId, logMessage);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Функции для обновления UI
|
|
function appendToLogView(message) {
|
|
const logContainer = document.getElementById('log-container');
|
|
const logLine = document.createElement('div');
|
|
logLine.className = 'log-line';
|
|
logLine.textContent = message;
|
|
logContainer.appendChild(logLine);
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
|
|
function appendToServiceLogView(containerId, message) {
|
|
// Реализация для отображения логов сервиса
|
|
}
|
|
|
|
function appendToGroupLogView(serviceName, containerId, message) {
|
|
// Реализация для отображения логов группы сервисов
|
|
}
|
|
|
|
main().catch(console.error);
|
|
```
|
|
|
|
### Python
|
|
|
|
```python
|
|
import asyncio
|
|
import websockets
|
|
import json
|
|
import requests
|
|
|
|
class LogBoardWebSocket:
|
|
def __init__(self, base_url, token):
|
|
self.base_url = base_url.replace('http://', 'ws://')
|
|
self.token = token
|
|
self.connections = {}
|
|
|
|
async def connect_to_container(self, container_id, tail=100, callback=None):
|
|
"""Подключение к логам контейнера"""
|
|
uri = f"{self.base_url}/ws/logs/{container_id}?token={self.token}&tail={tail}"
|
|
|
|
try:
|
|
async with websockets.connect(uri) as websocket:
|
|
print(f"Подключение к контейнеру {container_id} установлено")
|
|
|
|
async for message in websocket:
|
|
if callback:
|
|
await callback(container_id, message)
|
|
else:
|
|
print(f"[{container_id}] {message}")
|
|
|
|
except websockets.exceptions.ConnectionClosed:
|
|
print(f"Соединение с контейнером {container_id} закрыто")
|
|
except Exception as e:
|
|
print(f"Ошибка подключения к контейнеру {container_id}: {e}")
|
|
|
|
async def connect_to_service(self, service_name, tail=100, project=None, callback=None):
|
|
"""Подключение к логам сервиса"""
|
|
uri = f"{self.base_url}/ws/fan/{service_name}?token={self.token}&tail={tail}"
|
|
if project:
|
|
uri += f"&project={project}"
|
|
|
|
try:
|
|
async with websockets.connect(uri) as websocket:
|
|
print(f"Подключение к сервису {service_name} установлено")
|
|
|
|
async for message in websocket:
|
|
if callback:
|
|
await callback(service_name, message)
|
|
else:
|
|
print(f"[{service_name}] {message}")
|
|
|
|
except websockets.exceptions.ConnectionClosed:
|
|
print(f"Соединение с сервисом {service_name} закрыто")
|
|
except Exception as e:
|
|
print(f"Ошибка подключения к сервису {service_name}: {e}")
|
|
|
|
async def connect_to_service_group(self, services, tail=100, project=None, callback=None):
|
|
"""Подключение к логам группы сервисов"""
|
|
uri = f"{self.base_url}/ws/fan_group?services={services}&token={self.token}&tail={tail}"
|
|
if project:
|
|
uri += f"&project={project}"
|
|
|
|
try:
|
|
async with websockets.connect(uri) as websocket:
|
|
print(f"Подключение к группе сервисов {services} установлено")
|
|
|
|
async for message in websocket:
|
|
if callback:
|
|
await callback(services, message)
|
|
else:
|
|
print(f"[{services}] {message}")
|
|
|
|
except websockets.exceptions.ConnectionClosed:
|
|
print(f"Соединение с группой сервисов {services} закрыто")
|
|
except Exception as e:
|
|
print(f"Ошибка подключения к группе сервисов {services}: {e}")
|
|
|
|
# Функции обратного вызова
|
|
async def container_log_callback(container_id, message):
|
|
"""Обработка логов контейнера"""
|
|
print(f"[{container_id}] {message}")
|
|
|
|
async def service_log_callback(service_name, message):
|
|
"""Обработка логов сервиса"""
|
|
print(f"[{service_name}] {message}")
|
|
|
|
async def group_log_callback(services, message):
|
|
"""Обработка логов группы сервисов"""
|
|
print(f"[{services}] {message}")
|
|
|
|
async def main():
|
|
# Получение токена
|
|
response = requests.post('http://localhost:9001/api/auth/login', json={
|
|
'username': 'admin',
|
|
'password': 'your-password'
|
|
})
|
|
token = response.json()['access_token']
|
|
|
|
# Создание WebSocket клиента
|
|
ws_client = LogBoardWebSocket('http://localhost:9001', token)
|
|
|
|
# Создание задач для одновременного подключения
|
|
tasks = [
|
|
ws_client.connect_to_container('abc123def456', tail=100, callback=container_log_callback),
|
|
ws_client.connect_to_service('web', tail=100, callback=service_log_callback),
|
|
ws_client.connect_to_service_group('web,db,redis', tail=100, callback=group_log_callback)
|
|
]
|
|
|
|
# Запуск всех подключений одновременно
|
|
await asyncio.gather(*tasks)
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|
|
```
|
|
|
|
### Node.js
|
|
|
|
```javascript
|
|
const WebSocket = require('ws');
|
|
const axios = require('axios');
|
|
|
|
class LogBoardWebSocket {
|
|
constructor(baseUrl, token) {
|
|
this.baseUrl = baseUrl.replace('http://', 'ws://');
|
|
this.token = token;
|
|
this.connections = new Map();
|
|
}
|
|
|
|
// Подключение к логам контейнера
|
|
connectToContainer(containerId, options = {}) {
|
|
const { tail = 100, onMessage, onError, onClose } = options;
|
|
|
|
const url = `${this.baseUrl}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`;
|
|
const ws = new WebSocket(url);
|
|
|
|
ws.on('open', () => {
|
|
console.log(`Подключение к контейнеру ${containerId} установлено`);
|
|
});
|
|
|
|
ws.on('message', (data) => {
|
|
const message = data.toString();
|
|
if (onMessage) onMessage(message);
|
|
else console.log(`[${containerId}] ${message}`);
|
|
});
|
|
|
|
ws.on('error', (error) => {
|
|
console.error(`Ошибка WebSocket для контейнера ${containerId}:`, error);
|
|
if (onError) onError(error);
|
|
});
|
|
|
|
ws.on('close', (code, reason) => {
|
|
console.log(`Соединение с контейнером ${containerId} закрыто:`, code, reason);
|
|
if (onClose) onClose(code, reason);
|
|
});
|
|
|
|
this.connections.set(containerId, ws);
|
|
return ws;
|
|
}
|
|
|
|
// Подключение к логам сервиса
|
|
connectToService(serviceName, options = {}) {
|
|
const { tail = 100, project, onMessage, onError, onClose } = options;
|
|
|
|
let url = `${this.baseUrl}/ws/fan/${serviceName}?token=${this.token}&tail=${tail}`;
|
|
if (project) url += `&project=${project}`;
|
|
|
|
const ws = new WebSocket(url);
|
|
|
|
ws.on('open', () => {
|
|
console.log(`Подключение к сервису ${serviceName} установлено`);
|
|
});
|
|
|
|
ws.on('message', (data) => {
|
|
const message = data.toString();
|
|
if (onMessage) onMessage(message);
|
|
else console.log(`[${serviceName}] ${message}`);
|
|
});
|
|
|
|
ws.on('error', (error) => {
|
|
console.error(`Ошибка WebSocket для сервиса ${serviceName}:`, error);
|
|
if (onError) onError(error);
|
|
});
|
|
|
|
ws.on('close', (code, reason) => {
|
|
console.log(`Соединение с сервисом ${serviceName} закрыто:`, code, reason);
|
|
if (onClose) onClose(code, reason);
|
|
});
|
|
|
|
this.connections.set(serviceName, ws);
|
|
return ws;
|
|
}
|
|
|
|
// Закрытие соединения
|
|
disconnect(identifier) {
|
|
const ws = this.connections.get(identifier);
|
|
if (ws) {
|
|
ws.close();
|
|
this.connections.delete(identifier);
|
|
}
|
|
}
|
|
|
|
// Закрытие всех соединений
|
|
disconnectAll() {
|
|
for (const [identifier, ws] of this.connections) {
|
|
ws.close();
|
|
}
|
|
this.connections.clear();
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
// Получение токена
|
|
const loginResponse = await axios.post('http://localhost:9001/api/auth/login', {
|
|
username: 'admin',
|
|
password: 'your-password'
|
|
});
|
|
const token = loginResponse.data.access_token;
|
|
|
|
// Создание WebSocket клиента
|
|
const wsClient = new LogBoardWebSocket('http://localhost:9001', token);
|
|
|
|
// Подключение к контейнеру
|
|
wsClient.connectToContainer('abc123def456', {
|
|
tail: 100,
|
|
onMessage: (message) => {
|
|
console.log('Логи контейнера:', message);
|
|
}
|
|
});
|
|
|
|
// Подключение к сервису
|
|
wsClient.connectToService('web', {
|
|
tail: 100,
|
|
onMessage: (message) => {
|
|
console.log('Логи сервиса:', message);
|
|
}
|
|
});
|
|
|
|
// Обработка сигналов завершения
|
|
process.on('SIGINT', () => {
|
|
console.log('Закрытие соединений...');
|
|
wsClient.disconnectAll();
|
|
process.exit(0);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Ошибка:', error.message);
|
|
}
|
|
}
|
|
|
|
main();
|
|
```
|
|
|
|
## Обработка ошибок
|
|
|
|
### Коды ошибок WebSocket
|
|
|
|
| Код | Описание |
|
|
|-----|----------|
|
|
| 1000 | Нормальное закрытие |
|
|
| 1001 | Удаленная сторона закрыла соединение |
|
|
| 1002 | Ошибка протокола |
|
|
| 1003 | Неподдерживаемый тип данных |
|
|
| 1006 | Аномальное закрытие |
|
|
| 1011 | Внутренняя ошибка сервера |
|
|
|
|
### Обработка ошибок аутентификации
|
|
|
|
```javascript
|
|
ws.onmessage = function(event) {
|
|
if (event.data.startsWith('ERROR:')) {
|
|
const error = event.data.substring(6);
|
|
console.error('Ошибка аутентификации:', error);
|
|
|
|
if (error.includes('token required') || error.includes('invalid token')) {
|
|
// Переподключение с новым токеном
|
|
reconnectWithNewToken();
|
|
}
|
|
} else {
|
|
// Обработка обычных логов
|
|
console.log('Логи:', event.data);
|
|
}
|
|
};
|
|
```
|
|
|
|
### Автоматическое переподключение
|
|
|
|
```javascript
|
|
class ReconnectingWebSocket {
|
|
constructor(url, options = {}) {
|
|
this.url = url;
|
|
this.options = options;
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
|
|
this.reconnectInterval = options.reconnectInterval || 1000;
|
|
this.connect();
|
|
}
|
|
|
|
connect() {
|
|
this.ws = new WebSocket(this.url);
|
|
|
|
this.ws.onopen = () => {
|
|
console.log('WebSocket соединение установлено');
|
|
this.reconnectAttempts = 0;
|
|
};
|
|
|
|
this.ws.onclose = (event) => {
|
|
console.log('WebSocket соединение закрыто:', event.code, event.reason);
|
|
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
this.reconnectAttempts++;
|
|
console.log(`Попытка переподключения ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
|
|
|
|
setTimeout(() => {
|
|
this.connect();
|
|
}, this.reconnectInterval * this.reconnectAttempts);
|
|
} else {
|
|
console.error('Превышено максимальное количество попыток переподключения');
|
|
}
|
|
};
|
|
|
|
this.ws.onerror = (error) => {
|
|
console.error('WebSocket ошибка:', error);
|
|
};
|
|
}
|
|
|
|
send(data) {
|
|
if (this.ws.readyState === WebSocket.OPEN) {
|
|
this.ws.send(data);
|
|
}
|
|
}
|
|
|
|
close() {
|
|
this.ws.close();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Лучшие практики
|
|
|
|
### 1. Управление соединениями
|
|
|
|
```javascript
|
|
// Создание пула соединений
|
|
class WebSocketPool {
|
|
constructor() {
|
|
this.connections = new Map();
|
|
this.maxConnections = 10;
|
|
}
|
|
|
|
connect(identifier, url, options = {}) {
|
|
if (this.connections.size >= this.maxConnections) {
|
|
console.warn('Достигнут лимит соединений');
|
|
return null;
|
|
}
|
|
|
|
const ws = new WebSocket(url);
|
|
this.connections.set(identifier, ws);
|
|
|
|
ws.onclose = () => {
|
|
this.connections.delete(identifier);
|
|
};
|
|
|
|
return ws;
|
|
}
|
|
|
|
disconnect(identifier) {
|
|
const ws = this.connections.get(identifier);
|
|
if (ws) {
|
|
ws.close();
|
|
this.connections.delete(identifier);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Обработка больших объемов данных
|
|
|
|
```javascript
|
|
// Буферизация сообщений
|
|
class LogBuffer {
|
|
constructor(maxSize = 1000) {
|
|
this.buffer = [];
|
|
this.maxSize = maxSize;
|
|
}
|
|
|
|
add(message) {
|
|
this.buffer.push(message);
|
|
|
|
if (this.buffer.length > this.maxSize) {
|
|
this.buffer.shift(); // Удаляем старые сообщения
|
|
}
|
|
}
|
|
|
|
getMessages() {
|
|
return [...this.buffer];
|
|
}
|
|
|
|
clear() {
|
|
this.buffer = [];
|
|
}
|
|
}
|
|
|
|
// Использование
|
|
const logBuffer = new LogBuffer(1000);
|
|
|
|
ws.onmessage = function(event) {
|
|
logBuffer.add(event.data);
|
|
|
|
// Обновление UI каждые 100ms
|
|
if (!updateScheduled) {
|
|
updateScheduled = setTimeout(() => {
|
|
updateUI(logBuffer.getMessages());
|
|
updateScheduled = null;
|
|
}, 100);
|
|
}
|
|
};
|
|
```
|
|
|
|
### 3. Фильтрация логов
|
|
|
|
```javascript
|
|
// Фильтр по уровню логирования
|
|
class LogFilter {
|
|
constructor() {
|
|
this.filters = {
|
|
level: 'all', // debug, info, warn, error, all
|
|
service: null,
|
|
container: null,
|
|
text: null
|
|
};
|
|
}
|
|
|
|
setFilter(type, value) {
|
|
this.filters[type] = value;
|
|
}
|
|
|
|
shouldDisplay(message) {
|
|
// Фильтр по уровню
|
|
if (this.filters.level !== 'all') {
|
|
const levelMatch = message.toLowerCase().includes(`level=${this.filters.level}`);
|
|
if (!levelMatch) return false;
|
|
}
|
|
|
|
// Фильтр по сервису
|
|
if (this.filters.service) {
|
|
const serviceMatch = message.includes(`[${this.filters.service}]`);
|
|
if (!serviceMatch) return false;
|
|
}
|
|
|
|
// Фильтр по контейнеру
|
|
if (this.filters.container) {
|
|
const containerMatch = message.includes(`[${this.filters.container}]`);
|
|
if (!containerMatch) return false;
|
|
}
|
|
|
|
// Фильтр по тексту
|
|
if (this.filters.text) {
|
|
const textMatch = message.toLowerCase().includes(this.filters.text.toLowerCase());
|
|
if (!textMatch) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Использование
|
|
const logFilter = new LogFilter();
|
|
logFilter.setFilter('level', 'error');
|
|
|
|
ws.onmessage = function(event) {
|
|
if (logFilter.shouldDisplay(event.data)) {
|
|
console.log('Отфильтрованные логи:', event.data);
|
|
}
|
|
};
|
|
```
|
|
|
|
### 4. Мониторинг состояния соединений
|
|
|
|
```javascript
|
|
class ConnectionMonitor {
|
|
constructor() {
|
|
this.stats = {
|
|
totalMessages: 0,
|
|
totalBytes: 0,
|
|
connectionTime: null,
|
|
lastMessageTime: null,
|
|
errors: 0
|
|
};
|
|
}
|
|
|
|
onConnect() {
|
|
this.stats.connectionTime = new Date();
|
|
this.stats.totalMessages = 0;
|
|
this.stats.totalBytes = 0;
|
|
this.stats.errors = 0;
|
|
}
|
|
|
|
onMessage(message) {
|
|
this.stats.totalMessages++;
|
|
this.stats.totalBytes += message.length;
|
|
this.stats.lastMessageTime = new Date();
|
|
}
|
|
|
|
onError() {
|
|
this.stats.errors++;
|
|
}
|
|
|
|
getStats() {
|
|
return {
|
|
...this.stats,
|
|
uptime: this.stats.connectionTime ?
|
|
Date.now() - this.stats.connectionTime.getTime() : 0,
|
|
messagesPerSecond: this.stats.totalMessages /
|
|
(this.stats.uptime / 1000) || 0
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
## Ограничения и рекомендации
|
|
|
|
### Ограничения
|
|
|
|
- **Максимальное количество соединений:** 100 одновременных WebSocket соединений
|
|
- **Таймаут соединения:** 60 секунд бездействия
|
|
- **Размер сообщения:** до 1 MB на сообщение
|
|
- **Частота сообщений:** до 1000 сообщений в секунду
|
|
|
|
### Рекомендации
|
|
|
|
1. **Используйте переподключение** при разрыве соединений
|
|
2. **Ограничивайте количество соединений** для одного клиента
|
|
3. **Фильтруйте логи** на стороне клиента для снижения нагрузки
|
|
4. **Мониторьте состояние соединений** для диагностики проблем
|
|
5. **Используйте буферизацию** для больших объемов данных
|