-
Notifications
You must be signed in to change notification settings - Fork 54
New event added for sending analytics within package on errors #229
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
Changes from 12 commits
f87a22f
116b710
421df72
6f0e8a4
ac0f3ad
42161bc
5ec6a99
2bee9cc
e313828
b0960ea
5307fde
48abbd8
6079595
0120151
ef19bb6
103980a
74e7f8b
b67ce4b
1bd099f
369147b
39cb809
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import 'package:clock/clock.dart'; | |
import 'package:file/file.dart'; | ||
import 'package:path/path.dart' as p; | ||
|
||
import '../unified_analytics.dart'; | ||
import 'constants.dart'; | ||
import 'initializer.dart'; | ||
|
||
|
@@ -81,22 +82,6 @@ class LogFileStats { | |
required this.eventCount, | ||
}); | ||
|
||
@override | ||
String toString() { | ||
final encoder = const JsonEncoder.withIndent(' '); | ||
return encoder.convert({ | ||
'startDateTime': startDateTime.toString(), | ||
'minsFromStartDateTime': minsFromStartDateTime, | ||
'endDateTime': endDateTime.toString(), | ||
'minsFromEndDateTime': minsFromEndDateTime, | ||
'sessionCount': sessionCount, | ||
'recordCount': recordCount, | ||
'eventCount': eventCount, | ||
'toolCount': toolCount, | ||
'flutterChannelCount': flutterChannelCount, | ||
}); | ||
} | ||
|
||
/// Pass in a string label for one of the instance variables | ||
/// and return the integer value of that label. | ||
/// | ||
|
@@ -149,6 +134,22 @@ class LogFileStats { | |
|
||
return null; | ||
} | ||
|
||
@override | ||
String toString() { | ||
final encoder = const JsonEncoder.withIndent(' '); | ||
return encoder.convert({ | ||
'startDateTime': startDateTime.toString(), | ||
'minsFromStartDateTime': minsFromStartDateTime, | ||
'endDateTime': endDateTime.toString(), | ||
'minsFromEndDateTime': minsFromEndDateTime, | ||
'sessionCount': sessionCount, | ||
'recordCount': recordCount, | ||
'eventCount': eventCount, | ||
'toolCount': toolCount, | ||
'flutterChannelCount': flutterChannelCount, | ||
}); | ||
} | ||
} | ||
|
||
/// This class is responsible for writing to a log | ||
|
@@ -160,17 +161,23 @@ class LogHandler { | |
final FileSystem fs; | ||
final Directory homeDirectory; | ||
final File logFile; | ||
final Analytics _analyticsInstance; | ||
|
||
/// Ensure we are only sending once per invocation for this class. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why only send once? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the log handlers case, we are checking if each log record is malformed. So in the worst case scenario, we could have 2,500 malformed log records. This check would it make it so that we don't receive tons of errors logs from just one client Code from log handler below where send these events final records = logFile
.readAsLinesSync()
.map((String e) {
try {
return LogItem.fromRecord(jsonDecode(e) as Map<String, Object?>);
} on FormatException catch (err) {
if (!_errorEventSent) {
_sendFunction(Event.analyticsException(
workflow: 'LogFileStats.logFileStats',
error: err.runtimeType.toString(),
description: 'message: ${err.message}\nsource: ${err.source}',
));
_errorEventSent = true;
}
return null;
// ignore: avoid_catching_errors
} on TypeError catch (err) {
if (!_errorEventSent) {
_sendFunction(Event.analyticsException(
workflow: 'LogFileStats.logFileStats',
error: err.runtimeType.toString(),
));
_errorEventSent = true;
}
return null;
}
})
.whereType<LogItem>()
.toList(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is an "invocation" in this instance? For an analysis server session that lasts 8 hours, would we only ever send one error event, or is the LogHandler not that long-lived? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh that is a good point, as it stands right now, it would be per dart process. In that case, i can rework the logic for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than using timestamps, can we rather group together operations that we expect could error multiple times, and track a single local bool errorAlreadySent for those operations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might be even better to create an abstraction for handling errors, something like This class can keep track of what events we have sent for errors and block any events we have already sent. That should centralize our error handling to one class |
||
var _errorEventSent = false; | ||
|
||
/// A log handler constructor that will delegate saving | ||
/// logs and retrieving stats from the persisted log. | ||
LogHandler({ | ||
required this.fs, | ||
required this.homeDirectory, | ||
}) : logFile = fs.file(p.join( | ||
required Analytics analyticsInstance, | ||
}) : logFile = fs.file(p.join( | ||
homeDirectory.path, | ||
kDartToolDirectoryName, | ||
kLogFileName, | ||
)); | ||
)), | ||
_analyticsInstance = analyticsInstance; | ||
|
||
/// Get stats from the persisted log file. | ||
/// | ||
|
@@ -184,15 +191,27 @@ class LogHandler { | |
final records = logFile | ||
.readAsLinesSync() | ||
.map((String e) { | ||
// TODO: eliasyishak, once https://github.com/dart-lang/tools/issues/167 | ||
// has landed ensure we are sending an event for each error | ||
// with helpful messages | ||
try { | ||
return LogItem.fromRecord(jsonDecode(e) as Map<String, Object?>); | ||
} on FormatException { | ||
} on FormatException catch (err) { | ||
if (!_errorEventSent) { | ||
_analyticsInstance.send(Event.analyticsException( | ||
workflow: 'LogFileStats.logFileStats', | ||
error: err.runtimeType.toString(), | ||
description: 'message: ${err.message}\nsource: ${err.source}', | ||
)); | ||
_errorEventSent = true; | ||
} | ||
return null; | ||
// ignore: avoid_catching_errors | ||
} on TypeError { | ||
} on TypeError catch (err) { | ||
if (!_errorEventSent) { | ||
_analyticsInstance.send(Event.analyticsException( | ||
workflow: 'LogFileStats.logFileStats', | ||
error: err.runtimeType.toString(), | ||
)); | ||
_errorEventSent = true; | ||
} | ||
return null; | ||
} | ||
}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,28 +3,35 @@ | |
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'dart:convert'; | ||
import 'dart:io'; | ||
|
||
import 'package:clock/clock.dart'; | ||
import 'package:file/file.dart'; | ||
import 'package:path/path.dart' as p; | ||
|
||
import 'analytics.dart'; | ||
import 'constants.dart'; | ||
import 'event.dart'; | ||
import 'initializer.dart'; | ||
|
||
class Session { | ||
final Directory homeDirectory; | ||
final FileSystem fs; | ||
final File sessionFile; | ||
final Analytics _analyticsInstance; | ||
|
||
late int _sessionId; | ||
late int _lastPing; | ||
|
||
/// Ensure we are only sending once per invocation for this class. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, why only once? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same idea for the session handler here except we aren't dealing with the loop from the So if a user had 100 events sent while an instance of |
||
var _errorEventSent = false; | ||
|
||
Session({ | ||
required this.homeDirectory, | ||
required this.fs, | ||
}) : sessionFile = fs.file(p.join( | ||
homeDirectory.path, kDartToolDirectoryName, kSessionFileName)) { | ||
required Analytics analyticsInstance, | ||
}) : sessionFile = fs.file(p.join( | ||
homeDirectory.path, kDartToolDirectoryName, kSessionFileName)), | ||
_analyticsInstance = analyticsInstance { | ||
_refreshSessionData(); | ||
} | ||
|
||
|
@@ -87,13 +94,31 @@ class Session { | |
|
||
try { | ||
parseContents(); | ||
} on FormatException { | ||
} on FormatException catch (err) { | ||
Initializer.createSessionFile(sessionFile: sessionFile); | ||
|
||
if (!_errorEventSent) { | ||
_analyticsInstance.send(Event.analyticsException( | ||
workflow: 'Session._refreshSessionData', | ||
error: err.runtimeType.toString(), | ||
description: 'message: ${err.message}\nsource: ${err.source}', | ||
)); | ||
_errorEventSent = true; | ||
} | ||
|
||
parseContents(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this just throw again and not be caught? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this was naively assuming that the error we encounter while parsing the json was user related and not coming from the package, creating a fix for this now |
||
} on PathNotFoundException { | ||
} on FileSystemException catch (err) { | ||
Initializer.createSessionFile(sessionFile: sessionFile); | ||
|
||
if (!_errorEventSent) { | ||
_analyticsInstance.send(Event.analyticsException( | ||
workflow: 'Session._refreshSessionData', | ||
error: err.runtimeType.toString(), | ||
description: err.osError?.toString(), | ||
)); | ||
_errorEventSent = true; | ||
} | ||
|
||
parseContents(); | ||
} | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.