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

New event added for sending analytics within package on errors #229

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions pkgs/unified_analytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 5.8.2-wip

- Added new event `Event.analyticsException` to track internal errors for this package
- Redirecting the `Analytics.test` factory to return an instance of `FakeAnalytics`

## 5.8.1

- Refactor logic for `okToSend` and `shouldShowMessage`
Expand Down
37 changes: 27 additions & 10 deletions pkgs/unified_analytics/lib/src/analytics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'asserts.dart';
import 'config_handler.dart';
import 'constants.dart';
import 'enums.dart';
import 'error_handler.dart';
import 'event.dart';
import 'ga_client.dart';
import 'initializer.dart';
Expand All @@ -25,6 +26,9 @@ import 'survey_handler.dart';
import 'user_property.dart';
import 'utils.dart';

/// For passing the [Analytics.send] method to classes created by [Analytics].
typedef SendFunction = void Function(Event event);

abstract class Analytics {
/// The default factory constructor that will return an implementation
/// of the [Analytics] abstract class using the [LocalFileSystem].
Expand Down Expand Up @@ -60,7 +64,7 @@ abstract class Analytics {
final homeDirectory = getHomeDirectory(fs);
if (homeDirectory == null ||
!checkDirectoryForWritePermissions(homeDirectory)) {
return NoOpAnalytics();
return const NoOpAnalytics();
}

// Resolve the OS using dart:io
Expand Down Expand Up @@ -187,7 +191,7 @@ abstract class Analytics {
int toolsMessageVersion = kToolsMessageVersion,
String toolsMessage = kToolsMessage,
}) =>
AnalyticsImpl(
FakeAnalytics(
tool: tool,
homeDirectory: homeDirectory,
flutterChannel: flutterChannel,
Expand All @@ -203,7 +207,6 @@ abstract class Analytics {
initializedSurveys: [],
),
gaClient: gaClient ?? const FakeGAClient(),
enableAsserts: true,
clientIde: clientIde,
enabledFeatures: enabledFeatures,
);
Expand Down Expand Up @@ -325,6 +328,7 @@ class AnalyticsImpl implements Analytics {
late final UserProperty userProperty;
late final LogHandler _logHandler;
late final Session _sessionHandler;
late final ErrorHandler _errorHandler;
final int toolsMessageVersion;

/// Tells the client if they need to show a message to the
Expand Down Expand Up @@ -414,11 +418,20 @@ class AnalyticsImpl implements Analytics {
p.join(homeDirectory.path, kDartToolDirectoryName, kClientIdFileName));
_clientId = _clientIdFile.readAsStringSync();

// Initialization for the error handling class that will prevent duplicate
// [Event.analyticsException] events from being sent to GA4
_errorHandler = ErrorHandler(sendFunction: send);

// Initialize the user property class that will be attached to
// each event that is sent to Google Analytics -- it will be responsible
// for getting the session id or rolling the session if the duration
// exceeds [kSessionDurationMinutes]
_sessionHandler = Session(homeDirectory: homeDirectory, fs: fs);
_sessionHandler = Session(
homeDirectory: homeDirectory,
fs: fs,
errorHandler: _errorHandler,
telemetryEnabled: telemetryEnabled,
);
userProperty = UserProperty(
session: _sessionHandler,
flutterChannel: flutterChannel,
Expand All @@ -436,7 +449,11 @@ class AnalyticsImpl implements Analytics {
);

// Initialize the log handler to persist events that are being sent
_logHandler = LogHandler(fs: fs, homeDirectory: homeDirectory);
_logHandler = LogHandler(
fs: fs,
homeDirectory: homeDirectory,
errorHandler: _errorHandler,
);
}

@override
Expand Down Expand Up @@ -712,10 +729,12 @@ class FakeAnalytics extends AnalyticsImpl {
super.flutterVersion,
super.clientIde,
super.enabledFeatures,
int? toolsMessageVersion,
GAClient? gaClient,
}) : super(
gaClient: const FakeGAClient(),
gaClient: gaClient ?? const FakeGAClient(),
enableAsserts: true,
toolsMessageVersion: kToolsMessageVersion,
toolsMessageVersion: toolsMessageVersion ?? kToolsMessageVersion,
);

@override
Expand Down Expand Up @@ -767,9 +786,7 @@ class NoOpAnalytics implements Analytics {
final Map<String, Map<String, Object?>> userPropertyMap =
const <String, Map<String, Object?>>{};

factory NoOpAnalytics() => const NoOpAnalytics._();

const NoOpAnalytics._();
const NoOpAnalytics();

@override
String get clientId => staticClientId;
Expand Down
2 changes: 1 addition & 1 deletion pkgs/unified_analytics/lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const int kLogFileLength = 2500;
const String kLogFileName = 'dart-flutter-telemetry.log';

/// The current version of the package, should be in line with pubspec version.
const String kPackageVersion = '5.8.1';
const String kPackageVersion = '5.8.2-wip';

/// The minimum length for a session.
const int kSessionDurationMinutes = 30;
Expand Down
4 changes: 4 additions & 0 deletions pkgs/unified_analytics/lib/src/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ enum DashEvent {
label: 'analytics_collection_enabled',
description: 'The opt-in status for analytics collection',
),
analyticsException(
label: 'analytics_exception',
description: 'Errors that are encountered within package:unified_analytics',
),
exception(
label: 'exception',
description: 'General errors to log',
Expand Down
32 changes: 32 additions & 0 deletions pkgs/unified_analytics/lib/src/error_handler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'analytics.dart';
import 'enums.dart';
import 'event.dart';

class ErrorHandler {
/// Stores each of the events that have been sent to GA4 so that the
/// same error doesn't get sent twice.
final Set<Event> _sentErrorEvents = {};
final SendFunction _sendFunction;
Comment on lines +9 to +13
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@christopherfujino thoughts?


/// Handles any errors encountered within package:unified_analytics.
ErrorHandler({required SendFunction sendFunction})
: _sendFunction = sendFunction;

/// Sends the encountered error [Event.analyticsException] to GA4 backend.
///
/// This method will not send the event to GA4 if it has already been
/// sent before during the current process.
void log(Event event) {
if (event.eventName != DashEvent.analyticsException ||
_sentErrorEvents.contains(event)) {
return;
}

_sendFunction(event);
_sentErrorEvents.add(event);
}
}
26 changes: 25 additions & 1 deletion pkgs/unified_analytics/lib/src/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ final class Event {
: eventName = DashEvent.analyticsCollectionEnabled,
eventData = {'status': status};

/// Event that is emitted when an error occurs within
eliasyishak marked this conversation as resolved.
Show resolved Hide resolved
/// `package:unified_analytics`, tools that are using this package
/// should not use this event constructor.
///
/// Tools using this package should instead use the more generic
/// [Event.exception] constructor.
///
/// [workflow] - refers to what process caused the error, such as
/// "LogHandler.logFileStats".
///
/// [error] - the name of the error, such as "FormatException".
///
/// [description] - the description of the error being caught.
Event.analyticsException({
required String workflow,
required String error,
String? description,
}) : eventName = DashEvent.analyticsException,
eventData = {
'workflow': workflow,
'error': error,
if (description != null) 'description': description,
};

/// This is for various workflows within the flutter tool related
/// to iOS and macOS workflows.
///
Expand Down Expand Up @@ -685,7 +709,7 @@ final class Event {
};

@override
int get hashCode => eventData.hashCode;
int get hashCode => Object.hash(eventName, jsonEncode(eventData));

@override
bool operator ==(Object other) =>
Expand Down
Loading