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/api/websocket/logs/container-id?token=${token}`);
|
||
```
|
||
|
||
## Endpoints
|
||
|
||
### ws://host:port/api/websocket/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/api/websocket/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/api/websocket/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/api/websocket/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/api/websocket/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/api/websocket/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://', '')}/api/websocket/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://', '')}/api/websocket/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://', '')}/api/websocket/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}/api/websocket/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}/api/websocket/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}/api/websocket/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}/api/websocket/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}/api/websocket/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. **Используйте буферизацию** для больших объемов данных
|