SkillAgentSearch skills...

trackit_history

Lightweight modular logger for Dart and Flutter

Install / Use

npx skills add unger1984/trackit

Installs into whichever agent you are using.

About this skill
📐

Cursor Rules

Cursor IDE rules (v2)

Quality Score

64/100

Supported Platforms

Cursor

TrackIt 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) vs removeAt(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

  1. Установить разумный maxSize: Для production рекомендуется 500-2000 событий в зависимости от частоты логирования
  2. Фильтровать перед добавлением: Для экономии памяти фильтровать события по уровню или источнику
  3. Периодическая очистка: Для long-running приложений настроить автоматическую очистку
  4. Не злоупотреблять доступом: Геттер history создает новый список — кешировать результат если нужно
  5. Использовать для отладки: В production отключать или ограничивать размер для экономии памяти

Антипаттерны

❌ **Хранение

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars0
CategoryDevelopment
UpdatedNaNy ago
Forks0

Security Score

68/100

Audited on Invalid Date

2 medium1 low