Skip to content

Commit

Permalink
[FR] Logger and DI improvements (#384)
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkkiller authored Nov 3, 2024
1 parent 16c7689 commit 9274cbb
Show file tree
Hide file tree
Showing 18 changed files with 251 additions and 120 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,7 @@ pubspec.lock
.metadata

# FVM Version Cache
.fvm/
.fvm/

# Test reports
reports/
1 change: 1 addition & 0 deletions env/.dev.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SENTRY_DSN=""
14 changes: 9 additions & 5 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:async';

import 'package:sizzle_starter/src/core/utils/refined_logger.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';
import 'package:sizzle_starter/src/feature/initialization/logic/app_runner.dart';

void main() => runZonedGuarded(
() => const AppRunner().initializeAndRun(),
logger.logZoneError,
);
void main() {
final logger = DefaultLogger(const LoggingOptions(useDebugPrint: true));

runZonedGuarded(
() => AppRunner(logger).initializeAndRun(),
logger.logZoneError,
);
}
20 changes: 20 additions & 0 deletions lib/src/core/constant/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,23 @@ class Config {
/// Whether Sentry is enabled.
bool get enableSentry => sentryDsn.isNotEmpty;
}

/// {@template testing_dependencies_container}
/// A special version of [Config] that is used in tests.
///
/// In order to use [Config] in tests, it is needed to
/// extend this class and provide the dependencies that are needed for the test.
/// {@endtemplate}
base class TestConfig implements Config {
/// {@macro testing_dependencies_container}
const TestConfig();

@override
Object noSuchMethod(Invocation invocation) {
throw UnimplementedError(
'The test tries to access ${invocation.memberName} config option, but '
'it was not provided. Please provide the option in the test. '
'You can do it by extending this class and providing the option.',
);
}
}
4 changes: 2 additions & 2 deletions lib/src/core/rest_client/src/http/rest_client_http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import 'package:http/http.dart' as http;
import 'package:sizzle_starter/src/core/rest_client/rest_client.dart';
import 'package:sizzle_starter/src/core/rest_client/src/http/check_exception_io.dart'
if (dart.library.js_interop) 'package:sizzle_starter/src/core/rest_client/src/http/check_exception_browser.dart';
import 'package:sizzle_starter/src/core/utils/refined_logger.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';

// coverage:ignore-start
/// Creates an [http.Client] based on the current platform.
///
/// For Android, it returns a [CronetClient] with the default Cronet engine.
/// For iOS and macOS, it returns a [CupertinoClient]
/// with the default session configuration.
http.Client createDefaultHttpClient() {
http.Client createDefaultHttpClient(Logger logger) {
http.Client? client;
final platform = defaultTargetPlatform;

Expand Down
4 changes: 2 additions & 2 deletions lib/src/core/utils/analytics/firebase_analytics_reporter.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:sizzle_starter/src/core/utils/analytics/analytics_reporter.dart';
import 'package:sizzle_starter/src/core/utils/refined_logger.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';

/// {@template firebase_analytics_reporter}
/// An implementation of [AnalyticsReporter] that reports events to Firebase
Expand All @@ -11,7 +11,7 @@ final class FirebaseAnalyticsReporter implements AnalyticsReporter {
const FirebaseAnalyticsReporter({required this.logger, required this.analytics});

/// The logger used to log events locally for debugging.
final RefinedLogger logger;
final Logger logger;

/// The Firebase Analytics instance used to log events.
final FirebaseAnalytics analytics;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/core/utils/app_bloc_observer.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sizzle_starter/src/core/utils/extensions/string_extension.dart';
import 'package:sizzle_starter/src/core/utils/refined_logger.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';

/// [BlocObserver] which logs all bloc state changes, errors and events.
class AppBlocObserver extends BlocObserver {
/// Creates an instance of [AppBlocObserver] with the provided [logger].
const AppBlocObserver(this.logger);

/// Logger used to log information during bloc transitions.
final RefinedLogger logger;
final Logger logger;

@override
void onTransition(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:async';

import 'package:meta/meta.dart';
import 'package:sizzle_starter/src/core/utils/refined_logger.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';

/// {@template error_tracking_manager}
/// A class which is responsible for enabling error tracking.
Expand Down Expand Up @@ -30,7 +30,7 @@ abstract base class ErrorTrackingManagerBase implements ErrorTrackingManager {
/// {@macro error_tracking_manager_base}
ErrorTrackingManagerBase(this._logger);

final RefinedLogger _logger;
final Logger _logger;
StreamSubscription<LogMessage>? _subscription;

/// Catch only warnings and errors
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sizzle_starter/src/core/utils/error_tracking_manager/error_tracking_manager.dart';
import 'package:sizzle_starter/src/core/utils/refined_logger.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';

/// {@template sentry_tracking_manager}
/// A class that is responsible for managing Sentry error tracking.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,6 @@ import 'package:clock/clock.dart';
import 'package:flutter/foundation.dart';
import 'package:stack_trace/stack_trace.dart';

/// Logger global instance.
///
/// This is the default logger used by the application.
/// Prefer to pass this logger instance as a parameter to the classes
/// that need it, instead of using it directly.
///
/// This is a zone-scoped logger, which means that it can be overridden
/// in nested zones or during tests.
RefinedLogger get logger => Zone.current[kLoggerKey] as RefinedLogger? ?? _logger;

/// Runs [callback] with the given [logger] instance.
///
/// This is [Zone]-scoped, so asynchronous callbacks spawned within [callback]
/// will also use the new value for [logger].
///
/// This is useful for testing purposes, where you can pass a mock logger
/// to the callback.
void runWithLogger<T>(RefinedLogger logger, T Function() callback) {
runZoned(callback, zoneValues: {kLoggerKey: logger});
}

/// The key used to store the logger in the zone.
const kLoggerKey = 'logger';

/// A logger used to log errors in the root zone.
final _logger = DefaultLogger();

/// Defines the format of a log message.
///
/// This is a function type that takes a [LogMessage] and
Expand Down Expand Up @@ -65,7 +38,7 @@ class LoggingOptions {
this.showEmoji = true,
this.logInRelease = false,
this.useDebugPrint = false,
this.onMessageFormat,
this.onMessageFormatter,
});

/// Whether to include the timestamp in the log message.
Expand All @@ -92,7 +65,7 @@ class LoggingOptions {
/// An optional custom formatter for log messages.
///
/// If not provided, a default formatter is used.
final LogFormatter? onMessageFormat;
final LogFormatter? onMessageFormatter;
}

/// Internal class used by [DefaultLogger] to wrap the log messages.
Expand All @@ -114,8 +87,8 @@ class LogWrapper {
bool printError;
}

/// Logger class, that manages the logging of messages
class DefaultLogger extends RefinedLogger {
/// The default logger implementation, used by the application.
final class DefaultLogger extends Logger {
/// Constructs an instance of [DefaultLogger].
DefaultLogger([LoggingOptions options = const LoggingOptions()]) {
_init(options);
Expand All @@ -135,6 +108,33 @@ class DefaultLogger extends RefinedLogger {
_logSubscription = _logWrapStream.listen((l) => _printLogMessage(l, options));
}

/// Default log message formatter
static String defaultFormatter(LogWrapper wrappedMessage, LoggingOptions options) {
final message = wrappedMessage.message;
final time = options.showTime ? message.timestamp.toIso8601String() : '';
final emoji = options.showEmoji ? message.level.emoji : '';
final level = message.level;
final content = message.message;
final stackTrace = message.stackTrace;
final error = message.error;

final buffer = StringBuffer();

buffer.write('$emoji $time [${level.name.toUpperCase()}] $content');

if (error != null && wrappedMessage.printError) {
buffer.writeln();
buffer.write(error);
}

if (stackTrace != null && wrappedMessage.printStackTrace) {
buffer.writeln();
buffer.write(Trace.from(stackTrace).terse);
}

return buffer.toString();
}

@override
Stream<LogMessage> get logs => _logs;

Expand Down Expand Up @@ -182,39 +182,13 @@ class DefaultLogger extends RefinedLogger {
return;
}

final formattedMessage = options.onMessageFormat != null
? options.onMessageFormat!(wrappedMessage, options)
: _defaultFormatter(wrappedMessage, options);
final formattedMessage = options.onMessageFormatter != null
? options.onMessageFormatter!(wrappedMessage, options)
: defaultFormatter(wrappedMessage, options);

_log(formattedMessage);
}

String _defaultFormatter(LogWrapper wrappedMessage, LoggingOptions options) {
final message = wrappedMessage.message;
final time = options.showTime ? message.timestamp.toIso8601String() : '';
final emoji = options.showEmoji ? message.level.emoji : '';
final level = message.level;
final content = message.message;
final stackTrace = message.stackTrace;
final error = message.error;

final buffer = StringBuffer();

buffer.write('$emoji $time [${level.name.toUpperCase()}] $content');

if (error != null && wrappedMessage.printError) {
buffer.writeln();
buffer.write(error);
}

if (stackTrace != null && wrappedMessage.printStackTrace) {
buffer.writeln();
buffer.write(Trace.from(stackTrace).terse);
}

return buffer.toString();
}

/// Logs a chunk of message
void _log(String message, {bool useDebugPrint = false}) {
if (useDebugPrint) {
Expand All @@ -227,8 +201,8 @@ class DefaultLogger extends RefinedLogger {

/// {@macro logger}
///
/// A logger that does nothing.
class NoOpLogger extends RefinedLogger {
/// A logger that does nothing, used for testing purposes.
final class NoOpLogger extends Logger {
/// Constructs an instance of [NoOpLogger].
const NoOpLogger();

Expand All @@ -255,14 +229,16 @@ class NoOpLogger extends RefinedLogger {
/// {@template logger}
/// Logger class, that manages the logging of messages
/// {@endtemplate}
abstract class RefinedLogger {
/// Constructs an instance of [RefinedLogger].
const RefinedLogger();
abstract base class Logger {
/// Constructs an instance of [Logger].
const Logger();

/// Stream of log messages
Stream<LogMessage> get logs;

/// Destroys the logger and releases any resources
/// Destroys the logger and releases all resources
///
/// After calling this method, the logger should not be used anymore.
void destroy();

/// Logs a message with the specified [level].
Expand Down Expand Up @@ -297,14 +273,18 @@ abstract class RefinedLogger {
/// Logs a platform dispatcher error with [LogLevel.error].
bool logPlatformDispatcherError(Object error, StackTrace stackTrace) {
log(
'Platform dispatcher error',
'Platform Error',
level: LogLevel.error,
error: error,
stackTrace: stackTrace,
);

return true;
}

/// Creates a logger that uses this instance with a new prefix.
Logger withPrefix(String prefix) => PrefixedLogger(this, prefix);

/// Logs a message with [LogLevel.trace].
void trace(
String message, {
Expand Down Expand Up @@ -420,6 +400,45 @@ abstract class RefinedLogger {
);
}

/// A logger that prefixes all log messages with a given string.
base class PrefixedLogger extends Logger {
/// Constructs an instance of [PrefixedLogger].
const PrefixedLogger(this._logger, this._prefix);

final Logger _logger;
final String _prefix;

@override
Stream<LogMessage> get logs => _logger.logs;

@override
void destroy() => _logger.destroy();

@override
Logger withPrefix(String prefix) => PrefixedLogger(_logger, '$_prefix $prefix');

@override
void log(
String message, {
required LogLevel level,
Object? error,
StackTrace? stackTrace,
Map<String, Object?>? context,
bool printStackTrace = true,
bool printError = true,
}) {
_logger.log(
'$_prefix $message',
level: level,
error: error,
stackTrace: stackTrace,
context: context,
printStackTrace: printStackTrace,
printError: printError,
);
}
}

/// Represents a single log message with various details
/// for debugging and information purposes.
class LogMessage {
Expand Down
Loading

0 comments on commit 9274cbb

Please sign in to comment.