feat(logger): add extensible logging API, usage examples, and bilingual documentation

- Introduce CherryPickLogger interface, PrintLogger and SilentLogger implementations
- Add setGlobalLogger() to CherryPick API for custom DI logging
- Log key events (scope, module, error) via logger throughout DI lifecycle
- Comprehensive comments and code documentation in both English and Russian
- Document usage of logging system in quick_start and full_tutorial documentation (EN/RU)
- Provide usage examples in docs and code comments
- No logging inside GlobalCycleDetectionMixin (design choice: exceptions handled at Scope, not detector/mixin level) and detailed architectural reasoning
- Update helper.dart, logger.dart: comments, examples, API doc improvements
BREAKING CHANGE: Projects can now inject any logger via CherryPick.setGlobalLogger; default log behavior clarified and docstrings/usage examples enhanced
This commit is contained in:
Sergey Penkovsky
2025-08-08 08:13:58 +03:00
parent 41d49e98d0
commit aa97632add
16 changed files with 492 additions and 29 deletions

View File

@@ -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

View File

@@ -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<UserRepository>().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<UserRepository>();
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<UserRepository>(); // now without logs
}

View File

@@ -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';

View File

@@ -16,14 +16,51 @@ 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';
class Binding<T> {
late Type _key;
String? _name;
BindingResolver<T>? _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<T> {
/// return [Binding]
Binding<T> withName(String name) {
_name = name;
// Не логируем здесь, deferred log via markNamed()
return this;
}
@@ -67,7 +105,6 @@ class Binding<T> {
/// return [Binding]
Binding<T> toInstance(Instance<T> value) {
_resolver = InstanceResolver<T>(value);
return this;
}
@@ -77,7 +114,6 @@ class Binding<T> {
/// return [Binding]
Binding<T> toProvide(Provider<T> value) {
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
return this;
}
@@ -87,7 +123,6 @@ class Binding<T> {
/// return [Binding]
Binding<T> toProvideWithParams(ProviderWithParams<T> value) {
_resolver = ProviderResolver<T>(value, withParams: true);
return this;
}
@@ -112,15 +147,28 @@ class Binding<T> {
/// return [Binding]
Binding<T> 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<T>? 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;
}
}

View File

@@ -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<String> _resolutionStack = HashSet<String>();
// История разрешения для построения цепочки зависимостей
final List<String> _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<T>({String? named}) {
final dependencyKey = _createDependencyKey<T>(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<T>({String? named}) {
final dependencyKey = _createDependencyKey<T>(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;
}

View File

@@ -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!;
}

View File

@@ -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');
}
}

View File

@@ -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<String, Scope> _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<Module> _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<Module> 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<T>({String? named, dynamic params}) {
// Используем глобальное отслеживание, если включено
if (isGlobalCycleDetectionEnabled) {
try {
return withGlobalCycleDetection<T>(T, named, () {
return _resolveWithLocalDetection<T>(named: named, params: params);
});
} catch (e, s) {
logger?.error('Global cycle detection failed during resolve<$T>', e, s);
rethrow;
}
} else {
try {
return _resolveWithLocalDetection<T>(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>(T, named, () {
var resolved = _tryResolveInternal<T>(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?');
}

View File

@@ -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<DummyService>().toInstance(DummyService()).withName('test');
}
}
class A {}
class B {}
class CyclicModule extends Module {
@override
void builder(Scope cs) {
bind<A>().toProvide(() => cs.resolve<B>() as A);
bind<B>().toProvide(() => cs.resolve<A>() 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<DummyService>(named: 'test');
expect(logger.infos.any((m) => m.contains('Scope created')), isTrue);
expect(logger.infos.any((m) => m.contains('Binding<DummyService> created')), isTrue);
expect(logger.infos.any((m) =>
m.contains('Binding<DummyService> named as [test]') || m.contains('named as [test]')), isTrue);
expect(logger.infos.any((m) =>
m.contains('Resolve<DummyService> [named=test]: successfully resolved') ||
m.contains('Resolve<DummyService> [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<A>(),
throwsA(isA<CircularDependencyException>()),
);
expect(
logger.errors.any((m) =>
m.contains('CYCLE DETECTED!') || m.contains('Circular dependency detected')),
isTrue,
);
});
}

View File

@@ -0,0 +1,16 @@
import 'package:cherrypick/cherrypick.dart';
class MockLogger implements CherryPickLogger {
final List<String> infos = [];
final List<String> warns = [];
final List<String> 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' : ''}');
}

View File

@@ -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', () {

View File

@@ -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(() {
// Сбрасываем состояние перед каждым тестом

View File

@@ -313,7 +313,7 @@ final config = await scope.resolveAsync<RemoteConfig>();
[`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!

View File

@@ -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!

View File

@@ -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

View File

@@ -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 на логгере.
## Пример приложения