9.1 KiB
9.1 KiB
Логирование в Ласточке
Обзор
Система логирования записывает все важные действия пользователей и администраторов для:
- 🔍 Аудита безопасности
- 📊 Анализа использования
- 🐛 Отладки проблем
- 📋 Соответствия требованиям
Типы действий
Пользовательские
| Действие | Код | Описание |
|---|---|---|
| Вход | login |
Пользователь вошёл в систему |
| Выход | logout |
Пользователь вышел из системы |
| Регистрация | register |
Новый пользователь зарегистрировался |
| Обновление профиля | update_profile |
Изменены данные профиля |
| Смена пароля | change_password |
Пользователь сменил пароль |
Группы и каналы
| Действие | Код | Описание |
|---|---|---|
| Создание группы | create_group |
Создана новая группа/канал |
| Удаление группы | delete_group |
Группа удалена |
Административные
| Действие | Код | Описание |
|---|---|---|
| Блокировка | ban_user |
Пользователь заблокирован |
| Разблокировка | unban_user |
Пользователь разблокирован |
| Удаление пользователя | delete_user |
Аккаунт удалён |
| Изменение настроек | update_settings |
Изменены настройки системы |
| Отправка уведомления | send_notification |
Массовая рассылка |
| Экспорт данных | export_data |
Выгрузка данных |
Структура лога
interface ActivityLog {
id: string // Уникальный ID записи
userId: string // ID пользователя
userName: string // Отображаемое имя
action: ActionType // Тип действия
target: string // Цель (user/group/system)
targetId?: string // ID цели (если есть)
details?: string // Дополнительные детали
ip: string // IP адрес
timestamp: Date // Время действия
}
Примеры записей
Вход пользователя
{
"id": "log_1234567890",
"userId": "user_abc123",
"userName": "Иван Петров",
"action": "login",
"target": "system",
"ip": "192.168.1.100",
"timestamp": "2026-03-17T10:30:00Z"
}
Создание группы
{
"id": "log_1234567891",
"userId": "user_abc123",
"userName": "Иван Петров",
"action": "create_group",
"target": "group",
"targetId": "grp_xyz789",
"details": "Создана группа \"Рабочая\"",
"ip": "192.168.1.100",
"timestamp": "2026-03-17T11:45:00Z"
}
Блокировка пользователя (админ)
{
"id": "log_1234567892",
"userId": "admin_001",
"userName": "Администратор",
"action": "ban_user",
"target": "user",
"targetId": "user_def456",
"details": "Нарушение правил сообщества",
"ip": "10.0.0.1",
"timestamp": "2026-03-17T14:20:00Z"
}
Страница логов
Фильтры
Поиск:
- По имени пользователя
- По типу действия
- По цели действия
Фильтры:
- Тип действия (все/конкретный)
- Пользователь (все/конкретный)
- Период дат (с/по)
Сортировка:
- По времени (возрастанию/убыванию)
- По типу действия
Пагинация:
- 25 / 50 / 100 / 200 записей на странице
Экспорт
// Экспорт в JSON
const handleExport = () => {
const data = JSON.stringify(filteredLogs, null, 2)
const blob = new Blob([data], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `logs-${new Date().toISOString().split('T')[0]}.json`
a.click()
URL.revokeObjectURL(url)
}
API
Эндпоинты
GET /api/admin/logs
?action=login # Фильтр по действию
&userId=user123 # Фильтр по пользователю
&from=2026-03-01 # С даты
&to=2026-03-17 # По дату
&sort=timestamp # Сортировка
&order=desc # Порядок
&page=1 # Страница
&limit=50 # Лимит
DELETE /api/admin/logs # Очистка логов
Серверная логика
// Middleware для логирования действий
async function logAction(
userId: string,
action: ActionType,
target: string,
details?: string
) {
const log: ActivityLog = {
id: generateId(),
userId,
userName: await getUserName(userId),
action,
target,
details,
ip: getRequestIP(),
timestamp: new Date(),
}
await db.logs.insert(log)
// Асинхронная отправка в аналитику
sendToAnalytics(log)
}
Хранение
Стратегия
// Автоматическая очистка старых логов
const RETENTION_DAYS = 90
async function cleanupOldLogs() {
const cutoffDate = new Date()
cutoffDate.setDate(cutoffDate.getDate() - RETENTION_DAYS)
await db.logs.delete({
timestamp: { lt: cutoffDate }
})
}
Индексы
-- Для ускорения поиска
CREATE INDEX idx_logs_timestamp ON logs(timestamp DESC);
CREATE INDEX idx_logs_user ON logs(userId);
CREATE INDEX idx_logs_action ON logs(action);
CREATE INDEX idx_logs_target ON logs(target);
Использование
Логирование действия
import { logAction } from '@/lib/logging'
// При входе
await logAction(userId, 'login', 'system')
// При создании группы
await logAction(userId, 'create_group', 'group', {
groupId: newGroup.id,
groupName: newGroup.name,
})
// При блокировке (админ)
await logAction(adminId, 'ban_user', 'user', {
bannedUserId: userId,
reason: 'Нарушение правил',
duration: 7, // дней
})
Поиск логов
import { useAdminStore } from '@/store/admin'
const { logs, loadLogs } = useAdminStore()
// Загрузка с фильтрами
await loadLogs({
action: 'login',
userId: 'user123',
from: '2026-03-01',
to: '2026-03-17',
limit: 100,
})
Безопасность
Защита логов
- Доступ только для администраторов
- Логи действий администраторов тоже логируются
- Запрет на удаление отдельных записей
- Только массовая очистка по истечении срока
Аудит
// Проверка прав доступа
async function canAccessLogs(userId: string): Promise<boolean> {
const user = await getUser(userId)
return user?.role === 'admin' || user?.role === 'superadmin'
}
// Логирование доступа к логам
await logAction(adminId, 'view_logs', 'system')
Мониторинг
Оповещения
// Подозрительная активность
if (failedLoginAttempts > 10) {
await sendAlert('Много неудачных попыток входа', {
userId,
ip,
count: failedLoginAttempts,
})
}
// Массовые действия
if (actionsPerMinute > 100) {
await sendAlert('Подозрительная активность', {
userId,
actions: actionsPerMinute,
})
}
Метрики
// Статистика за период
const stats = {
totalLogs: await logs.count(),
byAction: await logs.groupBy('action'),
byUser: await logs.groupBy('userId'),
peakHour: await logs.peakHour(),
avgPerDay: await logs.averagePerDay(),
}
Примеры использования
Поиск всех входов пользователя
const logins = logs.filter(log =>
log.userId === 'user123' && log.action === 'login'
)
Поиск действий за сегодня
const today = new Date().toDateString()
const todayLogs = logs.filter(log =>
new Date(log.timestamp).toDateString() === today
)
Подсчёт действий по типу
const actionCounts = logs.reduce((acc, log) => {
acc[log.action] = (acc[log.action] || 0) + 1
return acc
}, {} as Record<string, number>)
Ласточка — народный мессенджер с открытым кодом 🕊️