318 lines
7.1 KiB
Dart
318 lines
7.1 KiB
Dart
import 'dart:async';
|
||
import 'dart:convert';
|
||
import 'package:flutter/foundation.dart';
|
||
|
||
|
||
enum LogLevel { debug, info, warning, error, critical }
|
||
|
||
|
||
class ConnectionLogger {
|
||
static final ConnectionLogger _instance = ConnectionLogger._internal();
|
||
factory ConnectionLogger() => _instance;
|
||
ConnectionLogger._internal();
|
||
|
||
final List<LogEntry> _logs = [];
|
||
final StreamController<LogEntry> _logController =
|
||
StreamController<LogEntry>.broadcast();
|
||
|
||
|
||
Stream<LogEntry> get logStream => _logController.stream;
|
||
|
||
|
||
List<LogEntry> get logs => List.unmodifiable(_logs);
|
||
|
||
|
||
static const int maxLogs = 1000;
|
||
|
||
|
||
LogLevel _currentLevel = LogLevel.debug;
|
||
|
||
void setLogLevel(LogLevel level) {
|
||
_currentLevel = level;
|
||
}
|
||
|
||
|
||
void log(
|
||
String message, {
|
||
LogLevel level = LogLevel.info,
|
||
String? category,
|
||
Map<String, dynamic>? data,
|
||
Object? error,
|
||
StackTrace? stackTrace,
|
||
}) {
|
||
if (level.index < _currentLevel.index) return;
|
||
|
||
final entry = LogEntry(
|
||
timestamp: DateTime.now(),
|
||
level: level,
|
||
message: message,
|
||
category: category ?? 'CONNECTION',
|
||
data: data,
|
||
error: error,
|
||
stackTrace: stackTrace,
|
||
);
|
||
|
||
_logs.add(entry);
|
||
|
||
|
||
if (_logs.length > maxLogs) {
|
||
_logs.removeRange(0, _logs.length - maxLogs);
|
||
}
|
||
|
||
_logController.add(entry);
|
||
|
||
|
||
if (kDebugMode) {
|
||
final emoji = _getEmojiForLevel(level);
|
||
final timestamp = entry.timestamp.toIso8601String().substring(11, 23);
|
||
final categoryStr = category != null ? '[$category]' : '';
|
||
final dataStr = data != null ? ' | Data: ${jsonEncode(data)}' : '';
|
||
final errorStr = error != null ? ' | Error: $error' : '';
|
||
|
||
print('$emoji [$timestamp] $categoryStr $message$dataStr$errorStr');
|
||
}
|
||
}
|
||
|
||
|
||
void logConnection(
|
||
String message, {
|
||
Map<String, dynamic>? data,
|
||
Object? error,
|
||
}) {
|
||
log(
|
||
message,
|
||
level: LogLevel.info,
|
||
category: 'CONNECTION',
|
||
data: data,
|
||
error: error,
|
||
);
|
||
}
|
||
|
||
|
||
void logError(
|
||
String message, {
|
||
Map<String, dynamic>? data,
|
||
Object? error,
|
||
StackTrace? stackTrace,
|
||
}) {
|
||
log(
|
||
message,
|
||
level: LogLevel.error,
|
||
category: 'ERROR',
|
||
data: data,
|
||
error: error,
|
||
stackTrace: stackTrace,
|
||
);
|
||
}
|
||
|
||
|
||
void logMessage(
|
||
String direction,
|
||
dynamic message, {
|
||
Map<String, dynamic>? metadata,
|
||
}) {
|
||
final data = <String, dynamic>{
|
||
'direction': direction,
|
||
'message': message,
|
||
if (metadata != null) ...metadata,
|
||
};
|
||
log(
|
||
'WebSocket $direction',
|
||
level: LogLevel.debug,
|
||
category: 'WEBSOCKET',
|
||
data: data,
|
||
);
|
||
}
|
||
|
||
|
||
void logReconnect(int attempt, String reason, {Duration? delay}) {
|
||
final data = <String, dynamic>{
|
||
'attempt': attempt,
|
||
'reason': reason,
|
||
if (delay != null) 'delay_seconds': delay.inSeconds,
|
||
};
|
||
log(
|
||
'Переподключение: $reason (попытка $attempt)',
|
||
level: LogLevel.warning,
|
||
category: 'RECONNECT',
|
||
data: data,
|
||
);
|
||
}
|
||
|
||
|
||
void logPerformance(
|
||
String operation,
|
||
Duration duration, {
|
||
Map<String, dynamic>? metadata,
|
||
}) {
|
||
final data = <String, dynamic>{
|
||
'operation': operation,
|
||
'duration_ms': duration.inMilliseconds,
|
||
if (metadata != null) ...metadata,
|
||
};
|
||
log(
|
||
'Performance: $operation за ${duration.inMilliseconds}ms',
|
||
level: LogLevel.debug,
|
||
category: 'PERFORMANCE',
|
||
data: data,
|
||
);
|
||
}
|
||
|
||
|
||
void logState(String from, String to, {Map<String, dynamic>? metadata}) {
|
||
final data = <String, dynamic>{
|
||
'from': from,
|
||
'to': to,
|
||
if (metadata != null) ...metadata,
|
||
};
|
||
log(
|
||
'Состояние: $from → $to',
|
||
level: LogLevel.info,
|
||
category: 'STATE',
|
||
data: data,
|
||
);
|
||
}
|
||
|
||
|
||
List<LogEntry> getLogsByCategory(String category) {
|
||
return _logs.where((log) => log.category == category).toList();
|
||
}
|
||
|
||
|
||
List<LogEntry> getLogsByLevel(LogLevel level) {
|
||
return _logs.where((log) => log.level == level).toList();
|
||
}
|
||
|
||
|
||
Map<String, int> getLogStats() {
|
||
final stats = <String, int>{};
|
||
for (final log in _logs) {
|
||
stats[log.category] = (stats[log.category] ?? 0) + 1;
|
||
}
|
||
return stats;
|
||
}
|
||
|
||
|
||
void clearLogs() {
|
||
_logs.clear();
|
||
log('Логи очищены', level: LogLevel.info, category: 'LOGGER');
|
||
}
|
||
|
||
|
||
String exportLogs() {
|
||
final logsJson = _logs.map((log) => log.toJson()).toList();
|
||
return jsonEncode(logsJson);
|
||
}
|
||
|
||
|
||
void importLogs(String jsonString) {
|
||
try {
|
||
final List<dynamic> logsList = jsonDecode(jsonString);
|
||
_logs.clear();
|
||
for (final logJson in logsList) {
|
||
_logs.add(LogEntry.fromJson(logJson));
|
||
}
|
||
log(
|
||
'Импортировано ${_logs.length} логов',
|
||
level: LogLevel.info,
|
||
category: 'LOGGER',
|
||
);
|
||
} catch (e) {
|
||
logError('Ошибка импорта логов', error: e);
|
||
}
|
||
}
|
||
|
||
String _getEmojiForLevel(LogLevel level) {
|
||
switch (level) {
|
||
case LogLevel.debug:
|
||
return '🔍';
|
||
case LogLevel.info:
|
||
return 'ℹ️';
|
||
case LogLevel.warning:
|
||
return '⚠️';
|
||
case LogLevel.error:
|
||
return '❌';
|
||
case LogLevel.critical:
|
||
return '🚨';
|
||
}
|
||
}
|
||
|
||
void dispose() {
|
||
_logController.close();
|
||
}
|
||
}
|
||
|
||
|
||
class LogEntry {
|
||
final DateTime timestamp;
|
||
final LogLevel level;
|
||
final String message;
|
||
final String category;
|
||
final Map<String, dynamic>? data;
|
||
final Object? error;
|
||
final StackTrace? stackTrace;
|
||
|
||
LogEntry({
|
||
required this.timestamp,
|
||
required this.level,
|
||
required this.message,
|
||
required this.category,
|
||
this.data,
|
||
this.error,
|
||
this.stackTrace,
|
||
});
|
||
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'timestamp': timestamp.toIso8601String(),
|
||
'level': level.name,
|
||
'message': message,
|
||
'category': category,
|
||
'data': data,
|
||
'error': error?.toString(),
|
||
'stackTrace': stackTrace?.toString(),
|
||
};
|
||
}
|
||
|
||
factory LogEntry.fromJson(Map<String, dynamic> json) {
|
||
return LogEntry(
|
||
timestamp: DateTime.parse(json['timestamp']),
|
||
level: LogLevel.values.firstWhere((l) => l.name == json['level']),
|
||
message: json['message'],
|
||
category: json['category'],
|
||
data: json['data'] != null
|
||
? Map<String, dynamic>.from(json['data'])
|
||
: null,
|
||
error: json['error'],
|
||
stackTrace: json['stackTrace'] != null
|
||
? StackTrace.fromString(json['stackTrace'])
|
||
: null,
|
||
);
|
||
}
|
||
|
||
@override
|
||
String toString() {
|
||
final emoji = _getEmojiForLevel(level);
|
||
final timestamp = this.timestamp.toIso8601String().substring(11, 23);
|
||
final dataStr = data != null ? ' | Data: ${jsonEncode(data)}' : '';
|
||
final errorStr = error != null ? ' | Error: $error' : '';
|
||
|
||
return '$emoji [$timestamp] [$category] $message$dataStr$errorStr';
|
||
}
|
||
|
||
String _getEmojiForLevel(LogLevel level) {
|
||
switch (level) {
|
||
case LogLevel.debug:
|
||
return '🔍';
|
||
case LogLevel.info:
|
||
return 'ℹ️';
|
||
case LogLevel.warning:
|
||
return '⚠️';
|
||
case LogLevel.error:
|
||
return '❌';
|
||
case LogLevel.critical:
|
||
return '🚨';
|
||
}
|
||
}
|
||
}
|