Skip to content

Commit 4c60686

Browse files
delete old log file if it exceeds kMaxLogFileSize of 25MiB (#277)
* delete old log file if it exceeds kMaxLogFileSize of 25MiB * update changelog * bump version in pubspec.yaml * update constants.dart
1 parent 7a231e5 commit 4c60686

File tree

5 files changed

+114
-5
lines changed

5 files changed

+114
-5
lines changed

pkgs/unified_analytics/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 6.1.2
2+
3+
- Avoid opening large telemetry log files to prevent out of memory errors.
4+
15
## 6.1.1
26

37
- Fixed bug where calling `Analytics.send` could result in a `FileSystemException` when unable to write to a log file.

pkgs/unified_analytics/lib/src/constants.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,16 @@ const String kGoogleAnalyticsMeasurementId = 'G-04BXPVBCWJ';
7878
/// How many data records to store in the log file.
7979
const int kLogFileLength = 2500;
8080

81+
/// The maximum allowed size of the telemetry log file.
82+
///
83+
/// 25 MiB.
84+
const int kMaxLogFileSize = 25 * (1 << 20);
85+
8186
/// Filename for the log file to persist sent events on user's machine.
8287
const String kLogFileName = 'dart-flutter-telemetry.log';
8388

8489
/// The current version of the package, should be in line with pubspec version.
85-
const String kPackageVersion = '6.1.1';
90+
const String kPackageVersion = '6.1.2';
8691

8792
/// The minimum length for a session.
8893
const int kSessionDurationMinutes = 30;

pkgs/unified_analytics/lib/src/log_handler.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,18 @@ class LogHandler {
272272
/// This will keep the max number of records limited to equal to
273273
/// or less than [kLogFileLength] records.
274274
void save({required Map<String, Object?> data}) {
275-
var records = logFile.readAsLinesSync();
276-
final content = '${jsonEncode(data)}\n';
277-
278275
try {
276+
final stat = logFile.statSync();
277+
List<String> records;
278+
if (stat.size > kMaxLogFileSize) {
279+
logFile.deleteSync();
280+
logFile.createSync();
281+
records = [];
282+
} else {
283+
records = logFile.readAsLinesSync();
284+
}
285+
final content = '${jsonEncode(data)}\n';
286+
279287
// When the record count is less than the max, add as normal;
280288
// else drop the oldest records until equal to max
281289
if (records.length < kLogFileLength) {

pkgs/unified_analytics/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: >-
44
to Google Analytics.
55
# When updating this, keep the version consistent with the changelog and the
66
# value in lib/src/constants.dart.
7-
version: 6.1.1
7+
version: 6.1.2
88
repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics
99

1010
environment:

pkgs/unified_analytics/test/log_handler_test.dart

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:convert';
6+
57
import 'package:file/file.dart';
68
import 'package:file/memory.dart';
79
import 'package:path/path.dart' as p;
10+
import 'package:test/fake.dart';
811
import 'package:test/test.dart';
912

1013
import 'package:unified_analytics/src/constants.dart';
@@ -212,6 +215,47 @@ void main() {
212215
logHandler.save(data: {});
213216
});
214217

218+
test('deletes log file larger than kMaxLogFileSize', () async {
219+
var deletedLargeLogFile = false;
220+
var wroteDataToLogFile = false;
221+
const data = <String, Object?>{};
222+
final logFile = _FakeFile('log.txt')
223+
.._deleteSyncImpl = (() => deletedLargeLogFile = true)
224+
.._createSyncImpl = () {}
225+
.._statSyncImpl = (() => _FakeFileStat(kMaxLogFileSize + 1))
226+
.._writeAsStringSync = (contents, {mode = FileMode.append}) {
227+
expect(contents.trim(), data.toString());
228+
expect(mode, FileMode.writeOnlyAppend);
229+
wroteDataToLogFile = true;
230+
};
231+
final logHandler = LogHandler(logFile: logFile);
232+
233+
logHandler.save(data: data);
234+
expect(deletedLargeLogFile, isTrue);
235+
expect(wroteDataToLogFile, isTrue);
236+
});
237+
238+
test('does not delete log file if smaller than kMaxLogFileSize', () async {
239+
var wroteDataToLogFile = false;
240+
const data = <String, Object?>{};
241+
final logFile = _FakeFile('log.txt')
242+
.._deleteSyncImpl =
243+
(() => fail('called logFile.deleteSync() when file was less than '
244+
'kMaxLogFileSize'))
245+
.._createSyncImpl = () {}
246+
.._readAsLinesSyncImpl = (() => ['three', 'previous', 'lines'])
247+
.._statSyncImpl = (() => _FakeFileStat(kMaxLogFileSize - 1))
248+
.._writeAsStringSync = (contents, {mode = FileMode.append}) {
249+
expect(contents.trim(), data.toString());
250+
expect(mode, FileMode.writeOnlyAppend);
251+
wroteDataToLogFile = true;
252+
};
253+
final logHandler = LogHandler(logFile: logFile);
254+
255+
logHandler.save(data: data);
256+
expect(wroteDataToLogFile, isTrue);
257+
});
258+
215259
test('Catching cast errors for each log record silently', () async {
216260
// Write a json array to the log file which will cause
217261
// a cast error when parsing each line
@@ -290,3 +334,51 @@ void main() {
290334
expect(newString, testString);
291335
});
292336
}
337+
338+
class _FakeFileStat extends Fake implements FileStat {
339+
_FakeFileStat(this.size);
340+
341+
@override
342+
final int size;
343+
}
344+
345+
class _FakeFile extends Fake implements File {
346+
_FakeFile(this.path);
347+
348+
List<String> Function()? _readAsLinesSyncImpl;
349+
350+
@override
351+
List<String> readAsLinesSync({Encoding encoding = utf8}) =>
352+
_readAsLinesSyncImpl!();
353+
354+
@override
355+
final String path;
356+
357+
FileStat Function()? _statSyncImpl;
358+
359+
@override
360+
FileStat statSync() => _statSyncImpl!();
361+
362+
void Function()? _deleteSyncImpl;
363+
364+
@override
365+
void deleteSync({bool recursive = false}) => _deleteSyncImpl!();
366+
367+
void Function()? _createSyncImpl;
368+
369+
@override
370+
void createSync({bool recursive = false, bool exclusive = false}) {
371+
return _createSyncImpl!();
372+
}
373+
374+
void Function(String contents, {FileMode mode})? _writeAsStringSync;
375+
376+
@override
377+
void writeAsStringSync(
378+
String contents, {
379+
FileMode mode = FileMode.write,
380+
Encoding encoding = utf8,
381+
bool flush = false,
382+
}) =>
383+
_writeAsStringSync!(contents, mode: mode);
384+
}

0 commit comments

Comments
 (0)