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

[et] Adds a logger #50693

Merged
merged 2 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions tools/engine_tool/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// found in the LICENSE file.

import 'dart:ffi' as ffi show Abi;
import 'dart:io' as io show Directory, exitCode, stderr, stdout;
import 'dart:io' as io show Directory, exitCode, stderr;

import 'package:engine_build_configs/engine_build_configs.dart';
import 'package:engine_repo_tools/engine_repo_tools.dart';
Expand All @@ -13,6 +13,7 @@ import 'package:process_runner/process_runner.dart';

import 'src/commands/command_runner.dart';
import 'src/environment.dart';
import 'src/logger.dart';

void main(List<String> args) async {
// Find the engine repo.
Expand Down Expand Up @@ -55,8 +56,7 @@ void main(List<String> args) async {
engine: engine,
platform: const LocalPlatform(),
processRunner: ProcessRunner(),
stderr: io.stderr,
stdout: io.stdout,
logger: Logger(),
),
configs: configs,
);
Expand Down
4 changes: 2 additions & 2 deletions tools/engine_tool/lib/src/commands/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ final class ToolCommandRunner extends CommandRunner<int> {
try{
return await runCommand(parse(args)) ?? 1;
} on FormatException catch (e) {
environment.stderr.writeln(e);
environment.logger.error(e);
return 1;
} on UsageException catch (e) {
environment.stderr.writeln(e);
environment.logger.error(e);
return 1;
}
}
Expand Down
22 changes: 8 additions & 14 deletions tools/engine_tool/lib/src/commands/query_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,6 @@ final class QueryCommand extends CommandBase {
@override
String get description => 'Provides information about build configurations '
'and tests.';

@override
Future<int> run() async {
environment.stdout.write(usage);
return 0;
}
}

