Skip to content

Commit 8323b21

Browse files
authored
New event added for sending analytics within package on errors (#229)
* Added new event + refactoring sentEvent on impl * Fix tests + limiting one error event for logFileStats * Make `Analytics` required for `LogHandler` * Make error sent a field in class * Events added for error handling in session handler * Remove unnecessary `io` import * Refactoring `legacyOptOut` to use loop * Only expose `sentEvents` on the `FakeAnalytics` instance * Bump version * Misc * Convert to wip * Pass send method instead of `Analytics` + nits * `ErrorHandler` class created + used in session * Use `ErrorHandler` with `LogHandler` * Check telemetry status in `Session` * Tests added for the survey handler * Fix error * Tests added for log handler exceptions * Use set for sent error messages * Test added to check for 2 unique error events
1 parent 2ef7673 commit 8323b21

15 files changed

+734
-231
lines changed

pkgs/unified_analytics/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 5.8.2-wip
2+
3+
- Added new event `Event.analyticsException` to track internal errors for this package
4+
- Redirecting the `Analytics.test` factory to return an instance of `FakeAnalytics`
5+
16
## 5.8.1
27

38
- Refactor logic for `okToSend` and `shouldShowMessage`

pkgs/unified_analytics/lib/src/analytics.dart

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'asserts.dart';
1616
import 'config_handler.dart';
1717
import 'constants.dart';
1818
import 'enums.dart';
19+
import 'error_handler.dart';
1920
import 'event.dart';
2021
import 'ga_client.dart';
2122
import 'initializer.dart';
@@ -25,6 +26,9 @@ import 'survey_handler.dart';
2526
import 'user_property.dart';
2627
import 'utils.dart';
2728

29+
/// For passing the [Analytics.send] method to classes created by [Analytics].
30+
typedef SendFunction = void Function(Event event);
31+
2832
abstract class Analytics {
2933
/// The default factory constructor that will return an implementation
3034
/// of the [Analytics] abstract class using the [LocalFileSystem].
@@ -60,7 +64,7 @@ abstract class Analytics {
6064
final homeDirectory = getHomeDirectory(fs);
6165
if (homeDirectory == null ||
6266
!checkDirectoryForWritePermissions(homeDirectory)) {
63-
return NoOpAnalytics();
67+
return const NoOpAnalytics();
6468
}
6569

6670
// Resolve the OS using dart:io
@@ -187,7 +191,7 @@ abstract class Analytics {
187191
int toolsMessageVersion = kToolsMessageVersion,
188192
String toolsMessage = kToolsMessage,
189193
}) =>
190-
AnalyticsImpl(
194+
FakeAnalytics(
191195
tool: tool,
192196
homeDirectory: homeDirectory,
193197
flutterChannel: flutterChannel,
@@ -203,7 +207,6 @@ abstract class Analytics {
203207
initializedSurveys: [],
204208
),
205209
gaClient: gaClient ?? const FakeGAClient(),
206-
enableAsserts: true,
207210
clientIde: clientIde,
208211
enabledFeatures: enabledFeatures,
209212
);
@@ -325,6 +328,7 @@ class AnalyticsImpl implements Analytics {
325328
late final UserProperty userProperty;
326329
late final LogHandler _logHandler;
327330
late final Session _sessionHandler;
331+
late final ErrorHandler _errorHandler;
328332
final int toolsMessageVersion;
329333

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

421+
// Initialization for the error handling class that will prevent duplicate
422+
// [Event.analyticsException] events from being sent to GA4
423+
_errorHandler = ErrorHandler(sendFunction: send);
424+
417425
// Initialize the user property class that will be attached to
418426
// each event that is sent to Google Analytics -- it will be responsible
419427
// for getting the session id or rolling the session if the duration
420428
// exceeds [kSessionDurationMinutes]
421-
_sessionHandler = Session(homeDirectory: homeDirectory, fs: fs);
429+
_sessionHandler = Session(
430+
homeDirectory: homeDirectory,
431+
fs: fs,
432+
errorHandler: _errorHandler,
433+
telemetryEnabled: telemetryEnabled,
434+
);
422435
userProperty = UserProperty(
423436
session: _sessionHandler,
424437
flutterChannel: flutterChannel,
@@ -436,7 +449,11 @@ class AnalyticsImpl implements Analytics {
436449
);
437450

438451
// Initialize the log handler to persist events that are being sent
439-
_logHandler = LogHandler(fs: fs, homeDirectory: homeDirectory);
452+
_logHandler = LogHandler(
453+
fs: fs,
454+
homeDirectory: homeDirectory,
455+
errorHandler: _errorHandler,
456+
);
440457
}
441458

442459
@override
@@ -712,10 +729,12 @@ class FakeAnalytics extends AnalyticsImpl {
712729
super.flutterVersion,
713730
super.clientIde,
714731
super.enabledFeatures,
732+
int? toolsMessageVersion,
733+
GAClient? gaClient,
715734
}) : super(
716-
gaClient: const FakeGAClient(),
735+
gaClient: gaClient ?? const FakeGAClient(),
717736
enableAsserts: true,
718-
toolsMessageVersion: kToolsMessageVersion,
737+
toolsMessageVersion: toolsMessageVersion ?? kToolsMessageVersion,
719738
);
720739

721740
@override
@@ -767,9 +786,7 @@ class NoOpAnalytics implements Analytics {
767786
final Map<String, Map<String, Object?>> userPropertyMap =
768787
const <String, Map<String, Object?>>{};
769788

770-
factory NoOpAnalytics() => const NoOpAnalytics._();
771-
772-
const NoOpAnalytics._();
789+
const NoOpAnalytics();
773790

774791
@override
775792
String get clientId => staticClientId;

pkgs/unified_analytics/lib/src/constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const int kLogFileLength = 2500;
8282
const String kLogFileName = 'dart-flutter-telemetry.log';
8383

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

8787
/// The minimum length for a session.
8888
const int kSessionDurationMinutes = 30;

pkgs/unified_analytics/lib/src/enums.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ enum DashEvent {
2222
label: 'analytics_collection_enabled',
2323
description: 'The opt-in status for analytics collection',
2424
),
25+
analyticsException(
26+
label: 'analytics_exception',
27+
description: 'Errors that are encountered within package:unified_analytics',
28+
),
2529
exception(
2630
label: 'exception',
2731
description: 'General errors to log',
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'analytics.dart';
6+
import 'enums.dart';
7+
import 'event.dart';
8+
9+
class ErrorHandler {
10+
/// Stores each of the events that have been sent to GA4 so that the
11+
/// same error doesn't get sent twice.
12+
final Set<Event> _sentErrorEvents = {};
13+
final SendFunction _sendFunction;
14+
15+
/// Handles any errors encountered within package:unified_analytics.
16+
ErrorHandler({required SendFunction sendFunction})
17+
: _sendFunction = sendFunction;
18+
19+
/// Sends the encountered error [Event.analyticsException] to GA4 backend.
20+
///
21+
/// This method will not send the event to GA4 if it has already been
22+
/// sent before during the current process.
23+
void log(Event event) {
24+
if (event.eventName != DashEvent.analyticsException ||
25+
_sentErrorEvents.contains(event)) {
26+
return;
27+
}
28+
29+
_sendFunction(event);
30+
_sentErrorEvents.add(event);
31+
}
32+
}

pkgs/unified_analytics/lib/src/event.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@ final class Event {
1919
: eventName = DashEvent.analyticsCollectionEnabled,
2020
eventData = {'status': status};
2121

22+
/// Event that is emitted when an error occurs within
23+
/// `package:unified_analytics`, tools that are using this package
24+
/// should not use this event constructor.
25+
///
26+
/// Tools using this package should instead use the more generic
27+
/// [Event.exception] constructor.
28+
///
29+
/// [workflow] - refers to what process caused the error, such as
30+
/// "LogHandler.logFileStats".
31+
///
32+
/// [error] - the name of the error, such as "FormatException".
33+
///
34+
/// [description] - the description of the error being caught.
35+
Event.analyticsException({
36+
required String workflow,
37+
required String error,
38+
String? description,
39+
}) : eventName = DashEvent.analyticsException,
40+
eventData = {
41+
'workflow': workflow,
42+
'error': error,
43+
if (description != null) 'description': description,
44+
};
45+
2246
/// This is for various workflows within the flutter tool related
2347
/// to iOS and macOS workflows.
2448
///
@@ -685,7 +709,7 @@ final class Event {
685709
};
686710

687711
@override
688-
int get hashCode => eventData.hashCode;
712+
int get hashCode => Object.hash(eventName, jsonEncode(eventData));
689713

690714
@override
691715
bool operator ==(Object other) =>

0 commit comments

Comments
 (0)