Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(logging): add amplify cloudwatch logger plugin base implementation #3736

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:amplify_logging_cloudwatch/src/amplify_log_stream_name_provider.dart';
import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart';

/// {@macro aws_logging_cloudwatch.cloudwatch_logger_plugin}
class AmplifyCloudWatchLoggerPlugin extends CloudWatchLoggerPlugin {
/// {@macro aws_logging_cloudwatch.cloudwatch_logger_plugin}
AmplifyCloudWatchLoggerPlugin({
required super.credentialsProvider,
required super.pluginConfig,
}) : super(
logStreamProvider: DefaultCloudWatchLogStreamProvider(
credentialsProvider: credentialsProvider,
region: pluginConfig.region,
logGroupName: pluginConfig.logGroupName,
defaultLogStreamNameProvider:
AmplifyLogStreamNameProvider().defaultLogStreamName,
),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:amplify_core/amplify_core.dart';
import 'package:amplify_logging_cloudwatch/src/device_info/device_info.dart';
import 'package:intl/intl.dart';

const _guestUserId = 'guest';

/// {@template amplify_logging_cloudwatch.amplify_log_stream_name_provider}
/// It uses {mm-dd-yyyy}.{deviceId}.{userId|guest} format for log stream name.
///
/// It gets deviceId from platform inforamtion or uuid if it is `null`.
/// It gets userId from `Amplify.Auth.getCurrentUser()).userId` or uses `guest`
/// if userId is not available.
/// {@endtemplate}
class AmplifyLogStreamNameProvider {
/// {@macro amplify_logging_cloudwatch.amplify_log_stream_name_provider}
AmplifyLogStreamNameProvider();
static final DateFormat _dateFormat = DateFormat('yyyy-MM-dd');
String? _deviceID;

/// Returns log stream name in `{mm-dd-yyyy}.{deviceId}.{userId|guest}` format
Future<String> defaultLogStreamName() async {
_deviceID ??= await getDeviceId() ?? UUID.getUUID();
String userId;
userId = await _getUserId();
return '${_dateFormat.format(DateTime.timestamp())}.$_deviceID.$userId';
}

Future<String> _getUserId() async {
String userId;
try {
userId = (await Amplify.Auth.getCurrentUser()).userId;
} on Error {
userId = _guestUserId;
} on Exception {
userId = _guestUserId;
}
return userId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'device_info.stub.dart'
if (dart.library.io) 'device_info.vm.dart'
if (dart.library.html) 'device_info.web.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// {@template amplify_logging_cloudwatch.device_info}
/// Returns device Id from platform information on `vm`.
/// Returns a UUID across browser sessions for web.
/// {@endtemplate}
Future<String?> getDeviceId() {
throw UnimplementedError('getDeviceId() has not been implemented.');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'dart:io';

import 'package:device_info_plus/device_info_plus.dart';

/// {@macro amplify_logging_cloudwatch.device_info}
Future<String?> getDeviceId() async {
final deviceInfo = DeviceInfoPlugin();
String? deviceID;
try {
if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo;
deviceID = androidInfo.id;
} else if (Platform.isIOS) {
final iosInfo = await deviceInfo.iosInfo;
deviceID = iosInfo.identifierForVendor ?? '';
} else if (Platform.isLinux) {
final linuxInfo = await deviceInfo.linuxInfo;
deviceID = linuxInfo.machineId ?? '';
} else if (Platform.isMacOS) {
final macInfo = await deviceInfo.macOsInfo;
deviceID = macInfo.systemGUID ?? '';
} else if (Platform.isWindows) {
final windowsInfo = await deviceInfo.windowsInfo;
deviceID = windowsInfo.deviceId;
}
} on Exception {
return null;
}
return deviceID;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'dart:html';

import 'package:amplify_core/amplify_core.dart';

const _localStorageKey = 'amplify-cloudwatch-logger-device-id';

/// {@macro amplify_logging_cloudwatch.device_info}
Future<String?> getDeviceId() async {
var deviceID = window.localStorage[_localStorageKey];
if (deviceID != null) {
return deviceID;
}
deviceID = UUID.getUUID();
window.localStorage.putIfAbsent(_localStorageKey, () => deviceID!);
return deviceID;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ dependencies:
aws_common: ">=0.6.0 <0.7.0"
aws_logging_cloudwatch: ^0.1.0
collection: ^1.15.0
device_info_plus: ^9.0.0
drift: ">=2.11.0 <2.12.0"
flutter:
sdk: flutter
intl: ">=0.18.0 <1.0.0"
meta: ^1.7.0
path_provider: ^2.0.0

Expand All @@ -27,4 +29,6 @@ dev_dependencies:
build_test: ^2.0.0
build_web_compilers: ^4.0.0
drift_dev: ^2.2.0+1
flutter_test:
sdk: flutter
test: ^1.22.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@TestOn('vm')

import 'package:amplify_logging_cloudwatch/src/amplify_log_stream_name_provider.dart';
import 'package:flutter_test/flutter_test.dart' as flutter;
import 'package:test/test.dart';

void main() {
flutter.TestWidgetsFlutterBinding.ensureInitialized();
test('it uses uuid and guest when their values are not provided', () async {
final logStreamNameProvider = AmplifyLogStreamNameProvider();
await expectLater(logStreamNameProvider.defaultLogStreamName(), completes);
});

test('it caches the device Id', () async {
final logStreamNameProvider = AmplifyLogStreamNameProvider();
final logStreamName1 = await logStreamNameProvider.defaultLogStreamName();
final logStreamName2 = await logStreamNameProvider.defaultLogStreamName();

expect(logStreamName1, logStreamName2);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,8 @@ class CloudWatchLoggerPlugin extends AWSLoggerPlugin
_logStreamProvider = logStreamProvider ??
DefaultCloudWatchLogStreamProvider(
logGroupName: pluginConfig.logGroupName,
client: CloudWatchLogsClient(
region: pluginConfig.region,
credentialsProvider: credentialsProvider,
),
region: pluginConfig.region,
credentialsProvider: credentialsProvider,
) {
_timer = pluginConfig.flushIntervalInSeconds > Duration.zero
? StoppableTimer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import 'dart:async';

import 'package:aws_common/aws_common.dart';
import 'package:aws_logging_cloudwatch/src/sdk/cloud_watch_logs.dart';
import 'package:intl/intl.dart';

Expand All @@ -22,30 +23,36 @@ abstract class CloudWatchLogStreamProvider {
/// {@template aws_logging_cloudwatch.default_cloudwatch_logstream_provider}
/// The default implementaion of [CloudWatchLogStreamProvider].
///
/// It uses `defaultLogStreamName` if provided otherwise uses `yyyy-MM-dd`
/// date format of UTC time now for the `defaultLogStreamName`.
/// It uses `defaultLogStreamNameProvider` if provided otherwise uses
/// `yyyy-MM-dd` date format of UTC time now for the `defaultLogStreamName`.
/// {@endtemplate}
class DefaultCloudWatchLogStreamProvider
implements CloudWatchLogStreamProvider {
/// {@macro aws_logging_cloudwatch.default_cloudwatch_logstream_provider}
DefaultCloudWatchLogStreamProvider({
required CloudWatchLogsClient client,
required AWSCredentialsProvider credentialsProvider,
required String region,
required String logGroupName,
String? defaultLogStreamName,
}) : _defaultLogStreamName = defaultLogStreamName,
_logGroupName = logGroupName,
_client = client;
FutureOr<String> Function()? defaultLogStreamNameProvider,
}) : _logGroupName = logGroupName,
_client = CloudWatchLogsClient(
region: region,
credentialsProvider: credentialsProvider,
),
_defaultLogStreamNameProvider = defaultLogStreamNameProvider;

final String? _defaultLogStreamName;
final FutureOr<String> Function()? _defaultLogStreamNameProvider;
final String _logGroupName;
final CloudWatchLogsClient _client;

static final DateFormat _dateFormat = DateFormat('yyyy-MM-dd');
final _createdLogStreams = <String>{};

@override
Future<String> get defaultLogStream async {
final logStreamName =
_defaultLogStreamName ?? _dateFormat.format(DateTime.timestamp());
final logStreamNameProvider =
_defaultLogStreamNameProvider ?? _timeBasedLogStreamNameProvider;
final logStreamName = await logStreamNameProvider();
if (_createdLogStreams.contains(logStreamName)) {
return logStreamName;
}
Expand All @@ -54,6 +61,10 @@ class DefaultCloudWatchLogStreamProvider
return logStreamName;
}

String _timeBasedLogStreamNameProvider() {
return _dateFormat.format(DateTime.timestamp());
}

@override
Future<void> createLogStream(String logStreamName) async {
try {
Expand Down