Skip to content

Commit

Permalink
feat(logging): logger can register one plugin per type in a logger hi…
Browse files Browse the repository at this point in the history
…erarchy (#3173)

Co-authored-by: Nika Hassani <[email protected]>
  • Loading branch information
NikaHsn and Nika Hassani authored Jul 19, 2023
1 parent 50b1dbe commit 629a43a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 5 deletions.
3 changes: 3 additions & 0 deletions packages/amplify_core/lib/src/logger/amplify_logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class AmplifyLogger extends AWSLogger {
assert(name.isNotEmpty, 'Name should not be empty');
return AmplifyLogger('$namespace.$name');
}

@override
String get runtimeTypeName => 'AmplifyLogger';
}

/// @nodoc
Expand Down
51 changes: 48 additions & 3 deletions packages/aws_common/lib/src/logging/aws_logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:async';

import 'package:aws_common/aws_common.dart';
import 'package:aws_common/src/logging/logging_ext.dart';
import 'package:collection/collection.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';

Expand All @@ -22,7 +23,7 @@ const zDefaultLogLevel = LogLevel.info;
///
/// By default, a [SimpleLogPrinter] is registered on the root [AWSLogger]
/// which impacts all child loggers.
class AWSLogger implements Closeable {
class AWSLogger with AWSDebuggable implements Closeable {
/// Creates a top-level [AWSLogger].
///
/// {@macro aws_common.logging.aws_logger}
Expand All @@ -40,6 +41,7 @@ class AWSLogger implements Closeable {
@protected
AWSLogger.protected(String namespace) : _logger = Logger(namespace) {
_init(this);
_parent?._children.add(this);
}

static bool _initialized = false;
Expand All @@ -63,6 +65,24 @@ class AWSLogger implements Closeable {

final Logger _logger;

/// Parent of this logger in the logger hierarchy.
AWSLogger? get _parent {
return activeLoggers[_logger.parent?.fullName];
}

/// Children of this logger in the logger hierarchy.
final List<AWSLogger> _children = [];

Never _pluginAlreadyRegistered(String pluginType) {
final loggerInstance = '$runtimeTypeName($namespace)';
throw StateError(
'A plugin of type "$pluginType" is already registered to'
' "$loggerInstance" in the same logging hierarchy. Unregister the'
' existing plugin from "$loggerInstance" first and then register the'
' new plugin.',
);
}

/// The namespace of this logger.
String get namespace => _logger.fullName;

Expand All @@ -72,10 +92,31 @@ class AWSLogger implements Closeable {
return AWSLogger('$namespace.$name');
}

/// Returns a plugin of type [Plugin] registered to this
/// logger hierarchy or `null`.
Plugin? getPlugin<Plugin extends AWSLoggerPlugin>() {
final registeredPlugin = _subscriptions.keys
.firstWhereOrNull((element) => element.runtimeType == Plugin)
as Plugin?;
return registeredPlugin ?? _parent?.getPlugin<Plugin>();
}

/// Registers an [AWSLoggerPlugin] to handle logs emitted by this logger
/// instance.
void registerPlugin(AWSLoggerPlugin plugin) {
unregisterPlugin(plugin);
///
/// Throws [StateError] if a plugin with same type is registered to this
/// logger hierarchy.
void registerPlugin<T extends AWSLoggerPlugin>(
T plugin,
) {
bool hasPlugin(AWSLogger logger) =>
logger._subscriptions.keys.any((element) => element.runtimeType == T) ||
logger._children.any(hasPlugin);

if (getPlugin<T>() != null || _children.any(hasPlugin)) {
_pluginAlreadyRegistered(T.toString());
}

_subscriptions[plugin] = _logger.onRecord
.map((record) => record.toLogEntry())
.listen(plugin.handleLogEntry);
Expand Down Expand Up @@ -141,6 +182,10 @@ class AWSLogger implements Closeable {

@override
void close() => unregisterAllPlugins();

@override
@mustBeOverridden
String get runtimeTypeName => 'AWSLogger';
}

/// {@template aws_common.logging.aws_logger_plugin}
Expand Down
75 changes: 74 additions & 1 deletion packages/aws_common/test/logging/aws_logger_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class MockLoggerPlugin extends AWSLoggerPlugin {
}
}

class MockLoggerPlugin2 extends MockLoggerPlugin {}

void main() {
final logRecords = {
LogLevel.verbose: LogEntry(
Expand Down Expand Up @@ -97,7 +99,7 @@ void main() {

test('Multiple LoggerPlugins register and unregister properly', () async {
final loggerPlugin1 = MockLoggerPlugin();
final loggerPlugin2 = MockLoggerPlugin();
final loggerPlugin2 = MockLoggerPlugin2();
final logger = AWSLogger()
..registerPlugin(loggerPlugin1)
..registerPlugin(loggerPlugin2)
Expand Down Expand Up @@ -220,5 +222,76 @@ void main() {
expect(loggerPlugin.timesCalled, 5);
expect(globalLoggerPlugin.timesCalled, 0);
});

test('getPlugin returns registered plugin in the logger hierarchy', () {
final plugin = MockLoggerPlugin();
final logger = AWSLogger()..registerPlugin(plugin);
expect(logger.getPlugin<MockLoggerPlugin>(), plugin);

final childLogger = logger.createChild('child');
expect(childLogger.getPlugin<MockLoggerPlugin>(), plugin);

logger.unregisterPlugin(plugin);
expect(logger.getPlugin<MockLoggerPlugin>(), isNull);
expect(childLogger.getPlugin<MockLoggerPlugin>(), isNull);

childLogger.registerPlugin(plugin);
expect(logger.getPlugin<MockLoggerPlugin>(), isNull);
expect(childLogger.getPlugin<MockLoggerPlugin>(), plugin);
});

test(
'registerPlugin handles registering one plugin per type'
' in the logger hierarchy', () {
final plugin = MockLoggerPlugin();
final childLogger = AWSLogger()
.createChild('child')
.createChild('grandChild')
.createChild('grandGrandChild');
expect(() => childLogger.registerPlugin(plugin), returnsNormally);
expect(() => childLogger.registerPlugin(plugin), throwsStateError);
expect(() => AWSLogger().registerPlugin(plugin), throwsStateError);

final newPlugin = MockLoggerPlugin();
expect(() => AWSLogger().registerPlugin(newPlugin), throwsStateError);
expect(() => childLogger.registerPlugin(newPlugin), throwsStateError);

expect(() => childLogger.unregisterPlugin(plugin), returnsNormally);
expect(childLogger.getPlugin<MockLoggerPlugin>(), isNull);

expect(() => AWSLogger().registerPlugin(newPlugin), returnsNormally);
expect(() => childLogger.registerPlugin(newPlugin), throwsStateError);
expect(AWSLogger().getPlugin<MockLoggerPlugin>(), newPlugin);
expect(childLogger.getPlugin<MockLoggerPlugin>(), newPlugin);
});

group('registerPlugin allows registering plugins of same superclass', () {
test('can register to the root logger', () {
final logger = AWSLogger();
final plugin1 = MockLoggerPlugin();
final plugin2 = MockLoggerPlugin2();

expect(() => logger.registerPlugin(plugin2), returnsNormally);
expect(() => logger.registerPlugin(plugin1), returnsNormally);

expect(logger.getPlugin<MockLoggerPlugin>(), plugin1);
expect(logger.getPlugin<MockLoggerPlugin2>(), plugin2);
});

test('can register to the root logger and its child', () {
final logger = AWSLogger();
final plugin1 = MockLoggerPlugin();
final plugin2 = MockLoggerPlugin2();
final childLogger = logger.createChild('child');

expect(() => childLogger.registerPlugin(plugin2), returnsNormally);
expect(() => logger.registerPlugin(plugin1), returnsNormally);

expect(logger.getPlugin<MockLoggerPlugin>(), plugin1);
expect(logger.getPlugin<MockLoggerPlugin2>(), isNull);
expect(childLogger.getPlugin<MockLoggerPlugin>(), plugin1);
expect(childLogger.getPlugin<MockLoggerPlugin2>(), plugin2);
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class _FakeAWSLogger_2 extends _i1.SmartFake implements _i4.AWSLogger {
parent,
parentInvocation,
);

@override
String get runtimeTypeName => '_FakeAWSLogger_2';
}

/// A class which mocks [PushNotificationsHostApi].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class _FakeAWSLogger_1 extends _i1.SmartFake implements _i3.AWSLogger {
parent,
parentInvocation,
);

@override
String get runtimeTypeName => '_FakeAWSLogger_1';
}

class _FakeAuthCategory_2 extends _i1.SmartFake implements _i4.AuthCategory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ class MockStorageS3Service extends Mock implements StorageS3Service {}

class MockS3Client extends Mock implements S3Client {}

class MockAWSLogger extends Mock implements AWSLogger {}
class MockAWSLogger extends Mock implements AWSLogger {
@override
String get runtimeTypeName => 'MockAWSLogger';
}

class MockAWSSigV4Signer extends Mock implements AWSSigV4Signer {}

Expand Down

0 comments on commit 629a43a

Please sign in to comment.