trackit_history
Lightweight modular logger for Dart and Flutter
Install / Use
npx skills add unger1984/trackitInstalls into whichever agent you are using.
Cursor Rules
Cursor IDE rules (v2)
Quality Score
Category
Development & EngineeringSupported Platforms
Tags
Skill content
View source on GitHubTrackIt History Package — Правила разработки
О пакете
trackit_history — модуль-расширение для хранения логов в памяти. Подписывается на поток событий из базового trackit и сохраняет их в FIFO очереди с ограничением по размеру.
Архитектурные паттерны
Singleton Pattern
Глобальное хранилище истории как singleton:
class TrackitHistory {
factory TrackitHistory() => _instance;
TrackitHistory._internal();
static final TrackitHistory _instance = TrackitHistory._internal();
final TrackitHistoryList _history = [];
int _maxSize = 1000;
}
Правила:
- Factory конструктор для доступа к единственному экземпляру
- Приватный конструктор
_internal() - Статическое поле
_instanceс eager initialization - Приватные поля для хранения состояния
FIFO Queue Pattern
Очередь с автоматическим удалением старых элементов:
void add(LogEvent event) {
_history.add(event);
while (_history.length > _maxSize) {
_history.removeAt(0); // Удаление самого старого
}
}
Правила:
- Добавление в конец списка (
add) - Удаление из начала списка (
removeAt(0)) - Проверка размера после каждого добавления
- Использовать
while, неifдля корректной обработки измененияmaxSize
Immutable Access Pattern
Защита от внешних изменений:
TrackitHistoryList get history => List.unmodifiable(_history);
Правила:
- Возвращать
List.unmodifiable()для предотвращения изменений - Внутреннее хранилище остается приватным
- Все изменения только через публичные методы класса
Структура класса
TrackitHistory (Singleton)
class TrackitHistory {
factory TrackitHistory() => _instance;
TrackitHistory._internal();
static final TrackitHistory _instance = TrackitHistory._internal();
final TrackitHistoryList _history = [];
int _maxSize = 1000;
/// Добавляет событие в историю
void add(LogEvent event) {
_history.add(event);
while (_history.length > _maxSize) {
_history.removeAt(0);
}
}
/// Возвращает неизменяемую копию истории
TrackitHistoryList get history => List.unmodifiable(_history);
/// Устанавливает максимальный размер истории
void setMaxSize(int size) {
_maxSize = size;
while (_history.length > _maxSize) {
_history.removeAt(0);
}
}
/// Очищает всю историю
void clear() {
_history.clear();
}
}
Ответственность:
- Хранение событий в памяти
- Управление размером истории
- Предоставление доступа к истории
TrackitHistoryList (Type Alias)
typedef TrackitHistoryList = List<LogEvent>;
Правила:
- Простой type alias для ясности API
- Используется для возвращаемых значений
- Подчеркивает назначение списка
API дизайн
Добавление событий
void add(LogEvent event)
Правила:
- Единственный параметр —
LogEvent - Void return type (side effect)
- Автоматически управляет размером
- Синхронный метод
Доступ к истории
TrackitHistoryList get history
Правила:
- Геттер, не метод
- Возвращает неизменяемый список
- O(n) операция (создается новый список)
- Не кешировать результат на стороне клиента
Установка максимального размера
void setMaxSize(int size)
Правила:
- Положительное целое число
- Применяется немедленно (удаляет лишние элементы)
- Может уменьшить текущую историю
- Default значение: 1000
Очистка истории
void clear()
Правила:
- Удаляет все события
- Не изменяет
maxSize - Используется для reset состояния
- Полезно для тестов и hot reload
Управление памятью
Ограничение размера
Default значение:
int _maxSize = 1000;
Правила:
- По умолчанию 1000 событий
- Можно изменить через
setMaxSize() - Рассчитывать потребление памяти: ~1000 событий ≈ 100-500 KB
- Для long-running приложений рекомендуется ограничивать размер
Автоматическая очистка
while (_history.length > _maxSize) {
_history.removeAt(0);
}
Правила:
- Очистка происходит при добавлении нового события
- Удаляются самые старые события (FIFO)
- Использовать
while, неif(корректно обрабатывает уменьшение maxSize) - O(n) сложность при удалении (допустимо для небольших n)
Оптимизация производительности
Для больших историй:
// Рассмотреть использование Queue вместо List
import 'dart:collection';
final Queue<LogEvent> _history = Queue();
void add(LogEvent event) {
_history.add(event);
while (_history.length > _maxSize) {
_history.removeFirst(); // O(1) вместо O(n)
}
}
Правила:
- Для
maxSize > 10000рассмотретьQueue removeFirst()в Queue — O(1) vsremoveAt(0)в List — O(n)- Компромисс: Queue требует больше памяти
Использование
Базовое использование
import 'package:trackit/trackit.dart';
import 'package:trackit_history/trackit_history.dart';
void main() {
// Подключение хранилища истории
Trackit().listen((event) {
TrackitHistory().add(event);
});
// Создание логгера
final log = Trackit.create('MyApp');
// Логирование
log.info('Event 1');
log.info('Event 2');
// Доступ к истории
final history = TrackitHistory().history;
print('Total events: ${history.length}');
}
Упрощенная подписка
// Короткая форма
Trackit().listen(TrackitHistory().add);
С ограничением размера
void main() {
// Установка максимального размера
TrackitHistory().setMaxSize(500);
// Подключение
Trackit().listen(TrackitHistory().add);
}
Фильтрация событий
// Сохранять только ошибки
Trackit().listen((event) {
if (event.level is LogLevelError || event.level is LogLevelFatal) {
TrackitHistory().add(event);
}
});
Периодическая очистка
// Очищать историю каждый час
Timer.periodic(Duration(hours: 1), (_) {
TrackitHistory().clear();
});
Доступ к истории
Получение всех событий
final allEvents = TrackitHistory().history;
Фильтрация по уровню
final errors = TrackitHistory()
.history
.where((e) => e.level is LogLevelError)
.toList();
Фильтрация по времени
final lastHour = DateTime.now().subtract(Duration(hours: 1));
final recentEvents = TrackitHistory()
.history
.where((e) => e.time != null && e.time!.isAfter(lastHour))
.toList();
Фильтрация по title
final serviceEvents = TrackitHistory()
.history
.where((e) => e.title == 'MyService')
.toList();
Сортировка
// По времени (по возрастанию)
final sorted = TrackitHistory()
.history
.sorted((a, b) => a.compareTo(b));
// По времени (по убыванию)
final reversed = TrackitHistory()
.history
.sorted((a, b) => b.compareTo(a));
Последние N событий
final last10 = TrackitHistory()
.history
.reversed
.take(10)
.toList();
Интеграция с UI
Flutter: Отображение истории логов
class LogHistoryScreen extends StatelessWidget {
const LogHistoryScreen({super.key});
@override
Widget build(BuildContext context) {
final history = TrackitHistory().history.reversed.toList();
return ListView.builder(
itemCount: history.length,
itemBuilder: (context, index) {
final event = history[index];
return ListTile(
leading: Icon(_getIconForLevel(event.level)),
title: Text(event.message),
subtitle: Text('${event.title} • ${event.time}'),
);
},
);
}
}
Реактивное обновление
class LogHistoryProvider extends ChangeNotifier {
List<LogEvent> get history => TrackitHistory().history;
void _updateHistory(LogEvent event) {
TrackitHistory().add(event);
notifyListeners();
}
void init() {
Trackit().listen(_updateHistory);
}
}
Stream для UI
// Создать Stream для реактивного UI
Stream<TrackitHistoryList> get historyStream async* {
await for (final _ in Trackit()) {
yield TrackitHistory().history;
}
}
Экспорт истории
В JSON
String exportToJson() {
final history = TrackitHistory().history;
final json = history.map((event) => {
'level': event.level.toString(),
'title': event.title,
'message': event.message,
'time': event.time?.toIso8601String(),
'exception': event.exception?.toString(),
'stackTrace': event.stackTrace?.toString(),
}).toList();
return jsonEncode(json);
}
В текстовый файл
Future<void> exportToFile(String path) async {
final history = TrackitHistory().history;
final buffer = StringBuffer();
for (final event in history) {
buffer.writeln('[${event.level}] ${event.time} - ${event.title}');
buffer.writeln(' ${event.message}');
if (event.exception != null) {
buffer.writeln(' Exception: ${event.exception}');
}
buffer.writeln();
}
final file = File(path);
await file.writeAsString(buffer.toString());
}
Зависимости
dependencies:
trackit: ^0.1.0 # Базовый пакет
meta: ^1.15.0
dev_dependencies:
lints: ^5.0.0
test: ^1.25.8
Правила:
- Зависимость от базового
trackit - Никаких других runtime зависимостей
- Чистый Dart код без platform-specific логики
Тестирование
Что тестировать
- Добавление событий в историю
- Ограничение по размеру (FIFO)
- Изменение maxSize с удалением лишних элементов
- Очистка истории
- Неизменяемость возвращаемого списка
- Singleton поведение
Пример теста
import 'package:test/test.dart';
import 'package:trackit/trackit.dart';
import 'package:trackit_history/trackit_history.dart';
void main() {
setUp(() {
TrackitHistory().clear();
});
group('TrackitHistory', () {
test('should add events to history', () {
final event = LogEvent(
level: const LogLevel.info(),
title: 'Test',
message: 'Test message',
);
TrackitHistory().add(event);
expect(TrackitHistory().history.length, equals(1));
expect(TrackitHistory().history.first, equals(event));
});
test('should limit history size', () {
TrackitHistory().setMaxSize(3);
for (var i = 0; i < 5; i++) {
TrackitHistory().add(LogEvent(
level: const LogLevel.info(),
title: 'Test',
message: 'Message $i',
));
}
expect(TrackitHistory().history.length, equals(3));
expect(TrackitHistory().history.last.message, equals('Message 4'));
expect(TrackitHistory().history.first.message, equals('Message 2'));
});
test('should clear history', () {
TrackitHistory().add(LogEvent(
level: const LogLevel.info(),
title: 'Test',
message: 'Test',
));
expect(TrackitHistory().history.length, equals(1));
TrackitHistory().clear();
expect(TrackitHistory().history.length, equals(0));
});
test('should return unmodifiable list', () {
final history = TrackitHistory().history;
expect(
() => history.add(LogEvent(
level: const LogLevel.info(),
title: 'Test',
message: 'Test',
)),
throwsUnsupportedError,
);
});
test('should be singleton', () {
final instance1 = TrackitHistory();
final instance2 = TrackitHistory();
expect(identical(instance1, instance2), isTrue);
});
});
}
Best Practices
- Установить разумный maxSize: Для production рекомендуется 500-2000 событий в зависимости от частоты логирования
- Фильтровать перед добавлением: Для экономии памяти фильтровать события по уровню или источнику
- Периодическая очистка: Для long-running приложений настроить автоматическую очистку
- Не злоупотреблять доступом: Геттер
historyсоздает новый список — кешировать результат если нужно - Использовать для отладки: В production отключать или ограничивать размер для экономии памяти
Антипаттерны
❌ **Хранение
Truncated for display — read the full file on GitHub.
Related Skills
career-ops
72.4kOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)
ai-job-search
43.6kThe job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
claude-howto
41.6kA visual, example-driven guide to Claude Code — from basic concepts to advanced agents, with copy-paste templates that bring immediate value.
guizang-ppt-skill
26.7kAI-agent Skill for generating polished HTML slide decks: editorial magazine and Swiss layouts, image prompts, social covers, and a WebGL/low-power presentation runtime.
Security Score
Audited on Invalid Date
