Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit b4e4d17

Browse files
authored
[et] Adds a logger (#50693)
1 parent 23462bc commit b4e4d17

File tree

8 files changed

+233
-47
lines changed

8 files changed

+233
-47
lines changed

tools/engine_tool/lib/main.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:ffi' as ffi show Abi;
6-
import 'dart:io' as io show Directory, exitCode, stderr, stdout;
6+
import 'dart:io' as io show Directory, exitCode, stderr;
77

88
import 'package:engine_build_configs/engine_build_configs.dart';
99
import 'package:engine_repo_tools/engine_repo_tools.dart';
@@ -13,6 +13,7 @@ import 'package:process_runner/process_runner.dart';
1313

1414
import 'src/commands/command_runner.dart';
1515
import 'src/environment.dart';
16+
import 'src/logger.dart';
1617

1718
void main(List<String> args) async {
1819
// Find the engine repo.
@@ -55,8 +56,7 @@ void main(List<String> args) async {
5556
engine: engine,
5657
platform: const LocalPlatform(),
5758
processRunner: ProcessRunner(),
58-
stderr: io.stderr,
59-
stdout: io.stdout,
59+
logger: Logger(),
6060
),
6161
configs: configs,
6262
);

tools/engine_tool/lib/src/commands/command_runner.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ final class ToolCommandRunner extends CommandRunner<int> {
4343
try{
4444
return await runCommand(parse(args)) ?? 1;
4545
} on FormatException catch (e) {
46-
environment.stderr.writeln(e);
46+
environment.logger.error(e);
4747
return 1;
4848
} on UsageException catch (e) {
49-
environment.stderr.writeln(e);
49+
environment.logger.error(e);
5050
return 1;
5151
}
5252
}

tools/engine_tool/lib/src/commands/query_command.dart

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,6 @@ final class QueryCommand extends CommandBase {
6363
@override
6464
String get description => 'Provides information about build configurations '
6565
'and tests.';
66-
67-
@override
68-
Future<int> run() async {
69-
environment.stdout.write(usage);
70-
return 0;
71-
}
7266
}
7367

7468
/// The 'query builds' command.
@@ -97,10 +91,10 @@ final class QueryBuildsCommand extends CommandBase {
9791
final String? builderName = parent!.argResults![_builderFlag] as String?;
9892
final bool verbose = parent!.argResults![_verboseFlag] as bool;
9993
if (!verbose) {
100-
environment.stdout.writeln(
94+
environment.logger.status(
10195
'Add --verbose to see detailed information about each builder',
10296
);
103-
environment.stdout.writeln();
97+
environment.logger.status('');
10498
}
10599
for (final String key in configs.keys) {
106100
if (builderName != null && key != builderName) {
@@ -112,23 +106,23 @@ final class QueryBuildsCommand extends CommandBase {
112106
continue;
113107
}
114108

115-
environment.stdout.writeln('"$key" builder:');
109+
environment.logger.status('"$key" builder:');
116110
for (final GlobalBuild build in config.builds) {
117111
if (!build.canRunOn(environment.platform) && !all) {
118112
continue;
119113
}
120-
environment.stdout.writeln(' "${build.name}" config');
114+
environment.logger.status('"${build.name}" config', indent: 3);
121115
if (!verbose) {
122116
continue;
123117
}
124-
environment.stdout.writeln(' gn flags:');
118+
environment.logger.status('gn flags:', indent: 6);
125119
for (final String flag in build.gn) {
126-
environment.stdout.writeln(' $flag');
120+
environment.logger.status(flag, indent: 9);
127121
}
128122
if (build.ninja.targets.isNotEmpty) {
129-
environment.stdout.writeln(' ninja targets:');
123+
environment.logger.status('ninja targets:', indent: 6);
130124
for (final String target in build.ninja.targets) {
131-
environment.stdout.writeln(' $target');
125+
environment.logger.status(target, indent: 9);
132126
}
133127
}
134128
}

tools/engine_tool/lib/src/environment.dart

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import 'package:engine_repo_tools/engine_repo_tools.dart';
88
import 'package:platform/platform.dart';
99
import 'package:process_runner/process_runner.dart';
1010

11+
import 'logger.dart';
12+
1113
/// This class encapsulates information about the host system.
1214
///
1315
/// Rather than being written directly against `dart:io`, implementations in the
@@ -19,10 +21,9 @@ final class Environment {
1921
Environment({
2022
required this.abi,
2123
required this.engine,
24+
required this.logger,
2225
required this.platform,
2326
required this.processRunner,
24-
required this.stderr,
25-
required this.stdout,
2627
});
2728

2829
/// The host OS and architecture that the tool is running on.
@@ -31,17 +32,12 @@ final class Environment {
3132
/// Information about paths in the engine repo.
3233
final Engine engine;
3334

35+
/// Where log messages are written.
36+
final Logger logger;
37+
3438
/// More detailed information about the host platform.
3539
final Platform platform;
3640

3741
/// Facility for commands to run subprocesses.
3842
final ProcessRunner processRunner;
39-
40-
// TODO(zanderso): Replace stderr and stdout with a real logger.
41-
42-
/// A sink for error messages from commands.
43-
final StringSink stderr;
44-
45-
/// A sink for non-error messages from commands.
46-
final StringSink stdout;
4743
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async' show runZoned;
6+
import 'dart:io' as io show
7+
IOSink,
8+
stderr,
9+
stdout;
10+
11+
import 'package:logging/logging.dart' as log;
12+
import 'package:meta/meta.dart';
13+
14+
// This is where a flutter_tool style progress spinner, color output,
15+
// ascii art, terminal control for clearing lines or the whole screen, etc.
16+
// can go. We can just add more methods to Logger using the flutter_tool's
17+
// Logger as a guide:
18+
//
19+
// https://github.com/flutter/flutter/blob/c530276f7806c77da2541c518a0e103c9bb44f10/packages/flutter_tools/lib/src/base/logger.dart#L422
20+
21+
/// A simplified wrapper around the [Logger] from package:logging.
22+
///
23+
/// The default log level is [Logger.status]. A --quiet flag might change it to
24+
/// [Logger.warning] or [Logger.error]. A --verbose flag might change it to
25+
/// [Logger.info].
26+
///
27+
/// Log messages at [Logger.warning] and higher will be written to stderr, and
28+
/// to stdout otherwise. [Logger.test] records all log messages to a buffer,
29+
/// which can be inspected by unit tetss.
30+
class Logger {
31+
/// Constructs a logger for use in the tool.
32+
Logger() : _logger = log.Logger.detached('et') {
33+
_logger.level = statusLevel;
34+
_logger.onRecord.listen(_handler);
35+
_setupIoSink(io.stderr);
36+
_setupIoSink(io.stdout);
37+
}
38+
39+
/// A logger for tests.
40+
@visibleForTesting
41+
Logger.test() : _logger = log.Logger.detached('et') {
42+
_logger.level = statusLevel;
43+
_logger.onRecord.listen((log.LogRecord r) => _testLogs.add(r));
44+
}
45+
46+
/// The logging level for error messages. These go to stderr.
47+
static const log.Level errorLevel = log.Level('ERROR', 100);
48+
49+
/// The logging level for warning messages. These go to stderr.
50+
static const log.Level warningLevel = log.Level('WARNING', 75);
51+
52+
/// The logging level for normal status messages. These go to stdout.
53+
static const log.Level statusLevel = log.Level('STATUS', 25);
54+
55+
/// The logging level for verbose informational messages. These go to stdout.
56+
static const log.Level infoLevel = log.Level('INFO', 10);
57+
58+
static void _handler(log.LogRecord r) {
59+
final io.IOSink sink = r.level >= warningLevel ? io.stderr : io.stdout;
60+
final String prefix = r.level >= warningLevel
61+
? '[${r.time}] ${r.level}: '
62+
: '';
63+
_ioSinkWrite(sink, '$prefix${r.message}');
64+
}
65+
66+
// Status of the global io.stderr and io.stdout is shared across all
67+
// Logger instances.
68+
static bool _stdioDone = false;
69+
70+
// stdout and stderr might already be closed, and when not already closed,
71+
// writing can still fail by throwing either a sync or async exception.
72+
// This function handles all three cases.
73+
static void _ioSinkWrite(io.IOSink sink, String message) {
74+
if (_stdioDone) {
75+
return;
76+
}
77+
runZoned<void>(() {
78+
try {
79+
sink.writeln(message);
80+
} catch (_) { // ignore: avoid_catches_without_on_clauses
81+
_stdioDone = true;
82+
}
83+
}, onError: (Object e, StackTrace s) {
84+
_stdioDone = true;
85+
});
86+
}
87+
88+
static void _setupIoSink(io.IOSink sink) {
89+
sink.done.then(
90+
(void _) { _stdioDone = true; },
91+
onError: (Object err, StackTrace st) { _stdioDone = true; },
92+
);
93+
}
94+
95+
final log.Logger _logger;
96+
final List<log.LogRecord> _testLogs = <log.LogRecord>[];
97+
98+
/// Get the current logging level.
99+
log.Level get level => _logger.level;
100+
101+
/// Set the current logging level.
102+
set level(log.Level l) {
103+
_logger.level = l;
104+
}
105+
106+
/// Record a log message at level [Logger.error].
107+
void error(Object? message, {int indent = 0}) {
108+
_emitLog(errorLevel, message, indent);
109+
}
110+
111+
/// Record a log message at level [Logger.warning].
112+
void warning(Object? message, {int indent = 0}) {
113+
_emitLog(warningLevel, message, indent);
114+
}
115+
116+
/// Record a log message at level [Logger.warning].
117+
void status(Object? message, {int indent = 0}) {
118+
_emitLog(statusLevel, message, indent);
119+
}
120+
121+
/// Record a log message at level [Logger.info].
122+
void info(Object? message, {int indent = 0}) {
123+
_emitLog(infoLevel, message, indent);
124+
}
125+
126+
void _emitLog(log.Level level, Object? message, int indent) {
127+
final String m = '${' ' * indent}$message';
128+
_logger.log(level, m);
129+
}
130+
131+
/// In a [Logger] constructed by [Logger.test], this list will contain all of
132+
/// the [LogRecord]s emitted by the test.
133+
@visibleForTesting
134+
List<log.LogRecord> get testLogs => _testLogs;
135+
}

tools/engine_tool/pubspec.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dependencies:
2323
engine_repo_tools:
2424
path: ../pkg/engine_repo_tools
2525
file: any
26+
logging: any
2627
meta: any
2728
path: any
2829
platform: any
@@ -51,6 +52,8 @@ dependency_overrides:
5152
path: ../../../third_party/dart/third_party/pkg/file/packages/file
5253
litetest:
5354
path: ../../testing/litetest
55+
logging:
56+
path: ../../../third_party/dart/third_party/pkg/logging
5457
meta:
5558
path: ../../../third_party/dart/pkg/meta
5659
path:
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:engine_tool/src/logger.dart';
6+
import 'package:litetest/litetest.dart';
7+
import 'package:logging/logging.dart' as log;
8+
9+
void main() {
10+
List<String> stringsFromLogs(List<log.LogRecord> logs) {
11+
return logs.map((log.LogRecord r) => r.message).toList();
12+
}
13+
14+
test('Setting the level works', () {
15+
final Logger logger = Logger.test();
16+
logger.level = Logger.infoLevel;
17+
expect(logger.level, equals(Logger.infoLevel));
18+
});
19+
20+
test('error messages are recorded at the default log level', () {
21+
final Logger logger = Logger.test();
22+
logger.error('Error');
23+
expect(stringsFromLogs(logger.testLogs), equals(<String>['Error']));
24+
});
25+
26+
test('warning messages are recorded at the default log level', () {
27+
final Logger logger = Logger.test();
28+
logger.warning('Warning');
29+
expect(stringsFromLogs(logger.testLogs), equals(<String>['Warning']));
30+
});
31+
32+
test('status messages are recorded at the default log level', () {
33+
final Logger logger = Logger.test();
34+
logger.status('Status');
35+
expect(stringsFromLogs(logger.testLogs), equals(<String>['Status']));
36+
});
37+
38+
test('info messages are not recorded at the default log level', () {
39+
final Logger logger = Logger.test();
40+
logger.info('info');
41+
expect(stringsFromLogs(logger.testLogs), equals(<String>[]));
42+
});
43+
44+
test('info messages are recorded at the infoLevel log level', () {
45+
final Logger logger = Logger.test();
46+
logger.level = Logger.infoLevel;
47+
logger.info('info');
48+
expect(stringsFromLogs(logger.testLogs), equals(<String>['info']));
49+
});
50+
51+
test('indent indents the message', () {
52+
final Logger logger = Logger.test();
53+
logger.status('Status', indent: 1);
54+
expect(stringsFromLogs(logger.testLogs), equals(<String>[' Status']));
55+
});
56+
}

0 commit comments

Comments
 (0)