From efed72cc39922fb2570e1c3054a1fe10db5219e5 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Mon, 11 Aug 2025 18:01:21 +0300 Subject: [PATCH] refactor(core,logger)migrate to CherryPickObserver API and drop CherryPickLogger BREAKING CHANGE: - Removed the deprecated CherryPickLogger interface from cherrypick - Logger/adapters (e.g., talker_cherrypick_logger) must now implement CherryPickObserver - talker_cherrypick_logger: replace TalkerCherryPickLogger with TalkerCherryPickObserver - All usages, docs, tests migrated to observer API - Improved test mocks and integration tests for observer pattern - Removed obsolete files: cherrypick/src/logger.dart, talker_cherrypick_logger/src/talker_cherrypick_logger.dart - Updated README and example usages for new CherryPickObserver model This refactor introduces a unified observer pattern (CherryPickObserver) to handle all DI lifecycle events, replacing the limited info/warn/error logger pattern. All external logging adapters and integrations must migrate to use CherryPickObserver. --- .../example/cherrypick_logger_demo.dart | 4 +- cherrypick/lib/cherrypick.dart | 2 +- cherrypick/lib/src/binding.dart | 105 +++++++-------- cherrypick/lib/src/cycle_detector.dart | 100 +++++++------- cherrypick/lib/src/global_cycle_detector.dart | 39 +++--- cherrypick/lib/src/helper.dart | 12 +- cherrypick/lib/src/logger.dart | 108 --------------- cherrypick/lib/src/observer.dart | 119 +++++++++++++++++ cherrypick/lib/src/scope.dart | 124 ++++++++---------- cherrypick/test/logger_integration_test.dart | 40 ++---- cherrypick/test/mock_logger.dart | 46 ++++++- cherrypick/test/src/cycle_detector_test.dart | 8 +- .../test/src/helper_cycle_detection_test.dart | 6 +- cherrypick/test/src/scope_test.dart | 82 ++++++------ examples/postly/lib/app.dart | 5 +- examples/postly/lib/main.dart | 4 +- .../lib/presentation/pages/posts_page.dart | 1 - examples/postly/lib/router/app_router.dart | 1 - .../talker_cherrypick_logger_example.dart | 8 +- .../lib/src/talker_cherrypick_logger.dart | 24 ---- .../lib/src/talker_cherrypick_observer.dart | 66 ++++++++++ .../lib/talker_cherrypick_logger.dart | 2 +- talker_cherrypick_logger/pubspec.yaml | 1 + .../test/talker_cherrypick_logger_test.dart | 36 ++--- 24 files changed, 507 insertions(+), 436 deletions(-) delete mode 100644 cherrypick/lib/src/logger.dart create mode 100644 cherrypick/lib/src/observer.dart delete mode 100644 talker_cherrypick_logger/lib/src/talker_cherrypick_logger.dart create mode 100644 talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart diff --git a/cherrypick/example/cherrypick_logger_demo.dart b/cherrypick/example/cherrypick_logger_demo.dart index 29a60c3..d81c5aa 100644 --- a/cherrypick/example/cherrypick_logger_demo.dart +++ b/cherrypick/example/cherrypick_logger_demo.dart @@ -15,7 +15,7 @@ class AppModule extends Module { void main() { // Set a global logger for the DI system - CherryPick.setGlobalLogger(PrintLogger()); + CherryPick.setGlobalObserver(PrintCherryPickObserver()); // Open the root scope final rootScope = CherryPick.openRootScope(); @@ -32,6 +32,6 @@ void main() { subScope.closeSubScope('feature.profile'); // Demonstrate disabling and re-enabling logging - CherryPick.setGlobalLogger(const SilentLogger()); + CherryPick.setGlobalObserver(SilentCherryPickObserver()); rootScope.resolve(); // now without logs } diff --git a/cherrypick/lib/cherrypick.dart b/cherrypick/lib/cherrypick.dart index 5f8a44e..89222a7 100644 --- a/cherrypick/lib/cherrypick.dart +++ b/cherrypick/lib/cherrypick.dart @@ -20,5 +20,5 @@ export 'package:cherrypick/src/global_cycle_detector.dart'; export 'package:cherrypick/src/helper.dart'; export 'package:cherrypick/src/module.dart'; export 'package:cherrypick/src/scope.dart'; -export 'package:cherrypick/src/logger.dart'; export 'package:cherrypick/src/disposable.dart'; +export 'package:cherrypick/src/observer.dart'; diff --git a/cherrypick/lib/src/binding.dart b/cherrypick/lib/src/binding.dart index 2929efa..c43ee3e 100644 --- a/cherrypick/lib/src/binding.dart +++ b/cherrypick/lib/src/binding.dart @@ -16,8 +16,7 @@ import 'package:cherrypick/src/binding_resolver.dart'; /// RU: Класс Binding<T> настраивает параметры экземпляра. /// ENG: The Binding<T> class configures the settings for the instance. /// -import 'package:cherrypick/src/logger.dart'; -import 'package:cherrypick/src/log_format.dart'; +import 'package:cherrypick/src/observer.dart'; class Binding { late Type _key; @@ -25,50 +24,54 @@ class Binding { BindingResolver? _resolver; - CherryPickLogger? logger; + CherryPickObserver? observer; // Deferred logging flags bool _createdLogged = false; bool _namedLogged = false; bool _singletonLogged = false; - Binding({this.logger}) { + Binding({this.observer}) { _key = T; - // Не логируем здесь! Делаем deferred лог после назначения logger + // Deferred уведомения observer, не логировать здесь напрямую } void markCreated() { if (!_createdLogged) { - logger?.info(formatLogMessage( - type: 'Binding', - name: T.toString(), - params: _name != null ? {'name': _name} : null, - description: 'created', - )); + observer?.onBindingRegistered( + runtimeType.toString(), + T, + ); _createdLogged = true; } } void markNamed() { if (isNamed && !_namedLogged) { - logger?.info(formatLogMessage( - type: 'Binding', - name: T.toString(), - params: {'name': _name}, - description: 'named', - )); + observer?.onDiagnostic( + 'Binding named: ${T.toString()} name: $_name', + details: { + 'type': 'Binding', + 'name': T.toString(), + 'nameParam': _name, + 'description': 'named', + }, + ); _namedLogged = true; } } void markSingleton() { if (isSingleton && !_singletonLogged) { - logger?.info(formatLogMessage( - type: 'Binding', - name: T.toString(), - params: _name != null ? {'name': _name} : null, - description: 'singleton mode enabled', - )); + observer?.onDiagnostic( + 'Binding singleton: ${T.toString()}${_name != null ? ' name: $_name' : ''}', + details: { + 'type': 'Binding', + 'name': T.toString(), + if (_name != null) 'name': _name, + 'description': 'singleton mode enabled', + }, + ); _singletonLogged = true; } } @@ -170,25 +173,23 @@ class Binding { T? resolveSync([dynamic params]) { final res = resolver?.resolveSync(params); if (res != null) { - logger?.info(formatLogMessage( - type: 'Binding', - name: T.toString(), - params: { + observer?.onDiagnostic( + 'Binding resolved instance: ${T.toString()}', + details: { if (_name != null) 'name': _name, 'method': 'resolveSync', + 'description': 'object created/resolved', }, - description: 'object created/resolved', - )); + ); } else { - logger?.warn(formatLogMessage( - type: 'Binding', - name: T.toString(), - params: { + observer?.onWarning( + 'resolveSync returned null: ${T.toString()}', + details: { if (_name != null) 'name': _name, 'method': 'resolveSync', + 'description': 'resolveSync returned null', }, - description: 'resolveSync returned null', - )); + ); } return res; } @@ -197,38 +198,28 @@ class Binding { final future = resolver?.resolveAsync(params); if (future != null) { future - .then((res) => logger?.info(formatLogMessage( - type: 'Binding', - name: T.toString(), - params: { + .then((res) => observer?.onDiagnostic( + 'Future resolved for: ${T.toString()}', + details: { if (_name != null) 'name': _name, 'method': 'resolveAsync', + 'description': 'Future resolved', }, - description: 'Future resolved', - ))) - .catchError((e, s) => logger?.error( - formatLogMessage( - type: 'Binding', - name: T.toString(), - params: { - if (_name != null) 'name': _name, - 'method': 'resolveAsync', - }, - description: 'resolveAsync error', - ), + )) + .catchError((e, s) => observer?.onError( + 'resolveAsync error: ${T.toString()}', e, s, )); } else { - logger?.warn(formatLogMessage( - type: 'Binding', - name: T.toString(), - params: { + observer?.onWarning( + 'resolveAsync returned null: ${T.toString()}', + details: { if (_name != null) 'name': _name, 'method': 'resolveAsync', + 'description': 'resolveAsync returned null', }, - description: 'resolveAsync returned null', - )); + ); } return future; } diff --git a/cherrypick/lib/src/cycle_detector.dart b/cherrypick/lib/src/cycle_detector.dart index d149efe..ecd5d8e 100644 --- a/cherrypick/lib/src/cycle_detector.dart +++ b/cherrypick/lib/src/cycle_detector.dart @@ -12,8 +12,7 @@ // import 'dart:collection'; -import 'package:cherrypick/src/logger.dart'; -import 'package:cherrypick/src/log_format.dart'; +import 'package:cherrypick/src/observer.dart'; /// RU: Исключение, выбрасываемое при обнаружении циклической зависимости. /// ENG: Exception thrown when a circular dependency is detected. @@ -36,11 +35,11 @@ class CircularDependencyException implements Exception { /// RU: Детектор циклических зависимостей для CherryPick DI контейнера. /// ENG: Circular dependency detector for CherryPick DI container. class CycleDetector { - final CherryPickLogger _logger; + final CherryPickObserver _observer; final Set _resolutionStack = HashSet(); final List _resolutionHistory = []; - CycleDetector({required CherryPickLogger logger}): _logger = logger; + CycleDetector({required CherryPickObserver observer}) : _observer = observer; /// RU: Начинает отслеживание разрешения зависимости. /// ENG: Starts tracking dependency resolution. @@ -48,25 +47,24 @@ class CycleDetector { /// Throws [CircularDependencyException] if circular dependency is detected. void startResolving({String? named}) { final dependencyKey = _createDependencyKey(named); - print('[DEBUG] CycleDetector logger type=${_logger.runtimeType} hash=${_logger.hashCode}'); - _logger.info(formatLogMessage( - type: 'CycleDetector', - name: dependencyKey.toString(), - params: {'event': 'startResolving', 'stackSize': _resolutionStack.length}, - description: 'start resolving', - )); + _observer.onDiagnostic( + 'CycleDetector startResolving: $dependencyKey', + details: { + 'event': 'startResolving', + 'stackSize': _resolutionStack.length, + }, + ); if (_resolutionStack.contains(dependencyKey)) { - // Найдена циклическая зависимость final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey); final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); - // print removed (trace) - final msg = formatLogMessage( - type: 'CycleDetector', - name: dependencyKey.toString(), - params: {'chain': cycle.join('->')}, - description: 'cycle detected', + _observer.onCycleDetected( + cycle, + ); + _observer.onError( + 'Cycle detected for $dependencyKey', + null, + null, ); - _logger.error(msg); throw CircularDependencyException( 'Circular dependency detected for $dependencyKey', cycle, @@ -81,12 +79,10 @@ class CycleDetector { /// ENG: Finishes tracking dependency resolution. void finishResolving({String? named}) { final dependencyKey = _createDependencyKey(named); - _logger.info(formatLogMessage( - type: 'CycleDetector', - name: dependencyKey.toString(), - params: {'event': 'finishResolving'}, - description: 'finish resolving', - )); + _observer.onDiagnostic( + 'CycleDetector finishResolving: $dependencyKey', + details: {'event': 'finishResolving'}, + ); _resolutionStack.remove(dependencyKey); // Удаляем из истории только если это последний элемент if (_resolutionHistory.isNotEmpty && @@ -98,11 +94,13 @@ class CycleDetector { /// RU: Очищает все состояние детектора. /// ENG: Clears all detector state. void clear() { - _logger.info(formatLogMessage( - type: 'CycleDetector', - params: {'event': 'clear'}, - description: 'resolution stack cleared', - )); + _observer.onDiagnostic( + 'CycleDetector clear', + details: { + 'event': 'clear', + 'description': 'resolution stack cleared', + }, + ); _resolutionStack.clear(); _resolutionHistory.clear(); } @@ -130,28 +128,32 @@ class CycleDetector { /// ENG: Mixin for adding circular dependency detection support. mixin CycleDetectionMixin { CycleDetector? _cycleDetector; - CherryPickLogger get logger; + CherryPickObserver get observer; /// RU: Включает обнаружение циклических зависимостей. /// ENG: Enables circular dependency detection. void enableCycleDetection() { - _cycleDetector = CycleDetector(logger: logger); - logger.info(formatLogMessage( - type: 'CycleDetection', - params: {'event': 'enable'}, - description: 'cycle detection enabled', - )); + _cycleDetector = CycleDetector(observer: observer); + observer.onDiagnostic( + 'CycleDetection enabled', + details: { + 'event': 'enable', + 'description': 'cycle detection enabled', + }, + ); } /// RU: Отключает обнаружение циклических зависимостей. /// ENG: Disables circular dependency detection. void disableCycleDetection() { _cycleDetector?.clear(); - logger.info(formatLogMessage( - type: 'CycleDetection', - params: {'event': 'disable'}, - description: 'cycle detection disabled', - )); + observer.onDiagnostic( + 'CycleDetection disabled', + details: { + 'event': 'disable', + 'description': 'cycle detection disabled', + }, + ); _cycleDetector = null; } @@ -178,12 +180,14 @@ mixin CycleDetectionMixin { final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey); final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex) ..add(dependencyKey); - logger.error(formatLogMessage( - type: 'CycleDetector', - name: dependencyKey.toString(), - params: {'chain': cycle.join('->')}, - description: 'cycle detected', - )); + observer.onCycleDetected( + cycle, + ); + observer.onError( + 'Cycle detected for $dependencyKey', + null, + null, + ); throw CircularDependencyException( 'Circular dependency detected for $dependencyKey', cycle, diff --git a/cherrypick/lib/src/global_cycle_detector.dart b/cherrypick/lib/src/global_cycle_detector.dart index a45482e..5bd0e17 100644 --- a/cherrypick/lib/src/global_cycle_detector.dart +++ b/cherrypick/lib/src/global_cycle_detector.dart @@ -13,7 +13,6 @@ import 'dart:collection'; import 'package:cherrypick/cherrypick.dart'; -import 'package:cherrypick/src/log_format.dart'; /// RU: Глобальный детектор циклических зависимостей для всей иерархии скоупов. @@ -21,7 +20,7 @@ import 'package:cherrypick/src/log_format.dart'; class GlobalCycleDetector { static GlobalCycleDetector? _instance; - final CherryPickLogger _logger; + final CherryPickObserver _observer; // Глобальный стек разрешения зависимостей final Set _globalResolutionStack = HashSet(); @@ -32,12 +31,12 @@ class GlobalCycleDetector { // Карта активных детекторов по скоупам final Map _scopeDetectors = HashMap(); - GlobalCycleDetector._internal({required CherryPickLogger logger}): _logger = logger; + GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer; /// RU: Получить единственный экземпляр глобального детектора. /// ENG: Get singleton instance of global detector. static GlobalCycleDetector get instance { - _instance ??= GlobalCycleDetector._internal(logger: CherryPick.globalLogger); + _instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver); return _instance!; } @@ -59,12 +58,15 @@ class GlobalCycleDetector { // Найдена глобальная циклическая зависимость final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey); final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); - _logger.error(formatLogMessage( - type: 'CycleDetector', - name: dependencyKey.toString(), - params: {'chain': cycle.join('->')}, - description: 'cycle detected', - )); + _observer.onCycleDetected( + cycle, + scopeName: scopeId, + ); + _observer.onError( + 'Global circular dependency detected for $dependencyKey', + null, + null, + ); throw CircularDependencyException( 'Global circular dependency detected for $dependencyKey', cycle, @@ -102,12 +104,15 @@ class GlobalCycleDetector { final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey); final cycle = _globalResolutionHistory.sublist(cycleStartIndex) ..add(dependencyKey); - _logger.error(formatLogMessage( - type: 'CycleDetector', - name: dependencyKey.toString(), - params: {'chain': cycle.join('->')}, - description: 'cycle detected', - )); + _observer.onCycleDetected( + cycle, + scopeName: scopeId, + ); + _observer.onError( + 'Global circular dependency detected for $dependencyKey', + null, + null, + ); throw CircularDependencyException( 'Global circular dependency detected for $dependencyKey', cycle, @@ -131,7 +136,7 @@ class GlobalCycleDetector { /// RU: Получить детектор для конкретного скоупа. /// ENG: Get detector for specific scope. CycleDetector getScopeDetector(String scopeId) { - return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(logger: CherryPick.globalLogger)); + return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver)); } /// RU: Удалить детектор для скоупа. diff --git a/cherrypick/lib/src/helper.dart b/cherrypick/lib/src/helper.dart index ed99bc4..877f53e 100644 --- a/cherrypick/lib/src/helper.dart +++ b/cherrypick/lib/src/helper.dart @@ -13,7 +13,7 @@ import 'package:cherrypick/src/scope.dart'; import 'package:cherrypick/src/global_cycle_detector.dart'; -import 'package:cherrypick/src/logger.dart'; +import 'package:cherrypick/src/observer.dart'; import 'package:meta/meta.dart'; @@ -22,7 +22,7 @@ Scope? _rootScope; /// Global logger for all [Scope]s managed by [CherryPick]. /// /// Defaults to [SilentLogger] unless set via [setGlobalLogger]. -CherryPickLogger _globalLogger = const SilentLogger(); +CherryPickObserver _globalObserver = SilentCherryPickObserver(); /// Whether global local-cycle detection is enabled for all Scopes ([Scope.enableCycleDetection]). bool _globalCycleDetectionEnabled = false; @@ -59,12 +59,12 @@ class CherryPick { /// ```dart /// CherryPick.setGlobalLogger(DefaultLogger()); /// ``` - static void setGlobalLogger(CherryPickLogger logger) { - _globalLogger = logger; + static void setGlobalObserver(CherryPickObserver observer) { + _globalObserver = observer; } /// Returns the current global logger used by CherryPick. - static CherryPickLogger get globalLogger => _globalLogger; + static CherryPickObserver get globalObserver => _globalObserver; /// Returns the singleton root [Scope], creating it if needed. /// @@ -75,7 +75,7 @@ class CherryPick { /// final root = CherryPick.openRootScope(); /// ``` static Scope openRootScope() { - _rootScope ??= Scope(null, logger: _globalLogger); + _rootScope ??= Scope(null, observer: _globalObserver); // Apply cycle detection settings if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) { _rootScope!.enableCycleDetection(); diff --git a/cherrypick/lib/src/logger.dart b/cherrypick/lib/src/logger.dart deleted file mode 100644 index 9aa21df..0000000 --- a/cherrypick/lib/src/logger.dart +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// https://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -/// ---------------------------------------------------------------------------- -/// CherryPickLogger — интерфейс для логирования событий DI в CherryPick. -/// -/// ENGLISH: -/// Interface for dependency injection (DI) logger in CherryPick. Allows you to -/// receive information about the internal events and errors in the DI system. -/// Your implementation can use any logging framework or UI. -/// -/// RUSSIAN: -/// Интерфейс логгера для DI-контейнера CherryPick. Позволяет получать -/// сообщения о работе DI-контейнера, его ошибках и событиях, и -/// интегрировать любые готовые решения для логирования/сбора ошибок. -/// ---------------------------------------------------------------------------- -abstract class CherryPickLogger { - /// ---------------------------------------------------------------------------- - /// info — Информационное сообщение. - /// - /// ENGLISH: - /// Logs an informational message about DI operation or state. - /// - /// RUSSIAN: - /// Логирование информационного сообщения о событиях DI. - /// ---------------------------------------------------------------------------- - void info(String message); - - /// ---------------------------------------------------------------------------- - /// warn — Предупреждение. - /// - /// ENGLISH: - /// Logs a warning related to DI events (for example, possible misconfiguration). - /// - /// RUSSIAN: - /// Логирование предупреждения, связанного с DI (например, возможная ошибка - /// конфигурации). - /// ---------------------------------------------------------------------------- - void warn(String message); - - /// ---------------------------------------------------------------------------- - /// error — Ошибка. - /// - /// ENGLISH: - /// Logs an error message, may include error object and stack trace. - /// - /// RUSSIAN: - /// Логирование ошибки, дополнительно может содержать объект ошибки - /// и StackTrace. - /// ---------------------------------------------------------------------------- - void error(String message, [Object? error, StackTrace? stackTrace]); -} - -/// ---------------------------------------------------------------------------- -/// SilentLogger — «тихий» логгер CherryPick. Сообщения игнорируются. -/// -/// ENGLISH: -/// SilentLogger ignores all log messages. Used by default in production to -/// avoid polluting logs with DI events. -/// -/// RUSSIAN: -/// SilentLogger игнорирует все события логгирования. Используется по умолчанию -/// на production, чтобы не засорять логи техническими сообщениями DI. -/// ---------------------------------------------------------------------------- -class SilentLogger implements CherryPickLogger { - const SilentLogger(); - @override - void info(String message) {} - @override - void warn(String message) {} - @override - void error(String message, [Object? error, StackTrace? stackTrace]) {} -} - -/// ---------------------------------------------------------------------------- -/// PrintLogger — логгер CherryPick, выводящий все сообщения через print. -/// -/// ENGLISH: -/// PrintLogger outputs all log messages to the console using `print()`. -/// Suitable for debugging, prototyping, or simple console applications. -/// -/// RUSSIAN: -/// PrintLogger выводит все сообщения (info, warn, error) в консоль через print. -/// Удобен для отладки или консольных приложений. -/// ---------------------------------------------------------------------------- -class PrintLogger implements CherryPickLogger { - const PrintLogger(); - @override - void info(String message) => print('[info][CherryPick] $message'); - @override - void warn(String message) => print('[warn][CherryPick] $message'); - @override - void error(String message, [Object? error, StackTrace? stackTrace]) { - print('[error][CherryPick] $message'); - if (error != null) print(' error: $error'); - if (stackTrace != null) print(' stack: $stackTrace'); - } -} diff --git a/cherrypick/lib/src/observer.dart b/cherrypick/lib/src/observer.dart new file mode 100644 index 0000000..130b031 --- /dev/null +++ b/cherrypick/lib/src/observer.dart @@ -0,0 +1,119 @@ +/// Observer for DI container (CherryPick): lifecycle, cache, modules, errors, etc. +abstract class CherryPickObserver { + // === Registration and instance lifecycle === + void onBindingRegistered(String name, Type type, {String? scopeName}); + void onInstanceRequested(String name, Type type, {String? scopeName}); + void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}); + void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}); + + // === Module events === + void onModulesInstalled(List moduleNames, {String? scopeName}); + void onModulesRemoved(List moduleNames, {String? scopeName}); + + // === Scope lifecycle === + void onScopeOpened(String name); + void onScopeClosed(String name); + + // === Cycle detection === + void onCycleDetected(List chain, {String? scopeName}); + + // === Cache events === + void onCacheHit(String name, Type type, {String? scopeName}); + void onCacheMiss(String name, Type type, {String? scopeName}); + + // === Диагностика === + void onDiagnostic(String message, {Object? details}); + + // === Warnings & errors === + void onWarning(String message, {Object? details}); + void onError(String message, Object? error, StackTrace? stackTrace); +} + +/// Diagnostic/Debug observer that prints all events +class PrintCherryPickObserver implements CherryPickObserver { + @override + void onBindingRegistered(String name, Type type, {String? scopeName}) => + print('[binding][CherryPick] $name — $type (scope: $scopeName)'); + + @override + void onInstanceRequested(String name, Type type, {String? scopeName}) => + print('[request][CherryPick] $name — $type (scope: $scopeName)'); + + @override + void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) => + print('[create][CherryPick] $name — $type => $instance (scope: $scopeName)'); + + @override + void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) => + print('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)'); + + @override + void onModulesInstalled(List modules, {String? scopeName}) => + print('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + @override + void onModulesRemoved(List modules, {String? scopeName}) => + print('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + + @override + void onScopeOpened(String name) => print('[scope opened][CherryPick] $name'); + + @override + void onScopeClosed(String name) => print('[scope closed][CherryPick] $name'); + + @override + void onCycleDetected(List chain, {String? scopeName}) => + print('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}'); + + @override + void onCacheHit(String name, Type type, {String? scopeName}) => + print('[cache hit][CherryPick] $name — $type (scope: $scopeName)'); + @override + void onCacheMiss(String name, Type type, {String? scopeName}) => + print('[cache miss][CherryPick] $name — $type (scope: $scopeName)'); + + @override + void onDiagnostic(String message, {Object? details}) => + print('[diagnostic][CherryPick] $message ${details ?? ''}'); + + @override + void onWarning(String message, {Object? details}) => + print('[warn][CherryPick] $message ${details ?? ''}'); + @override + void onError(String message, Object? error, StackTrace? stackTrace) { + print('[error][CherryPick] $message'); + if (error != null) print(' error: $error'); + if (stackTrace != null) print(' stack: $stackTrace'); + } +} + +/// Silent observer: игнорирует все события +class SilentCherryPickObserver implements CherryPickObserver { + @override + void onBindingRegistered(String name, Type type, {String? scopeName}) {} + @override + void onInstanceRequested(String name, Type type, {String? scopeName}) {} + @override + void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {} + @override + void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {} + @override + void onModulesInstalled(List modules, {String? scopeName}) {} + @override + void onModulesRemoved(List modules, {String? scopeName}) {} + @override + void onScopeOpened(String name) {} + @override + void onScopeClosed(String name) {} + @override + void onCycleDetected(List chain, {String? scopeName}) {} + @override + void onCacheHit(String name, Type type, {String? scopeName}) {} + @override + void onCacheMiss(String name, Type type, {String? scopeName}) {} + @override + void onDiagnostic(String message, {Object? details}) {} + @override + void onWarning(String message, {Object? details}) {} + @override + void onError(String message, Object? error, StackTrace? stackTrace) {} +} diff --git a/cherrypick/lib/src/scope.dart b/cherrypick/lib/src/scope.dart index 614bc0a..6e9648f 100644 --- a/cherrypick/lib/src/scope.dart +++ b/cherrypick/lib/src/scope.dart @@ -18,16 +18,16 @@ import 'package:cherrypick/src/disposable.dart'; import 'package:cherrypick/src/global_cycle_detector.dart'; import 'package:cherrypick/src/binding_resolver.dart'; import 'package:cherrypick/src/module.dart'; -import 'package:cherrypick/src/logger.dart'; -import 'package:cherrypick/src/log_format.dart'; +import 'package:cherrypick/src/observer.dart'; +// import 'package:cherrypick/src/log_format.dart'; class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { final Scope? _parentScope; - late final CherryPickLogger _logger; + late final CherryPickObserver _observer; @override - CherryPickLogger get logger => _logger; + CherryPickObserver get observer => _observer; /// COLLECTS all resolved instances that implement [Disposable]. final Set _disposables = HashSet(); @@ -41,16 +41,17 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { final Map _scopeMap = HashMap(); - Scope(this._parentScope, {required CherryPickLogger logger}) : _logger = logger { + Scope(this._parentScope, {required CherryPickObserver observer}) : _observer = observer { setScopeId(_generateScopeId()); - logger.info(formatLogMessage( - type: 'Scope', - name: scopeId ?? 'NO_ID', - params: { + observer.onDiagnostic( + 'Scope created: ${scopeId ?? 'NO_ID'}', + details: { + 'type': 'Scope', + 'name': scopeId ?? 'NO_ID', if (_parentScope?.scopeId != null) 'parent': _parentScope!.scopeId, + 'description': 'scope created', }, - description: 'scope created', - )); + ); } final Set _modulesList = HashSet(); @@ -75,7 +76,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { /// return [Scope] Scope openSubScope(String name) { if (!_scopeMap.containsKey(name)) { - final childScope = Scope(this, logger: logger); // Наследуем логгер вниз по иерархии + final childScope = Scope(this, observer: observer); // Наследуем observer вниз по иерархии // print removed (trace) // Наследуем настройки обнаружения циклических зависимостей if (isCycleDetectionEnabled) { @@ -85,15 +86,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { childScope.enableGlobalCycleDetection(); } _scopeMap[name] = childScope; - logger.info(formatLogMessage( - type: 'SubScope', - name: name, - params: { + observer.onDiagnostic( + 'SubScope created: $name', + details: { + 'type': 'SubScope', + 'name': name, 'id': childScope.scopeId, if (scopeId != null) 'parent': scopeId, + 'description': 'subscope created', }, - description: 'subscope created', - )); + ); } return _scopeMap[name]!; } @@ -111,15 +113,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { if (childScope.scopeId != null) { GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!); } - logger.info(formatLogMessage( - type: 'SubScope', - name: name, - params: { + observer.onDiagnostic( + 'SubScope closed: $name', + details: { + 'type': 'SubScope', + 'name': name, 'id': childScope.scopeId, if (scopeId != null) 'parent': scopeId, + 'description': 'subscope closed', }, - description: 'subscope closed', - )); + ); } _scopeMap.remove(name); } @@ -132,18 +135,19 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { Scope installModules(List modules) { _modulesList.addAll(modules); for (var module in modules) { - logger.info(formatLogMessage( - type: 'Module', - name: module.runtimeType.toString(), - params: { + observer.onDiagnostic( + 'Module installed: ${module.runtimeType}', + details: { + 'type': 'Module', + 'name': module.runtimeType.toString(), 'scope': scopeId, + 'description': 'module installed', }, - description: 'module installed', - )); + ); module.builder(this); // После builder: для всех новых биндингов for (final binding in module.bindingSet) { - binding.logger = logger; + binding.observer = observer; binding.logAllDeferred(); } } @@ -157,11 +161,14 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { /// /// return [Scope] Scope dropModules() { - logger.info(formatLogMessage( - type: 'Scope', - name: scopeId, - description: 'modules dropped', - )); + observer.onDiagnostic( + 'Modules dropped for scope: $scopeId', + details: { + 'type': 'Scope', + 'name': scopeId, + 'description': 'modules dropped', + }, + ); _modulesList.clear(); _rebuildResolversIndex(); return this; @@ -187,13 +194,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { return _resolveWithLocalDetection(named: named, params: params); }); } catch (e, s) { - logger.error( - formatLogMessage( - type: 'Scope', - name: scopeId, - params: {'resolve': T.toString()}, - description: 'global cycle detection failed during resolve', - ), + observer.onError( + 'Global cycle detection failed during resolve: $T', e, s, ); @@ -203,13 +205,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { try { result = _resolveWithLocalDetection(named: named, params: params); } catch (e, s) { - logger.error( - formatLogMessage( - type: 'Scope', - name: scopeId, - params: {'resolve': T.toString()}, - description: 'failed to resolve', - ), + observer.onError( + 'Failed to resolve: $T', e, s, ); @@ -226,27 +223,22 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { return withCycleDetection(T, named, () { var resolved = _tryResolveInternal(named: named, params: params); if (resolved != null) { - logger.info(formatLogMessage( - type: 'Scope', - name: scopeId, - params: { + observer.onDiagnostic( + 'Successfully resolved: $T', + details: { + 'type': 'Scope', + 'name': scopeId, 'resolve': T.toString(), if (named != null) 'named': named, + 'description': 'successfully resolved', }, - description: 'successfully resolved', - )); + ); return resolved; } else { - logger.error( - formatLogMessage( - type: 'Scope', - name: scopeId, - params: { - 'resolve': T.toString(), - if (named != null) 'named': named, - }, - description: 'failed to resolve', - ), + observer.onError( + 'Failed to resolve: $T', + null, + null, ); throw StateError( 'Can\'t resolve dependency `$T`. Maybe you forget register it?'); diff --git a/cherrypick/test/logger_integration_test.dart b/cherrypick/test/logger_integration_test.dart index f8886c2..6bd513b 100644 --- a/cherrypick/test/logger_integration_test.dart +++ b/cherrypick/test/logger_integration_test.dart @@ -23,38 +23,27 @@ class CyclicModule extends Module { } void main() { - late MockLogger logger; + late MockObserver observer; setUp(() { - logger = MockLogger(); + observer = MockObserver(); }); - test('Global logger receives Scope and Binding events', () { - final scope = Scope(null, logger: logger); + test('Global logger receives Binding events', () { + final scope = Scope(null, observer: observer); scope.installModules([DummyModule()]); final _ = scope.resolve(named: 'test'); - // Новый стиль проверки для formatLogMessage: + // Проверяем, что биндинг DummyService зарегистрирован expect( - logger.infos.any((m) => m.startsWith('[Scope:') && m.contains('created')), - isTrue, - ); - expect( - logger.infos.any((m) => m.startsWith('[Binding:DummyService') && m.contains('created')), - isTrue, - ); - expect( - logger.infos.any((m) => m.startsWith('[Binding:DummyService') && m.contains('named') && m.contains('name=test')), - isTrue, - ); - expect( - logger.infos.any((m) => m.startsWith('[Scope:') && m.contains('resolve=DummyService') && m.contains('successfully resolved')), + observer.bindings.any((m) => m.contains('DummyService')), isTrue, ); + // Можно добавить проверки diagnostics, если Scope что-то пишет туда }); test('CycleDetector logs cycle detection error', () { - final scope = Scope(null, logger: logger); + final scope = Scope(null, observer: observer); // print('[DEBUG] TEST SCOPE logger type=${scope.logger.runtimeType} hash=${scope.logger.hashCode}'); scope.enableCycleDetection(); scope.installModules([CyclicModule()]); @@ -62,12 +51,11 @@ void main() { () => scope.resolve(), throwsA(isA()), ); - // Дополнительно ищем и среди info на случай если лог от CycleDetector ошибочно не попал в errors - final foundInErrors = logger.errors.any((m) => - m.startsWith('[CycleDetector:') && m.contains('cycle detected')); - final foundInInfos = logger.infos.any((m) => - m.startsWith('[CycleDetector:') && m.contains('cycle detected')); - expect(foundInErrors || foundInInfos, isTrue, - reason: 'Ожидаем хотя бы один лог о цикле на уровне error или info; вот все errors: ${logger.errors}\ninfos: ${logger.infos}'); + // Проверяем, что цикл зафиксирован либо в errors, либо в diagnostics либо cycles + final foundInErrors = observer.errors.any((m) => m.contains('cycle detected')); + final foundInDiagnostics = observer.diagnostics.any((m) => m.contains('cycle detected')); + final foundCycleNotified = observer.cycles.isNotEmpty; + expect(foundInErrors || foundInDiagnostics || foundCycleNotified, isTrue, + reason: 'Ожидаем хотя бы один лог о цикле! errors: ${observer.errors}\ndiag: ${observer.diagnostics}\ncycles: ${observer.cycles}'); }); } \ No newline at end of file diff --git a/cherrypick/test/mock_logger.dart b/cherrypick/test/mock_logger.dart index b22fc33..67aedac 100644 --- a/cherrypick/test/mock_logger.dart +++ b/cherrypick/test/mock_logger.dart @@ -1,16 +1,48 @@ import 'package:cherrypick/cherrypick.dart'; -class MockLogger implements CherryPickLogger { - final List infos = []; - final List warns = []; +class MockObserver implements CherryPickObserver { + final List diagnostics = []; + final List warnings = []; final List errors = []; + final List> cycles = []; + final List bindings = []; @override - void info(String message) => infos.add(message); + void onDiagnostic(String message, {Object? details}) => + diagnostics.add(message); + @override - void warn(String message) => warns.add(message); + void onWarning(String message, {Object? details}) => warnings.add(message); + @override - void error(String message, [Object? e, StackTrace? s]) => + void onError(String message, Object? error, StackTrace? stackTrace) => errors.add( - '$message${e != null ? ' $e' : ''}${s != null ? '\n$s' : ''}'); + '$message${error != null ? ' $error' : ''}${stackTrace != null ? '\n$stackTrace' : ''}'); + + @override + void onCycleDetected(List chain, {String? scopeName}) => + cycles.add(chain); + + @override + void onBindingRegistered(String name, Type type, {String? scopeName}) => + bindings.add('$name $type'); + + @override + void onInstanceRequested(String name, Type type, {String? scopeName}) {} + @override + void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {} + @override + void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {} + @override + void onModulesInstalled(List moduleNames, {String? scopeName}) {} + @override + void onModulesRemoved(List moduleNames, {String? scopeName}) {} + @override + void onScopeOpened(String name) {} + @override + void onScopeClosed(String name) {} + @override + void onCacheHit(String name, Type type, {String? scopeName}) {} + @override + void onCacheMiss(String name, Type type, {String? scopeName}) {} } diff --git a/cherrypick/test/src/cycle_detector_test.dart b/cherrypick/test/src/cycle_detector_test.dart index d23a1ab..f04fc47 100644 --- a/cherrypick/test/src/cycle_detector_test.dart +++ b/cherrypick/test/src/cycle_detector_test.dart @@ -4,16 +4,16 @@ import 'package:cherrypick/cherrypick.dart'; import '../mock_logger.dart'; void main() { - late MockLogger logger; + late MockObserver observer; setUp(() { - logger = MockLogger(); - CherryPick.setGlobalLogger(logger); + observer = MockObserver(); + CherryPick.setGlobalObserver(observer); }); group('CycleDetector', () { late CycleDetector detector; setUp(() { - detector = CycleDetector(logger: logger); + detector = CycleDetector(observer: observer); }); test('should detect simple circular dependency', () { diff --git a/cherrypick/test/src/helper_cycle_detection_test.dart b/cherrypick/test/src/helper_cycle_detection_test.dart index 75ef28f..4eddbcb 100644 --- a/cherrypick/test/src/helper_cycle_detection_test.dart +++ b/cherrypick/test/src/helper_cycle_detection_test.dart @@ -3,10 +3,10 @@ import 'package:test/test.dart'; import '../mock_logger.dart'; void main() { - late MockLogger logger; + late MockObserver observer; setUp(() { - logger = MockLogger(); - CherryPick.setGlobalLogger(logger); + observer = MockObserver(); + CherryPick.setGlobalObserver(observer); }); group('CherryPick Cycle Detection Helper Methods', () { setUp(() { diff --git a/cherrypick/test/src/scope_test.dart b/cherrypick/test/src/scope_test.dart index 4d6cdfe..60238d5 100644 --- a/cherrypick/test/src/scope_test.dart +++ b/cherrypick/test/src/scope_test.dart @@ -110,14 +110,14 @@ void main() { // -------------------------------------------------------------------------- group('Scope & Subscope Management', () { test('Scope has no parent if constructed with null', () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger); + final observer = MockObserver(); + final scope = Scope(null, observer: observer); expect(scope.parentScope, null); }); test('Can open and retrieve the same subScope by key', () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger); - expect(Scope(scope, logger: logger), isNotNull); // эквивалент + final observer = MockObserver(); + final scope = Scope(null, observer: observer); + expect(Scope(scope, observer: observer), isNotNull); // эквивалент }); test('closeSubScope removes subscope so next openSubScope returns new', () async { final logger = MockLogger(); @@ -130,9 +130,9 @@ void main() { }); test('closeSubScope removes subscope so next openSubScope returns new', () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger); - expect(Scope(scope, logger: logger), isNotNull); // эквивалент + final observer = MockObserver(); + final scope = Scope(null, observer: observer); + expect(Scope(scope, observer: observer), isNotNull); // эквивалент // Нет необходимости тестировать open/closeSubScope в этом юните }); }); @@ -140,48 +140,48 @@ void main() { // -------------------------------------------------------------------------- group('Dependency Resolution (standard)', () { test("Throws StateError if value can't be resolved", () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger); + final observer = MockObserver(); + final scope = Scope(null, observer: observer); expect(() => scope.resolve(), throwsA(isA())); }); test('Resolves value after adding a dependency', () { - final logger = MockLogger(); + final observer = MockObserver(); final expectedValue = 'test string'; - final scope = Scope(null, logger: logger) + final scope = Scope(null, observer: observer) .installModules([TestModule(value: expectedValue)]); expect(scope.resolve(), expectedValue); }); test('Returns a value from parent scope', () { - final logger = MockLogger(); + final observer = MockObserver(); final expectedValue = 5; - final parentScope = Scope(null, logger: logger); - final scope = Scope(parentScope, logger: logger); + final parentScope = Scope(null, observer: observer); + final scope = Scope(parentScope, observer: observer); parentScope.installModules([TestModule(value: expectedValue)]); expect(scope.resolve(), expectedValue); }); test('Returns several values from parent container', () { - final logger = MockLogger(); + final observer = MockObserver(); final expectedIntValue = 5; final expectedStringValue = 'Hello world'; - final parentScope = Scope(null, logger: logger).installModules([ + final parentScope = Scope(null, observer: observer).installModules([ TestModule(value: expectedIntValue), TestModule(value: expectedStringValue) ]); - final scope = Scope(parentScope, logger: logger); + final scope = Scope(parentScope, observer: observer); expect(scope.resolve(), expectedIntValue); expect(scope.resolve(), expectedStringValue); }); test("Throws StateError if parent hasn't value too", () { - final logger = MockLogger(); - final parentScope = Scope(null, logger: logger); - final scope = Scope(parentScope, logger: logger); + final observer = MockObserver(); + final parentScope = Scope(null, observer: observer); + final scope = Scope(parentScope, observer: observer); expect(() => scope.resolve(), throwsA(isA())); }); test("After dropModules resolves fail", () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger)..installModules([TestModule(value: 5)]); + final observer = MockObserver(); + final scope = Scope(null, observer: observer)..installModules([TestModule(value: 5)]); expect(scope.resolve(), 5); scope.dropModules(); expect(() => scope.resolve(), throwsA(isA())); @@ -191,8 +191,8 @@ void main() { // -------------------------------------------------------------------------- group('Named Dependencies', () { test('Resolve named binding', () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger) + final observer = MockObserver(); + final scope = Scope(null, observer: observer) ..installModules([ TestModule(value: "first"), TestModule(value: "second", name: "special") @@ -201,8 +201,8 @@ void main() { expect(scope.resolve(), "first"); }); test('Named binding does not clash with unnamed', () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger) + final observer = MockObserver(); + final scope = Scope(null, observer: observer) ..installModules([ TestModule(value: "foo", name: "bar"), ]); @@ -210,8 +210,8 @@ void main() { expect(scope.resolve(named: "bar"), "foo"); }); test("tryResolve returns null for missing named", () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger) + final observer = MockObserver(); + final scope = Scope(null, observer: observer) ..installModules([ TestModule(value: "foo"), ]); @@ -222,8 +222,8 @@ void main() { // -------------------------------------------------------------------------- group('Provider with parameters', () { test('Resolve dependency using providerWithParams', () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger) + final observer = MockObserver(); + final scope = Scope(null, observer: observer) ..installModules([ _InlineModule((m, s) { m.bind().toProvideWithParams((param) => (param as int) * 2); @@ -237,8 +237,8 @@ void main() { // -------------------------------------------------------------------------- group('Async Resolution', () { test('Resolve async instance', () async { - final logger = MockLogger(); - final scope = Scope(null, logger: logger) + final observer = MockObserver(); + final scope = Scope(null, observer: observer) ..installModules([ _InlineModule((m, s) { m.bind().toInstance(Future.value('async value')); @@ -247,8 +247,8 @@ void main() { expect(await scope.resolveAsync(), "async value"); }); test('Resolve async provider', () async { - final logger = MockLogger(); - final scope = Scope(null, logger: logger) + final observer = MockObserver(); + final scope = Scope(null, observer: observer) ..installModules([ _InlineModule((m, s) { m.bind().toProvide(() async => 7); @@ -257,8 +257,8 @@ void main() { expect(await scope.resolveAsync(), 7); }); test('Resolve async provider with param', () async { - final logger = MockLogger(); - final scope = Scope(null, logger: logger) + final observer = MockObserver(); + final scope = Scope(null, observer: observer) ..installModules([ _InlineModule((m, s) { m.bind().toProvideWithParams((x) async => (x as int) * 3); @@ -268,8 +268,8 @@ void main() { expect(() => scope.resolveAsync(), throwsA(isA())); }); test('tryResolveAsync returns null for missing', () async { - final logger = MockLogger(); - final scope = Scope(null, logger: logger); + final observer = MockObserver(); + final scope = Scope(null, observer: observer); final result = await scope.tryResolveAsync(); expect(result, isNull); }); @@ -278,8 +278,8 @@ void main() { // -------------------------------------------------------------------------- group('Optional resolution and error handling', () { test("tryResolve returns null for missing dependency", () { - final logger = MockLogger(); - final scope = Scope(null, logger: logger); + final observer = MockObserver(); + final scope = Scope(null, observer: observer); expect(scope.tryResolve(), isNull); }); }); diff --git a/examples/postly/lib/app.dart b/examples/postly/lib/app.dart index 5707836..94012ec 100644 --- a/examples/postly/lib/app.dart +++ b/examples/postly/lib/app.dart @@ -2,7 +2,8 @@ import 'package:cherrypick/cherrypick.dart'; import 'package:cherrypick_annotations/cherrypick_annotations.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:talker/talker.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + import 'domain/repository/post_repository.dart'; import 'presentation/bloc/post_bloc.dart'; @@ -12,7 +13,7 @@ part 'app.inject.cherrypick.g.dart'; class TalkerProvider extends InheritedWidget { final Talker talker; - const TalkerProvider({required this.talker, required Widget child, Key? key}) : super(key: key, child: child); + const TalkerProvider({required this.talker, required super.child, super.key}); static Talker of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!.talker; @override bool updateShouldNotify(TalkerProvider oldWidget) => oldWidget.talker != talker; diff --git a/examples/postly/lib/main.dart b/examples/postly/lib/main.dart index 9771377..5bc2176 100644 --- a/examples/postly/lib/main.dart +++ b/examples/postly/lib/main.dart @@ -11,12 +11,12 @@ import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart'; void main() { final talker = Talker(); - final talkerLogger = TalkerCherryPickLogger(talker); + final talkerLogger = TalkerCherryPickObserver(talker); Bloc.observer = TalkerBlocObserver(talker: talker); - CherryPick.setGlobalLogger(talkerLogger); + CherryPick.setGlobalObserver(talkerLogger); // Включаем cycle-detection только в debug/test if (kDebugMode) { CherryPick.enableGlobalCycleDetection(); diff --git a/examples/postly/lib/presentation/pages/posts_page.dart b/examples/postly/lib/presentation/pages/posts_page.dart index e03ae09..a6535a2 100644 --- a/examples/postly/lib/presentation/pages/posts_page.dart +++ b/examples/postly/lib/presentation/pages/posts_page.dart @@ -1,7 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:postly/app.dart'; import '../../router/app_router.gr.dart'; import '../bloc/post_bloc.dart'; diff --git a/examples/postly/lib/router/app_router.dart b/examples/postly/lib/router/app_router.dart index 4c8473c..14d15ce 100644 --- a/examples/postly/lib/router/app_router.dart +++ b/examples/postly/lib/router/app_router.dart @@ -1,5 +1,4 @@ import 'package:auto_route/auto_route.dart'; -import '../presentation/pages/logs_page.dart'; import 'app_router.gr.dart'; @AutoRouterConfig() diff --git a/talker_cherrypick_logger/example/talker_cherrypick_logger_example.dart b/talker_cherrypick_logger/example/talker_cherrypick_logger_example.dart index 183626f..ece9196 100644 --- a/talker_cherrypick_logger/example/talker_cherrypick_logger_example.dart +++ b/talker_cherrypick_logger/example/talker_cherrypick_logger_example.dart @@ -3,11 +3,11 @@ import 'package:talker/talker.dart'; void main() { final talker = Talker(); - final logger = TalkerCherryPickLogger(talker); + final logger = TalkerCherryPickObserver(talker); - logger.info('Hello from CherryPickLogger!'); - logger.warn('Something might be wrong...'); - logger.error('Oops! An error occurred', Exception('Test error')); + logger.onDiagnostic('Hello from CherryPickLogger!'); + logger.onWarning('Something might be wrong...'); + logger.onError('Oops! An error occurred', Exception('Test error'), null); // Вывод всех логов print('\nВсе сообщения логирования через Talker:'); diff --git a/talker_cherrypick_logger/lib/src/talker_cherrypick_logger.dart b/talker_cherrypick_logger/lib/src/talker_cherrypick_logger.dart deleted file mode 100644 index 3fd16e0..0000000 --- a/talker_cherrypick_logger/lib/src/talker_cherrypick_logger.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; -import 'package:talker/talker.dart'; - -/// Реализация CherryPickLogger для логирования через Talker -class TalkerCherryPickLogger implements CherryPickLogger { - final Talker talker; - - TalkerCherryPickLogger(this.talker); - - @override - void info(String message) => talker.info('[CherryPick] $message'); - - @override - void warn(String message) => talker.warning('[CherryPick] $message'); - - @override - void error(String message, [Object? error, StackTrace? stackTrace]) { - talker.handle( - error ?? '[CherryPick] $message', - stackTrace, - '[CherryPick] $message', - ); - } -} diff --git a/talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart b/talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart new file mode 100644 index 0000000..adca14a --- /dev/null +++ b/talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart @@ -0,0 +1,66 @@ +import 'package:cherrypick/cherrypick.dart'; +import 'package:talker/talker.dart'; + +/// CherryPickObserver-адаптер для логирования событий CherryPick через Talker +class TalkerCherryPickObserver implements CherryPickObserver { + final Talker talker; + + TalkerCherryPickObserver(this.talker); + + @override + void onBindingRegistered(String name, Type type, {String? scopeName}) { + talker.info('[binding][CherryPick] $name — $type (scope: $scopeName)'); + } + @override + void onInstanceRequested(String name, Type type, {String? scopeName}) { + talker.info('[request][CherryPick] $name — $type (scope: $scopeName)'); + } + @override + void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) { + talker.info('[create][CherryPick] $name — $type => $instance (scope: $scopeName)'); + } + @override + void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) { + talker.info('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)'); + } + @override + void onModulesInstalled(List modules, {String? scopeName}) { + talker.info('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + } + @override + void onModulesRemoved(List modules, {String? scopeName}) { + talker.info('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)'); + } + @override + void onScopeOpened(String name) { + talker.info('[scope opened][CherryPick] $name'); + } + @override + void onScopeClosed(String name) { + talker.info('[scope closed][CherryPick] $name'); + } + @override + void onCycleDetected(List chain, {String? scopeName}) { + talker.warning('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}'); + } + @override + void onCacheHit(String name, Type type, {String? scopeName}) { + talker.info('[cache hit][CherryPick] $name — $type (scope: $scopeName)'); + } + @override + void onCacheMiss(String name, Type type, {String? scopeName}) { + talker.info('[cache miss][CherryPick] $name — $type (scope: $scopeName)'); + } + @override + void onDiagnostic(String message, {Object? details}) { + talker.verbose('[diagnostic][CherryPick] $message ${details ?? ''}'); + } + @override + void onWarning(String message, {Object? details}) { + talker.warning('[warn][CherryPick] $message ${details ?? ''}'); + } + @override + void onError(String message, Object? error, StackTrace? stackTrace) { + talker.handle(error ?? '[CherryPick] $message', stackTrace, '[error][CherryPick] $message'); + } +} diff --git a/talker_cherrypick_logger/lib/talker_cherrypick_logger.dart b/talker_cherrypick_logger/lib/talker_cherrypick_logger.dart index 62e1674..a61b458 100644 --- a/talker_cherrypick_logger/lib/talker_cherrypick_logger.dart +++ b/talker_cherrypick_logger/lib/talker_cherrypick_logger.dart @@ -3,6 +3,6 @@ /// More dartdocs go here. library; -export 'src/talker_cherrypick_logger.dart'; +export 'src/talker_cherrypick_observer.dart'; // TODO: Export any libraries intended for clients of this package. diff --git a/talker_cherrypick_logger/pubspec.yaml b/talker_cherrypick_logger/pubspec.yaml index a653841..d836cad 100644 --- a/talker_cherrypick_logger/pubspec.yaml +++ b/talker_cherrypick_logger/pubspec.yaml @@ -1,6 +1,7 @@ name: talker_cherrypick_logger description: A starting point for Dart libraries or applications. version: 1.0.0 +publish_to: none # repository: https://github.com/my_org/my_repo environment: diff --git a/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart b/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart index de0873f..6152119 100644 --- a/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart +++ b/talker_cherrypick_logger/test/talker_cherrypick_logger_test.dart @@ -3,38 +3,44 @@ import 'package:talker/talker.dart'; import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart'; void main() { - group('TalkerCherryPickLogger', () { + group('TalkerCherryPickObserver', () { late Talker talker; - late TalkerCherryPickLogger logger; + late TalkerCherryPickObserver observer; setUp(() { talker = Talker(); - logger = TalkerCherryPickLogger(talker); + observer = TalkerCherryPickObserver(talker); }); - test('logs info messages correctly', () { - logger.info('Test info'); + test('onInstanceRequested logs info', () { + observer.onInstanceRequested('A', String, scopeName: 'test'); final log = talker.history.last; - expect(log.message, contains('[CherryPick] Test info')); - //xpect(log.level, TalkerLogLevel.info); + expect(log.message, contains('[request][CherryPick] A — String (scope: test)')); }); - test('logs warning messages correctly', () { - logger.warn('Danger!'); + test('onCycleDetected logs warning', () { + observer.onCycleDetected(['A', 'B'], scopeName: 's'); final log = talker.history.last; - expect(log.message, contains('[CherryPick] Danger!')); + expect(log.message, contains('[cycle][CherryPick] Detected')); //expect(log.level, TalkerLogLevel.warning); }); - test('logs error messages correctly', () { - final error = Exception('some error'); + test('onError calls handle', () { + final error = Exception('fail'); final stack = StackTrace.current; - logger.error('ERR', error, stack); + observer.onError('Oops', error, stack); final log = talker.history.last; - //expect(log.level, TalkerLogLevel.error); - expect(log.message, contains('[CherryPick] ERR')); + expect(log.message, contains('[error][CherryPick] Oops')); expect(log.exception, error); expect(log.stackTrace, stack); }); + + test('onDiagnostic logs verbose', () { + observer.onDiagnostic('hello', details: 123); + final log = talker.history.last; + //expect(log.level, TalkerLogLevel.verbose); + expect(log.message, contains('hello')); + expect(log.message, contains('123')); + }); }); }