/// The 'query builds' command.
Expand Down Expand Up @@ -97,10 +91,10 @@ final class QueryBuildsCommand extends CommandBase {
final String? builderName = parent!.argResults![_builderFlag] as String?;
final bool verbose = parent!.argResults![_verboseFlag] as bool;
if (!verbose) {
environment.stdout.writeln(
environment.logger.status(
'Add --verbose to see detailed information about each builder',
);
environment.stdout.writeln();
environment.logger.status('');
}
for (final String key in configs.keys) {
if (builderName != null && key != builderName) {
Expand All @@ -112,23 +106,23 @@ final class QueryBuildsCommand extends CommandBase {
continue;
}

environment.stdout.writeln('"$key" builder:');
environment.logger.status('"$key" builder:');
for (final GlobalBuild build in config.builds) {
if (!build.canRunOn(environment.platform) && !all) {
continue;
}
environment.stdout.writeln(' "${build.name}" config');
environment.logger.status('"${build.name}" config', indent: 3);
if (!verbose) {
continue;
}
environment.stdout.writeln(' gn flags:');
environment.logger.status('gn flags:', indent: 6);
for (final String flag in build.gn) {
environment.stdout.writeln(' $flag');
environment.logger.status(flag, indent: 9);
}
if (build.ninja.targets.isNotEmpty) {
environment.stdout.writeln(' ninja targets:');
environment.logger.status('ninja targets:', indent: 6);
for (final String target in build.ninja.targets) {
environment.stdout.writeln(' $target');
environment.logger.status(target, indent: 9);
}
}
}
Expand Down
16 changes: 6 additions & 10 deletions tools/engine_tool/lib/src/environment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:engine_repo_tools/engine_repo_tools.dart';
import 'package:platform/platform.dart';
import 'package:process_runner/process_runner.dart';

import 'logger.dart';

/// This class encapsulates information about the host system.
///
/// Rather than being written directly against `dart:io`, implementations in the
Expand All @@ -19,10 +21,9 @@ final class Environment {
Environment({
required this.abi,
required this.engine,
required this.logger,
required this.platform,
required this.processRunner,
required this.stderr,
required this.stdout,
});

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

/// Where log messages are written.
final Logger logger;

/// More detailed information about the host platform.
final Platform platform;

/// Facility for commands to run subprocesses.
final ProcessRunner processRunner;

// TODO(zanderso): Replace stderr and stdout with a real logger.

/// A sink for error messages from commands.
final StringSink stderr;

/// A sink for non-error messages from commands.
final StringSink stdout;
}
135 changes: 135 additions & 0 deletions tools/engine_tool/lib/src/logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async' show runZoned;
import 'dart:io' as io show
IOSink,
stderr,
stdout;

import 'package:logging/logging.dart' as log;
import 'package:meta/meta.dart';

// This is where a flutter_tool style progress spinner, color output,
// ascii art, terminal control for clearing lines or the whole screen, etc.
// can go. We can just add more methods to Logger using the flutter_tool's
// Logger as a guide:
//
// https://github.com/flutter/flutter/blob/c530276f7806c77da2541c518a0e103c9bb44f10/packages/flutter_tools/lib/src/base/logger.dart#L422

/// A simplified wrapper around the [Logger] from package:logging.
///
/// The default log level is [Logger.status]. A --quiet flag might change it to
/// [Logger.warning] or [Logger.error]. A --verbose flag might change it to
/// [Logger.info].
///
/// Log messages at [Logger.warning] and higher will be written to stderr, and
/// to stdout otherwise. [Logger.test] records all log messages to a buffer,
/// which can be inspected by unit tetss.
class Logger {
/// Constructs a logger for use in the tool.
Logger() : _logger = log.Logger.detached('et') {
_logger.level = statusLevel;
_logger.onRecord.listen(_handler);
_setupIoSink(io.stderr);
_setupIoSink(io.stdout);
}

/// A logger for tests.
@visibleForTesting
Logger.test() : _logger = log.Logger.detached('et') {
_logger.level = statusLevel;
_logger.onRecord.listen((log.LogRecord r) => _testLogs.add(r));
}

/// The logging level for error messages. These go to stderr.
static const log.Level errorLevel = log.Level('ERROR', 100);

/// The logging level for warning messages. These go to stderr.
static const log.Level warningLevel = log.Level('WARNING', 75);

/// The logging level for normal status messages. These go to stdout.
static const log.Level statusLevel = log.Level('STATUS', 25);

/// The logging level for verbose informational messages. These go to stdout.
static const log.Level infoLevel = log.Level('INFO', 10);

static void _handler(log.LogRecord r) {
final io.IOSink sink = r.level >= warningLevel ? io.stderr : io.stdout;
final String prefix = r.level >= warningLevel
? '[${r.time}] ${r.level}: '
: '';
_ioSinkWrite(sink, '$prefix${r.message}');
}

// Status of the global io.stderr and io.stdout is shared across all
// Logger instances.
static bool _stdioDone = false;

// stdout and stderr might already be closed, and when not already closed,
// writing can still fail by throwing either a sync or async exception.
// This function handles all three cases.
static void _ioSinkWrite(io.IOSink sink, String message) {
if (_stdioDone) {
return;
}
runZoned<void>(() {
try {
sink.writeln(message);
} catch (_) { // ignore: avoid_catches_without_on_clauses
_stdioDone = true;
}
}, onError: (Object e, StackTrace s) {
_stdioDone = true;
});
}

static void _setupIoSink(io.IOSink sink) {
sink.done.then(
(void _) { _stdioDone = true; },
onError: (Object err, StackTrace st) { _stdioDone = true; },
);
}

final log.Logger _logger;
final List<log.LogRecord> _testLogs = <log.LogRecord>[];

/// Get the current logging level.
log.Level get level => _logger.level;

/// Set the current logging level.
set level(log.Level l) {
_logger.level = l;
}

/// Record a log message at level [Logger.error].
void error(Object? message, {int indent = 0}) {
_emitLog(errorLevel, message, indent);
}

/// Record a log message at level [Logger.warning].
void warning(Object? message, {int indent = 0}) {
_emitLog(warningLevel, message, indent);
}

/// Record a log message at level [Logger.warning].
void status(Object? message, {int indent = 0}) {
_emitLog(statusLevel, message, indent);
}

/// Record a log message at level [Logger.info].
void info(Object? message, {int indent = 0}) {
_emitLog(infoLevel, message, indent);
}

void _emitLog(log.Level level, Object? message, int indent) {
final String m = '${' ' * indent}$message';
_logger.log(level, m);
}

/// In a [Logger] constructed by [Logger.test], this list will contain all of
/// the [LogRecord]s emitted by the test.
@visibleForTesting
List<log.LogRecord> get testLogs => _testLogs;
}
3 changes: 3 additions & 0 deletions tools/engine_tool/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies:
engine_repo_tools:
path: ../pkg/engine_repo_tools
file: any
logging: any
meta: any
path: any
platform: any
Expand Down Expand Up @@ -51,6 +52,8 @@ dependency_overrides:
path: ../../../third_party/dart/third_party/pkg/file/packages/file
litetest:
path: ../../testing/litetest
logging:
path: ../../../third_party/dart/third_party/pkg/logging
meta:
path: ../../../third_party/dart/pkg/meta
path:
Expand Down
56 changes: 56 additions & 0 deletions tools/engine_tool/test/logger_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:engine_tool/src/logger.dart';
import 'package:litetest/litetest.dart';
import 'package:logging/logging.dart' as log;

void main() {
List<String> stringsFromLogs(List<log.LogRecord> logs) {
return logs.map((log.LogRecord r) => r.message).toList();
}

test('Setting the level works', () {
final Logger logger = Logger.test();
logger.level = Logger.infoLevel;
expect(logger.level, equals(Logger.infoLevel));
});

test('error messages are recorded at the default log level', () {
final Logger logger = Logger.test();
logger.error('Error');
expect(stringsFromLogs(logger.testLogs), equals(<String>['Error']));
});

test('warning messages are recorded at the default log level', () {
final Logger logger = Logger.test();
logger.warning('Warning');
expect(stringsFromLogs(logger.testLogs), equals(<String>['Warning']));
});

test('status messages are recorded at the default log level', () {
final Logger logger = Logger.test();
logger.status('Status');
expect(stringsFromLogs(logger.testLogs), equals(<String>['Status']));
});

test('info messages are not recorded at the default log level', () {
final Logger logger = Logger.test();
logger.info('info');
expect(stringsFromLogs(logger.testLogs), equals(<String>[]));
});

test('info messages are recorded at the infoLevel log level', () {
final Logger logger = Logger.test();
logger.level = Logger.infoLevel;
logger.info('info');
expect(stringsFromLogs(logger.testLogs), equals(<String>['info']));
});

test('indent indents the message', () {
final Logger logger = Logger.test();
logger.status('Status', indent: 1);
expect(stringsFromLogs(logger.testLogs), equals(<String>[' Status']));
});
}
Loading