diff --git a/cherrypick/README.md b/cherrypick/README.md index f4b599d..5e0e8b7 100644 --- a/cherrypick/README.md +++ b/cherrypick/README.md @@ -234,6 +234,32 @@ class ApiClientImpl implements ApiClient { } ``` +## Logging + +CherryPick supports centralized logging of all dependency injection (DI) events and errors. You can globally enable logs for your application or test environment with: + +```dart +import 'package:cherrypick/cherrypick.dart'; + +void main() { + // Set a global logger before any scopes are created + CherryPick.setGlobalLogger(PrintLogger()); // or your custom logger + + final scope = CherryPick.openRootScope(); + // All DI actions and errors will now be logged! +} +``` +- All dependency resolution, scope creation, module installation, and circular dependency errors will be sent to your logger (via info/error method). +- By default, logs are off (SilentLogger is used in production). + +If you want fine-grained, test-local, or isolated logging, you can provide a logger directly to each scope: + +```dart +final logger = MockLogger(); +final scope = Scope(null, logger: logger); // works in tests for isolation +scope.installModules([...]); +``` + ## Features - [x] Main Scope and Named Subscopes diff --git a/cherrypick/example/cherrypick_logger_demo.dart b/cherrypick/example/cherrypick_logger_demo.dart new file mode 100644 index 0000000..29a60c3 --- /dev/null +++ b/cherrypick/example/cherrypick_logger_demo.dart @@ -0,0 +1,37 @@ +import 'package:cherrypick/cherrypick.dart'; + +/// Example of a simple service class. +class UserRepository { + String getUserName() => 'Sergey DI'; +} + +/// DI module for registering dependencies. +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().toInstance(UserRepository()); + } +} + +void main() { + // Set a global logger for the DI system + CherryPick.setGlobalLogger(PrintLogger()); + + // Open the root scope + final rootScope = CherryPick.openRootScope(); + + // Register the DI module + rootScope.installModules([AppModule()]); + + // Resolve a dependency (service) + final repo = rootScope.resolve(); + print('User: ${repo.getUserName()}'); + + // Work with a sub-scope (create/close) + final subScope = rootScope.openSubScope('feature.profile'); + subScope.closeSubScope('feature.profile'); + + // Demonstrate disabling and re-enabling logging + CherryPick.setGlobalLogger(const SilentLogger()); + rootScope.resolve(); // now without logs +} diff --git a/cherrypick/lib/cherrypick.dart b/cherrypick/lib/cherrypick.dart index 9532298..0ab1080 100644 --- a/cherrypick/lib/cherrypick.dart +++ b/cherrypick/lib/cherrypick.dart @@ -20,3 +20,4 @@ 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'; diff --git a/cherrypick/lib/src/binding.dart b/cherrypick/lib/src/binding.dart index e1669fb..8c8369d 100644 --- a/cherrypick/lib/src/binding.dart +++ b/cherrypick/lib/src/binding.dart @@ -16,14 +16,51 @@ import 'package:cherrypick/src/binding_resolver.dart'; /// RU: Класс Binding настраивает параметры экземпляра. /// ENG: The Binding class configures the settings for the instance. /// +import 'package:cherrypick/src/logger.dart'; + class Binding { late Type _key; String? _name; BindingResolver? _resolver; - Binding() { + CherryPickLogger? logger; + + // Deferred logging flags + bool _createdLogged = false; + bool _namedLogged = false; + bool _singletonLogged = false; + + Binding({this.logger}) { _key = T; + // Не логируем здесь! Делаем deferred лог после назначения logger + } + + void markCreated() { + if (!_createdLogged) { + logger?.info('Binding<$T> created'); + _createdLogged = true; + } + } + + void markNamed() { + if (isNamed && !_namedLogged) { + logger?.info('Binding<$T> named as [$_name]'); + _namedLogged = true; + } + } + + void markSingleton() { + if (isSingleton && !_singletonLogged) { + logger?.info('Binding<$T> singleton mode enabled'); + _singletonLogged = true; + } + } + + void logAllDeferred() { + markCreated(); + markNamed(); + markSingleton(); } /// RU: Метод возвращает тип экземпляра. @@ -58,6 +95,7 @@ class Binding { /// return [Binding] Binding withName(String name) { _name = name; + // Не логируем здесь, deferred log via markNamed() return this; } @@ -67,7 +105,6 @@ class Binding { /// return [Binding] Binding toInstance(Instance value) { _resolver = InstanceResolver(value); - return this; } @@ -77,7 +114,6 @@ class Binding { /// return [Binding] Binding toProvide(Provider value) { _resolver = ProviderResolver((_) => value.call(), withParams: false); - return this; } @@ -87,7 +123,6 @@ class Binding { /// return [Binding] Binding toProvideWithParams(ProviderWithParams value) { _resolver = ProviderResolver(value, withParams: true); - return this; } @@ -112,15 +147,28 @@ class Binding { /// return [Binding] Binding singleton() { _resolver?.toSingleton(); - + // Не логируем здесь, deferred log via markSingleton() return this; } T? resolveSync([dynamic params]) { - return resolver?.resolveSync(params); + final res = resolver?.resolveSync(params); + if (res != null) { + logger?.info('Binding<$T> resolveSync => object created/resolved.'); + } else { + logger?.warn('Binding<$T> resolveSync => returned null!'); + } + return res; } Future? resolveAsync([dynamic params]) { - return resolver?.resolveAsync(params); + final future = resolver?.resolveAsync(params); + if (future != null) { + future.then((res) => logger?.info('Binding<$T> resolveAsync => Future resolved')) + .catchError((e, s) => logger?.error('Binding<$T> resolveAsync error', e, s)); + } else { + logger?.warn('Binding<$T> resolveAsync => returned null!'); + } + return future; } } diff --git a/cherrypick/lib/src/cycle_detector.dart b/cherrypick/lib/src/cycle_detector.dart index f30ed4e..dcbf5b4 100644 --- a/cherrypick/lib/src/cycle_detector.dart +++ b/cherrypick/lib/src/cycle_detector.dart @@ -12,6 +12,7 @@ // import 'dart:collection'; +import 'package:cherrypick/src/logger.dart'; /// RU: Исключение, выбрасываемое при обнаружении циклической зависимости. /// ENG: Exception thrown when a circular dependency is detected. @@ -31,24 +32,30 @@ class CircularDependencyException implements Exception { /// RU: Детектор циклических зависимостей для CherryPick DI контейнера. /// ENG: Circular dependency detector for CherryPick DI container. class CycleDetector { + final CherryPickLogger logger; // Стек текущих разрешаемых зависимостей final Set _resolutionStack = HashSet(); // История разрешения для построения цепочки зависимостей final List _resolutionHistory = []; + CycleDetector({CherryPickLogger? logger}) : logger = logger ?? const SilentLogger() { + // print removed (trace) + } + /// RU: Начинает отслеживание разрешения зависимости. /// ENG: Starts tracking dependency resolution. /// /// Throws [CircularDependencyException] if circular dependency is detected. void startResolving({String? named}) { final dependencyKey = _createDependencyKey(named); - + logger.info('CycleDetector: startResolving $dependencyKey stackSize=${_resolutionStack.length}'); if (_resolutionStack.contains(dependencyKey)) { // Найдена циклическая зависимость final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey); final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); - + // print removed (trace) + logger.error('CycleDetector: CYCLE DETECTED! $dependencyKey chain: ${cycle.join(' -> ')}'); throw CircularDependencyException( 'Circular dependency detected for $dependencyKey', cycle, @@ -63,8 +70,8 @@ class CycleDetector { /// ENG: Finishes tracking dependency resolution. void finishResolving({String? named}) { final dependencyKey = _createDependencyKey(named); + logger.info('CycleDetector: finishResolving $dependencyKey'); _resolutionStack.remove(dependencyKey); - // Удаляем из истории только если это последний элемент if (_resolutionHistory.isNotEmpty && _resolutionHistory.last == dependencyKey) { @@ -75,6 +82,7 @@ class CycleDetector { /// RU: Очищает все состояние детектора. /// ENG: Clears all detector state. void clear() { + logger.info('CycleDetector: clear'); _resolutionStack.clear(); _resolutionHistory.clear(); } @@ -103,16 +111,21 @@ class CycleDetector { mixin CycleDetectionMixin { CycleDetector? _cycleDetector; + CherryPickLogger? get logger; + /// RU: Включает обнаружение циклических зависимостей. /// ENG: Enables circular dependency detection. void enableCycleDetection() { - _cycleDetector = CycleDetector(); + // print removed (trace) + _cycleDetector = CycleDetector(logger: logger); + logger?.info('CycleDetection: cycle detection enabled'); } /// RU: Отключает обнаружение циклических зависимостей. /// ENG: Disables circular dependency detection. void disableCycleDetection() { _cycleDetector?.clear(); + logger?.info('CycleDetection: cycle detection disabled'); _cycleDetector = null; } diff --git a/cherrypick/lib/src/helper.dart b/cherrypick/lib/src/helper.dart index 30362a4..ab53535 100644 --- a/cherrypick/lib/src/helper.dart +++ b/cherrypick/lib/src/helper.dart @@ -12,30 +12,72 @@ // import 'package:cherrypick/src/scope.dart'; import 'package:cherrypick/src/global_cycle_detector.dart'; +import 'package:cherrypick/src/logger.dart'; import 'package:meta/meta.dart'; +CherryPickLogger? _globalLogger = const SilentLogger(); + Scope? _rootScope; bool _globalCycleDetectionEnabled = false; bool _globalCrossScopeCycleDetectionEnabled = false; class CherryPick { + /// Позволяет задать глобальный логгер для всей DI-системы. + /// ---------------------------------------------------------------------------- + /// setGlobalLogger — установка глобального логгера для всей системы CherryPick DI. + /// + /// ENGLISH: + /// Sets the global logger for all CherryPick DI containers and scopes. + /// All dependency resolution, scope lifecycle, and error events will use + /// this logger instance for info/warn/error output. + /// Can be used to connect a custom logger (e.g. to external monitoring or UI). + /// + /// Usage example: + /// ```dart + /// import 'package:cherrypick/cherrypick.dart'; + /// + /// void main() { + /// CherryPick.setGlobalLogger(PrintLogger()); // Or your custom logger + /// final rootScope = CherryPick.openRootScope(); + /// // DI logs and errors will now go to your logger + /// } + /// ``` + /// + /// RUSSIAN: + /// Устанавливает глобальный логгер для всей DI-системы CherryPick. + /// Все операции разрешения зависимостей, жизненного цикла скоупов и ошибки + /// будут регистрироваться через этот логгер (info/warn/error). + /// Можно подключить свою реализацию для интеграции со сторонними системами. + /// + /// Пример использования: + /// ```dart + /// import 'package:cherrypick/cherrypick.dart'; + /// + /// void main() { + /// CherryPick.setGlobalLogger(PrintLogger()); // Или ваш собственный логгер + /// final rootScope = CherryPick.openRootScope(); + /// // Все события DI и ошибки попадут в ваш логгер. + /// } + /// ``` + /// ---------------------------------------------------------------------------- + static void setGlobalLogger(CherryPickLogger logger) { + _globalLogger = logger; + } + /// RU: Метод открывает главный [Scope]. /// ENG: The method opens the main [Scope]. /// /// return static Scope openRootScope() { - _rootScope ??= Scope(null); - + _rootScope ??= Scope(null, logger: _globalLogger); // Применяем глобальную настройку обнаружения циклических зависимостей if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) { _rootScope!.enableCycleDetection(); } - // Применяем глобальную настройку обнаружения между скоупами if (_globalCrossScopeCycleDetectionEnabled && !_rootScope!.isGlobalCycleDetectionEnabled) { _rootScope!.enableGlobalCycleDetection(); } - return _rootScope!; } diff --git a/cherrypick/lib/src/logger.dart b/cherrypick/lib/src/logger.dart new file mode 100644 index 0000000..bdacc47 --- /dev/null +++ b/cherrypick/lib/src/logger.dart @@ -0,0 +1,108 @@ +// +// 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 +// http://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/scope.dart b/cherrypick/lib/src/scope.dart index 55cd9ec..a09f053 100644 --- a/cherrypick/lib/src/scope.dart +++ b/cherrypick/lib/src/scope.dart @@ -17,12 +17,22 @@ import 'package:cherrypick/src/cycle_detector.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'; -Scope openRootScope() => Scope(null); + +CherryPickLogger _globalLogger = const SilentLogger(); + +Scope openRootScope({CherryPickLogger? logger}) => Scope(null, logger: logger); class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { final Scope? _parentScope; + CherryPickLogger? _logger; + + @override + CherryPickLogger? get logger => _logger; + set logger(CherryPickLogger? value) => _logger = value; + /// RU: Метод возвращает родительский [Scope]. /// /// ENG: The method returns the parent [Scope]. @@ -32,9 +42,11 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { final Map _scopeMap = HashMap(); - Scope(this._parentScope) { + Scope(this._parentScope, {CherryPickLogger? logger}) : _logger = logger ?? _globalLogger { + // print removed (trace) // Генерируем уникальный ID для скоупа setScopeId(_generateScopeId()); + _logger?.info('Scope created: id=${scopeId ?? "NO_ID"}, parent=${_parentScope?.scopeId}'); } final Set _modulesList = HashSet(); @@ -59,8 +71,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { /// return [Scope] Scope openSubScope(String name) { if (!_scopeMap.containsKey(name)) { - final childScope = Scope(this); - + final childScope = Scope(this, logger: logger); // Наследуем логгер вниз по иерархии + // print removed (trace) // Наследуем настройки обнаружения циклических зависимостей if (isCycleDetectionEnabled) { childScope.enableCycleDetection(); @@ -68,8 +80,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { if (isGlobalCycleDetectionEnabled) { childScope.enableGlobalCycleDetection(); } - _scopeMap[name] = childScope; + logger?.info('SubScope created: $name, id=${childScope.scopeId} (parent=$scopeId)'); } return _scopeMap[name]!; } @@ -86,6 +98,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { if (childScope.scopeId != null) { GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!); } + logger?.info('SubScope closed: $name, id=${childScope.scopeId} (parent=$scopeId)'); } _scopeMap.remove(name); } @@ -98,7 +111,13 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { Scope installModules(List modules) { _modulesList.addAll(modules); for (var module in modules) { + logger?.info('Installing module: ${module.runtimeType} in scope $scopeId'); module.builder(this); + // После builder: для всех новых биндингов + for (final binding in module.bindingSet) { + binding.logger = logger; + binding.logAllDeferred(); + } } _rebuildResolversIndex(); return this; @@ -110,7 +129,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { /// /// return [Scope] Scope dropModules() { - // [AlexeyYuPopkov](https://github.com/AlexeyYuPopkov) Thank you for the [Removed exception "ConcurrentModificationError"](https://github.com/pese-git/cherrypick/pull/2) + logger?.info('Modules dropped from scope: $scopeId'); _modulesList.clear(); _rebuildResolversIndex(); return this; @@ -130,11 +149,21 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { T resolve({String? named, dynamic params}) { // Используем глобальное отслеживание, если включено if (isGlobalCycleDetectionEnabled) { - return withGlobalCycleDetection(T, named, () { - return _resolveWithLocalDetection(named: named, params: params); - }); + try { + return withGlobalCycleDetection(T, named, () { + return _resolveWithLocalDetection(named: named, params: params); + }); + } catch (e, s) { + logger?.error('Global cycle detection failed during resolve<$T>', e, s); + rethrow; + } } else { - return _resolveWithLocalDetection(named: named, params: params); + try { + return _resolveWithLocalDetection(named: named, params: params); + } catch (e, s) { + logger?.error('Failed to resolve<$T>', e, s); + rethrow; + } } } @@ -144,8 +173,10 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { return withCycleDetection(T, named, () { var resolved = _tryResolveInternal(named: named, params: params); if (resolved != null) { + logger?.info('Resolve<$T> [named=$named]: successfully resolved in scope $scopeId.'); return resolved; } else { + logger?.error('Failed to resolve<$T> [named=$named] in scope $scopeId.'); 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 new file mode 100644 index 0000000..282c9d8 --- /dev/null +++ b/cherrypick/test/logger_integration_test.dart @@ -0,0 +1,60 @@ +import 'package:cherrypick/cherrypick.dart'; +import 'package:test/test.dart'; +import 'mock_logger.dart'; + +class DummyService {} + +class DummyModule extends Module { + @override + void builder(Scope currentScope) { + bind().toInstance(DummyService()).withName('test'); + } +} + +class A {} +class B {} + +class CyclicModule extends Module { + @override + void builder(Scope cs) { + bind().toProvide(() => cs.resolve() as A); + bind().toProvide(() => cs.resolve() as B); + } +} + +void main() { + late MockLogger logger; + + setUp(() { + logger = MockLogger(); + }); + + test('Global logger receives Scope and Binding events', () { + final scope = Scope(null, logger: logger); + scope.installModules([DummyModule()]); + final _ = scope.resolve(named: 'test'); + + expect(logger.infos.any((m) => m.contains('Scope created')), isTrue); + expect(logger.infos.any((m) => m.contains('Binding created')), isTrue); + expect(logger.infos.any((m) => + m.contains('Binding named as [test]') || m.contains('named as [test]')), isTrue); + expect(logger.infos.any((m) => + m.contains('Resolve [named=test]: successfully resolved') || + m.contains('Resolve [named=test]: successfully resolved in scope')), isTrue); + }); + + test('CycleDetector logs cycle detection error', () { + final scope = Scope(null, logger: logger); + scope.enableCycleDetection(); + scope.installModules([CyclicModule()]); + expect( + () => scope.resolve(), + throwsA(isA()), + ); + expect( + logger.errors.any((m) => + m.contains('CYCLE DETECTED!') || m.contains('Circular dependency detected')), + isTrue, + ); + }); +} \ No newline at end of file diff --git a/cherrypick/test/mock_logger.dart b/cherrypick/test/mock_logger.dart new file mode 100644 index 0000000..b22fc33 --- /dev/null +++ b/cherrypick/test/mock_logger.dart @@ -0,0 +1,16 @@ +import 'package:cherrypick/cherrypick.dart'; + +class MockLogger implements CherryPickLogger { + final List infos = []; + final List warns = []; + final List errors = []; + + @override + void info(String message) => infos.add(message); + @override + void warn(String message) => warns.add(message); + @override + void error(String message, [Object? e, StackTrace? s]) => + errors.add( + '$message${e != null ? ' $e' : ''}${s != null ? '\n$s' : ''}'); +} diff --git a/cherrypick/test/src/cycle_detector_test.dart b/cherrypick/test/src/cycle_detector_test.dart index 5ca84b7..58186fd 100644 --- a/cherrypick/test/src/cycle_detector_test.dart +++ b/cherrypick/test/src/cycle_detector_test.dart @@ -1,7 +1,5 @@ -import 'package:cherrypick/src/cycle_detector.dart'; -import 'package:cherrypick/src/module.dart'; -import 'package:cherrypick/src/scope.dart'; import 'package:test/test.dart'; +import 'package:cherrypick/cherrypick.dart'; void main() { group('CycleDetector', () { diff --git a/cherrypick/test/src/helper_cycle_detection_test.dart b/cherrypick/test/src/helper_cycle_detection_test.dart index afe968d..c63dc82 100644 --- a/cherrypick/test/src/helper_cycle_detection_test.dart +++ b/cherrypick/test/src/helper_cycle_detection_test.dart @@ -1,7 +1,14 @@ import 'package:cherrypick/cherrypick.dart'; import 'package:test/test.dart'; +import '../mock_logger.dart'; +import 'package:cherrypick/cherrypick.dart'; void main() { + late MockLogger logger; + setUp(() { + logger = MockLogger(); + CherryPick.setGlobalLogger(logger); + }); group('CherryPick Cycle Detection Helper Methods', () { setUp(() { // Сбрасываем состояние перед каждым тестом diff --git a/doc/full_tutorial_en.md b/doc/full_tutorial_en.md index 03c24b9..483164f 100644 --- a/doc/full_tutorial_en.md +++ b/doc/full_tutorial_en.md @@ -313,7 +313,7 @@ final config = await scope.resolveAsync(); [`cherrypick_flutter`](https://pub.dev/packages/cherrypick_flutter) is the integration package for CherryPick DI in Flutter. It provides a convenient `CherryPickProvider` widget which sits in your widget tree and gives access to the root DI scope (and subscopes) from context. -### Features +## Features - **Global DI Scope Access:** Use `CherryPickProvider` to access rootScope and subscopes anywhere in the widget tree. @@ -356,6 +356,26 @@ class MyApp extends StatelessWidget { - You can create subscopes, e.g. for screens or modules: `final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");` +--- + +## Logging + +To enable logging of all dependency injection (DI) events and errors in CherryPick, set the global logger before creating your scopes: + +```dart +import 'package:cherrypick/cherrypick.dart'; + +void main() { + // Set a global logger before any scopes are created + CherryPick.setGlobalLogger(PrintLogger()); // or your own custom logger + final scope = CherryPick.openRootScope(); + // All DI events and cycle errors will now be sent to your logger +} +``` + +- By default, CherryPick uses SilentLogger (no output in production). +- Any dependency resolution, scope events, or cycle detection errors are logged via info/error on your logger. + --- ## CherryPick is not just for Flutter! diff --git a/doc/full_tutorial_ru.md b/doc/full_tutorial_ru.md index 9af1f20..52f6c25 100644 --- a/doc/full_tutorial_ru.md +++ b/doc/full_tutorial_ru.md @@ -358,6 +358,26 @@ class MyApp extends StatelessWidget { - Вы можете создавать подскоупы, если нужно, например, для экранов или модулей: `final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");` +--- + +## Логирование + +Чтобы включить вывод логов о событиях и ошибках DI в CherryPick, настройте глобальный логгер до создания любых scope: + +```dart +import 'package:cherrypick/cherrypick.dart'; + +void main() { + // Установите глобальный логгер до создания scope + CherryPick.setGlobalLogger(PrintLogger()); // или свой логгер + final scope = CherryPick.openRootScope(); + // Логи DI и циклов будут выводиться через ваш логгер +} +``` + +- По умолчанию используется SilentLogger (нет логов в продакшене). +- Любые ошибки резолва и события циклов логируются через info/error на логгере. + --- ## CherryPick подходит не только для Flutter! diff --git a/doc/quick_start_en.md b/doc/quick_start_en.md index 486ce6c..63ab9ae 100644 --- a/doc/quick_start_en.md +++ b/doc/quick_start_en.md @@ -79,6 +79,24 @@ Example: Cherrypick.closeRootScope(); ``` +## Logging + +To enable logging of all dependency injection (DI) events and errors in CherryPick, set the global logger before creating your scopes: + +```dart +import 'package:cherrypick/cherrypick.dart'; + +void main() { + // Set a global logger before any scopes are created + CherryPick.setGlobalLogger(PrintLogger()); // or your own custom logger + final scope = CherryPick.openRootScope(); + // All DI events and cycle errors will now be sent to your logger +} +``` + +- By default, CherryPick uses SilentLogger (no output in production). +- Any dependency resolution, scope events, or cycle detection errors are logged via info/error on your logger. + ## Example app diff --git a/doc/quick_start_ru.md b/doc/quick_start_ru.md index 94301f0..402fa6a 100644 --- a/doc/quick_start_ru.md +++ b/doc/quick_start_ru.md @@ -79,6 +79,24 @@ Scope - это контейнер, который хранит все дерев Cherrypick.closeRootScope(); ``` +## Логирование + +Чтобы включить вывод логов о событиях и ошибках DI в CherryPick, настройте глобальный логгер до создания любых scope: + +```dart +import 'package:cherrypick/cherrypick.dart'; + +void main() { + // Установите глобальный логгер до создания scope + CherryPick.setGlobalLogger(PrintLogger()); // или свой логгер + final scope = CherryPick.openRootScope(); + // Логи DI и циклов будут выводиться через ваш логгер +} +``` + +- По умолчанию используется SilentLogger (нет логов в продакшене). +- Любые ошибки резолва и события циклов логируются через info/error на логгере. + ## Пример приложения