Добавлена полная документация проекта LogBoard+
- Создан основной README.md с описанием проекта - Добавлена подробная документация в папке docs/ - Создан файл LICENSE (MIT) - Обновлен .gitignore - Добавлена документация по безопасности с генерацией ключей - Включены примеры конфигураций и устранение неполадок Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
913
docs/websocket.md
Normal file
913
docs/websocket.md
Normal file
@@ -0,0 +1,913 @@
|
||||
# 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. **Используйте буферизацию** для больших объемов данных
|
||||
Reference in New Issue
Block a user