mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 05:25:19 +00:00
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:
@@ -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
|
## Features
|
||||||
|
|
||||||
- [x] Main Scope and Named Subscopes
|
- [x] Main Scope and Named Subscopes
|
||||||
|
|||||||
37
cherrypick/example/cherrypick_logger_demo.dart
Normal file
37
cherrypick/example/cherrypick_logger_demo.dart
Normal 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
|
||||||
|
}
|
||||||
@@ -20,3 +20,4 @@ export 'package:cherrypick/src/global_cycle_detector.dart';
|
|||||||
export 'package:cherrypick/src/helper.dart';
|
export 'package:cherrypick/src/helper.dart';
|
||||||
export 'package:cherrypick/src/module.dart';
|
export 'package:cherrypick/src/module.dart';
|
||||||
export 'package:cherrypick/src/scope.dart';
|
export 'package:cherrypick/src/scope.dart';
|
||||||
|
export 'package:cherrypick/src/logger.dart';
|
||||||
|
|||||||
@@ -16,14 +16,51 @@ import 'package:cherrypick/src/binding_resolver.dart';
|
|||||||
/// RU: Класс Binding<T> настраивает параметры экземпляра.
|
/// RU: Класс Binding<T> настраивает параметры экземпляра.
|
||||||
/// ENG: The Binding<T> class configures the settings for the instance.
|
/// ENG: The Binding<T> class configures the settings for the instance.
|
||||||
///
|
///
|
||||||
|
import 'package:cherrypick/src/logger.dart';
|
||||||
|
|
||||||
class Binding<T> {
|
class Binding<T> {
|
||||||
late Type _key;
|
late Type _key;
|
||||||
String? _name;
|
String? _name;
|
||||||
|
|
||||||
BindingResolver<T>? _resolver;
|
BindingResolver<T>? _resolver;
|
||||||
|
|
||||||
Binding() {
|
CherryPickLogger? logger;
|
||||||
|
|
||||||
|
// Deferred logging flags
|
||||||
|
bool _createdLogged = false;
|
||||||
|
bool _namedLogged = false;
|
||||||
|
bool _singletonLogged = false;
|
||||||
|
|
||||||
|
Binding({this.logger}) {
|
||||||
_key = T;
|
_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: Метод возвращает тип экземпляра.
|
/// RU: Метод возвращает тип экземпляра.
|
||||||
@@ -58,6 +95,7 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> withName(String name) {
|
Binding<T> withName(String name) {
|
||||||
_name = name;
|
_name = name;
|
||||||
|
// Не логируем здесь, deferred log via markNamed()
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +105,6 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> toInstance(Instance<T> value) {
|
Binding<T> toInstance(Instance<T> value) {
|
||||||
_resolver = InstanceResolver<T>(value);
|
_resolver = InstanceResolver<T>(value);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +114,6 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> toProvide(Provider<T> value) {
|
Binding<T> toProvide(Provider<T> value) {
|
||||||
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
|
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +123,6 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> toProvideWithParams(ProviderWithParams<T> value) {
|
Binding<T> toProvideWithParams(ProviderWithParams<T> value) {
|
||||||
_resolver = ProviderResolver<T>(value, withParams: true);
|
_resolver = ProviderResolver<T>(value, withParams: true);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,15 +147,28 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> singleton() {
|
Binding<T> singleton() {
|
||||||
_resolver?.toSingleton();
|
_resolver?.toSingleton();
|
||||||
|
// Не логируем здесь, deferred log via markSingleton()
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
T? resolveSync([dynamic params]) {
|
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]) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'package:cherrypick/src/logger.dart';
|
||||||
|
|
||||||
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости.
|
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости.
|
||||||
/// ENG: Exception thrown when a circular dependency is detected.
|
/// ENG: Exception thrown when a circular dependency is detected.
|
||||||
@@ -31,24 +32,30 @@ class CircularDependencyException implements Exception {
|
|||||||
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера.
|
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера.
|
||||||
/// ENG: Circular dependency detector for CherryPick DI container.
|
/// ENG: Circular dependency detector for CherryPick DI container.
|
||||||
class CycleDetector {
|
class CycleDetector {
|
||||||
|
final CherryPickLogger logger;
|
||||||
// Стек текущих разрешаемых зависимостей
|
// Стек текущих разрешаемых зависимостей
|
||||||
final Set<String> _resolutionStack = HashSet<String>();
|
final Set<String> _resolutionStack = HashSet<String>();
|
||||||
|
|
||||||
// История разрешения для построения цепочки зависимостей
|
// История разрешения для построения цепочки зависимостей
|
||||||
final List<String> _resolutionHistory = [];
|
final List<String> _resolutionHistory = [];
|
||||||
|
|
||||||
|
CycleDetector({CherryPickLogger? logger}) : logger = logger ?? const SilentLogger() {
|
||||||
|
// print removed (trace)
|
||||||
|
}
|
||||||
|
|
||||||
/// RU: Начинает отслеживание разрешения зависимости.
|
/// RU: Начинает отслеживание разрешения зависимости.
|
||||||
/// ENG: Starts tracking dependency resolution.
|
/// ENG: Starts tracking dependency resolution.
|
||||||
///
|
///
|
||||||
/// Throws [CircularDependencyException] if circular dependency is detected.
|
/// Throws [CircularDependencyException] if circular dependency is detected.
|
||||||
void startResolving<T>({String? named}) {
|
void startResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
|
logger.info('CycleDetector: startResolving $dependencyKey stackSize=${_resolutionStack.length}');
|
||||||
if (_resolutionStack.contains(dependencyKey)) {
|
if (_resolutionStack.contains(dependencyKey)) {
|
||||||
// Найдена циклическая зависимость
|
// Найдена циклическая зависимость
|
||||||
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
|
// print removed (trace)
|
||||||
|
logger.error('CycleDetector: CYCLE DETECTED! $dependencyKey chain: ${cycle.join(' -> ')}');
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Circular dependency detected for $dependencyKey',
|
'Circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -63,8 +70,8 @@ class CycleDetector {
|
|||||||
/// ENG: Finishes tracking dependency resolution.
|
/// ENG: Finishes tracking dependency resolution.
|
||||||
void finishResolving<T>({String? named}) {
|
void finishResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
|
logger.info('CycleDetector: finishResolving $dependencyKey');
|
||||||
_resolutionStack.remove(dependencyKey);
|
_resolutionStack.remove(dependencyKey);
|
||||||
|
|
||||||
// Удаляем из истории только если это последний элемент
|
// Удаляем из истории только если это последний элемент
|
||||||
if (_resolutionHistory.isNotEmpty &&
|
if (_resolutionHistory.isNotEmpty &&
|
||||||
_resolutionHistory.last == dependencyKey) {
|
_resolutionHistory.last == dependencyKey) {
|
||||||
@@ -75,6 +82,7 @@ class CycleDetector {
|
|||||||
/// RU: Очищает все состояние детектора.
|
/// RU: Очищает все состояние детектора.
|
||||||
/// ENG: Clears all detector state.
|
/// ENG: Clears all detector state.
|
||||||
void clear() {
|
void clear() {
|
||||||
|
logger.info('CycleDetector: clear');
|
||||||
_resolutionStack.clear();
|
_resolutionStack.clear();
|
||||||
_resolutionHistory.clear();
|
_resolutionHistory.clear();
|
||||||
}
|
}
|
||||||
@@ -103,16 +111,21 @@ class CycleDetector {
|
|||||||
mixin CycleDetectionMixin {
|
mixin CycleDetectionMixin {
|
||||||
CycleDetector? _cycleDetector;
|
CycleDetector? _cycleDetector;
|
||||||
|
|
||||||
|
CherryPickLogger? get logger;
|
||||||
|
|
||||||
/// RU: Включает обнаружение циклических зависимостей.
|
/// RU: Включает обнаружение циклических зависимостей.
|
||||||
/// ENG: Enables circular dependency detection.
|
/// ENG: Enables circular dependency detection.
|
||||||
void enableCycleDetection() {
|
void enableCycleDetection() {
|
||||||
_cycleDetector = CycleDetector();
|
// print removed (trace)
|
||||||
|
_cycleDetector = CycleDetector(logger: logger);
|
||||||
|
logger?.info('CycleDetection: cycle detection enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Отключает обнаружение циклических зависимостей.
|
/// RU: Отключает обнаружение циклических зависимостей.
|
||||||
/// ENG: Disables circular dependency detection.
|
/// ENG: Disables circular dependency detection.
|
||||||
void disableCycleDetection() {
|
void disableCycleDetection() {
|
||||||
_cycleDetector?.clear();
|
_cycleDetector?.clear();
|
||||||
|
logger?.info('CycleDetection: cycle detection disabled');
|
||||||
_cycleDetector = null;
|
_cycleDetector = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,30 +12,72 @@
|
|||||||
//
|
//
|
||||||
import 'package:cherrypick/src/scope.dart';
|
import 'package:cherrypick/src/scope.dart';
|
||||||
import 'package:cherrypick/src/global_cycle_detector.dart';
|
import 'package:cherrypick/src/global_cycle_detector.dart';
|
||||||
|
import 'package:cherrypick/src/logger.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
CherryPickLogger? _globalLogger = const SilentLogger();
|
||||||
|
|
||||||
Scope? _rootScope;
|
Scope? _rootScope;
|
||||||
bool _globalCycleDetectionEnabled = false;
|
bool _globalCycleDetectionEnabled = false;
|
||||||
bool _globalCrossScopeCycleDetectionEnabled = false;
|
bool _globalCrossScopeCycleDetectionEnabled = false;
|
||||||
|
|
||||||
class CherryPick {
|
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].
|
/// RU: Метод открывает главный [Scope].
|
||||||
/// ENG: The method opens the main [Scope].
|
/// ENG: The method opens the main [Scope].
|
||||||
///
|
///
|
||||||
/// return
|
/// return
|
||||||
static Scope openRootScope() {
|
static Scope openRootScope() {
|
||||||
_rootScope ??= Scope(null);
|
_rootScope ??= Scope(null, logger: _globalLogger);
|
||||||
|
|
||||||
// Применяем глобальную настройку обнаружения циклических зависимостей
|
// Применяем глобальную настройку обнаружения циклических зависимостей
|
||||||
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
||||||
_rootScope!.enableCycleDetection();
|
_rootScope!.enableCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применяем глобальную настройку обнаружения между скоупами
|
// Применяем глобальную настройку обнаружения между скоупами
|
||||||
if (_globalCrossScopeCycleDetectionEnabled && !_rootScope!.isGlobalCycleDetectionEnabled) {
|
if (_globalCrossScopeCycleDetectionEnabled && !_rootScope!.isGlobalCycleDetectionEnabled) {
|
||||||
_rootScope!.enableGlobalCycleDetection();
|
_rootScope!.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _rootScope!;
|
return _rootScope!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
108
cherrypick/lib/src/logger.dart
Normal file
108
cherrypick/lib/src/logger.dart
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,12 +17,22 @@ import 'package:cherrypick/src/cycle_detector.dart';
|
|||||||
import 'package:cherrypick/src/global_cycle_detector.dart';
|
import 'package:cherrypick/src/global_cycle_detector.dart';
|
||||||
import 'package:cherrypick/src/binding_resolver.dart';
|
import 'package:cherrypick/src/binding_resolver.dart';
|
||||||
import 'package:cherrypick/src/module.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 {
|
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
||||||
final Scope? _parentScope;
|
final Scope? _parentScope;
|
||||||
|
|
||||||
|
CherryPickLogger? _logger;
|
||||||
|
|
||||||
|
@override
|
||||||
|
CherryPickLogger? get logger => _logger;
|
||||||
|
set logger(CherryPickLogger? value) => _logger = value;
|
||||||
|
|
||||||
/// RU: Метод возвращает родительский [Scope].
|
/// RU: Метод возвращает родительский [Scope].
|
||||||
///
|
///
|
||||||
/// ENG: The method returns the parent [Scope].
|
/// ENG: The method returns the parent [Scope].
|
||||||
@@ -32,9 +42,11 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
final Map<String, Scope> _scopeMap = HashMap();
|
final Map<String, Scope> _scopeMap = HashMap();
|
||||||
|
|
||||||
Scope(this._parentScope) {
|
Scope(this._parentScope, {CherryPickLogger? logger}) : _logger = logger ?? _globalLogger {
|
||||||
|
// print removed (trace)
|
||||||
// Генерируем уникальный ID для скоупа
|
// Генерируем уникальный ID для скоупа
|
||||||
setScopeId(_generateScopeId());
|
setScopeId(_generateScopeId());
|
||||||
|
_logger?.info('Scope created: id=${scopeId ?? "NO_ID"}, parent=${_parentScope?.scopeId}');
|
||||||
}
|
}
|
||||||
|
|
||||||
final Set<Module> _modulesList = HashSet();
|
final Set<Module> _modulesList = HashSet();
|
||||||
@@ -59,8 +71,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope openSubScope(String name) {
|
Scope openSubScope(String name) {
|
||||||
if (!_scopeMap.containsKey(name)) {
|
if (!_scopeMap.containsKey(name)) {
|
||||||
final childScope = Scope(this);
|
final childScope = Scope(this, logger: logger); // Наследуем логгер вниз по иерархии
|
||||||
|
// print removed (trace)
|
||||||
// Наследуем настройки обнаружения циклических зависимостей
|
// Наследуем настройки обнаружения циклических зависимостей
|
||||||
if (isCycleDetectionEnabled) {
|
if (isCycleDetectionEnabled) {
|
||||||
childScope.enableCycleDetection();
|
childScope.enableCycleDetection();
|
||||||
@@ -68,8 +80,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
childScope.enableGlobalCycleDetection();
|
childScope.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
_scopeMap[name] = childScope;
|
_scopeMap[name] = childScope;
|
||||||
|
logger?.info('SubScope created: $name, id=${childScope.scopeId} (parent=$scopeId)');
|
||||||
}
|
}
|
||||||
return _scopeMap[name]!;
|
return _scopeMap[name]!;
|
||||||
}
|
}
|
||||||
@@ -86,6 +98,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
if (childScope.scopeId != null) {
|
if (childScope.scopeId != null) {
|
||||||
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
||||||
}
|
}
|
||||||
|
logger?.info('SubScope closed: $name, id=${childScope.scopeId} (parent=$scopeId)');
|
||||||
}
|
}
|
||||||
_scopeMap.remove(name);
|
_scopeMap.remove(name);
|
||||||
}
|
}
|
||||||
@@ -98,7 +111,13 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
Scope installModules(List<Module> modules) {
|
Scope installModules(List<Module> modules) {
|
||||||
_modulesList.addAll(modules);
|
_modulesList.addAll(modules);
|
||||||
for (var module in modules) {
|
for (var module in modules) {
|
||||||
|
logger?.info('Installing module: ${module.runtimeType} in scope $scopeId');
|
||||||
module.builder(this);
|
module.builder(this);
|
||||||
|
// После builder: для всех новых биндингов
|
||||||
|
for (final binding in module.bindingSet) {
|
||||||
|
binding.logger = logger;
|
||||||
|
binding.logAllDeferred();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_rebuildResolversIndex();
|
_rebuildResolversIndex();
|
||||||
return this;
|
return this;
|
||||||
@@ -110,7 +129,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
///
|
///
|
||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope dropModules() {
|
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();
|
_modulesList.clear();
|
||||||
_rebuildResolversIndex();
|
_rebuildResolversIndex();
|
||||||
return this;
|
return this;
|
||||||
@@ -130,11 +149,21 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
T resolve<T>({String? named, dynamic params}) {
|
T resolve<T>({String? named, dynamic params}) {
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<T>(T, named, () {
|
try {
|
||||||
return _resolveWithLocalDetection<T>(named: named, params: params);
|
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 {
|
} else {
|
||||||
return _resolveWithLocalDetection<T>(named: named, params: params);
|
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, () {
|
return withCycleDetection<T>(T, named, () {
|
||||||
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
|
logger?.info('Resolve<$T> [named=$named]: successfully resolved in scope $scopeId.');
|
||||||
return resolved;
|
return resolved;
|
||||||
} else {
|
} else {
|
||||||
|
logger?.error('Failed to resolve<$T> [named=$named] in scope $scopeId.');
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
|
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
|
||||||
}
|
}
|
||||||
|
|||||||
60
cherrypick/test/logger_integration_test.dart
Normal file
60
cherrypick/test/logger_integration_test.dart
Normal 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
16
cherrypick/test/mock_logger.dart
Normal file
16
cherrypick/test/mock_logger.dart
Normal 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' : ''}');
|
||||||
|
}
|
||||||
@@ -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:test/test.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('CycleDetector', () {
|
group('CycleDetector', () {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
import '../mock_logger.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
late MockLogger logger;
|
||||||
|
setUp(() {
|
||||||
|
logger = MockLogger();
|
||||||
|
CherryPick.setGlobalLogger(logger);
|
||||||
|
});
|
||||||
group('CherryPick Cycle Detection Helper Methods', () {
|
group('CherryPick Cycle Detection Helper Methods', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
// Сбрасываем состояние перед каждым тестом
|
// Сбрасываем состояние перед каждым тестом
|
||||||
|
|||||||
@@ -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.
|
[`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:**
|
- **Global DI Scope Access:**
|
||||||
Use `CherryPickProvider` to access rootScope and subscopes anywhere in the widget tree.
|
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:
|
- You can create subscopes, e.g. for screens or modules:
|
||||||
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
`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!
|
## CherryPick is not just for Flutter!
|
||||||
|
|
||||||
|
|||||||
@@ -358,6 +358,26 @@ class MyApp extends StatelessWidget {
|
|||||||
- Вы можете создавать подскоупы, если нужно, например, для экранов или модулей:
|
- Вы можете создавать подскоупы, если нужно, например, для экранов или модулей:
|
||||||
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
`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!
|
## CherryPick подходит не только для Flutter!
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,24 @@ Example:
|
|||||||
Cherrypick.closeRootScope();
|
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
|
## Example app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,24 @@ Scope - это контейнер, который хранит все дерев
|
|||||||
Cherrypick.closeRootScope();
|
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 на логгере.
|
||||||
|
|
||||||
## Пример приложения
|
## Пример приложения
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user