Skip to content

Add JSON logging #1531

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

Merged
merged 3 commits into from
Nov 3, 2017
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## unreleased
* Add a `--json` flag to providing logging in a machine-readable format.

## 0.14.1
* Add better support for GenericFunctionTypeElementImpl (#1506, #1509)
* Fix up dartdoc so it can be used with the head analyzer again (#1509)
Expand Down
106 changes: 69 additions & 37 deletions bin/dartdoc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

library dartdoc.bin;

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate' show Isolate;

Expand Down Expand Up @@ -122,48 +124,69 @@ main(List<String> arguments) async {
_printUsageAndExit(parser, exitCode: 1);
}

final logJson = args['json'] as bool;

// By default, get all log output at `progressLevel` or greater.
// This allows us to capture progress events and print `...`.
logging.Logger.root.level = progressLevel;
final stopwatch = new Stopwatch()..start();

// Used to track if we're printing `...` to show progress.
// Allows unified new-line tracking
var writingProgress = false;

logging.Logger.root.onRecord.listen((record) {
if (record.level == progressLevel) {
if (showProgress && stopwatch.elapsed.inMilliseconds > 250) {
writingProgress = true;
stdout.write('.');
stopwatch.reset();

if (logJson) {
logging.Logger.root.onRecord.listen((record) {
if (record.level == progressLevel) {
return;
}
return;
}

stopwatch.reset();
if (writingProgress) {
// print a new line after progress dots...
print('');
writingProgress = false;
}
var message = record.message;
assert(message == message.trimRight());
assert(message.isNotEmpty);

if (record.level < logging.Level.WARNING) {
if (message.endsWith('...')) {
// Assume there may be more progress to print, so omit the trailing
// newline
writingProgress = true;
stdout.write(message);
var output = <String, dynamic>{'level': record.level.name};

if (record.object is Jsonable) {
output['data'] = record.object;
} else {
print(message);
output['message'] = record.message;
}
} else {
stderr.writeln(message);
}
});

print(JSON.encode(output));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing this to stdout is OK if you can redirect stdout internally in dartdoc to something else when writing json, so an errant print statement somewhere doesn't break your JSON.

Indeed, that's how this should be implemented generally, assuming that's possible in dart. That way you don't have to require that all output goes through this library.

});
} else {
final stopwatch = new Stopwatch()..start();

// Used to track if we're printing `...` to show progress.
// Allows unified new-line tracking
var writingProgress = false;

logging.Logger.root.onRecord.listen((record) {
if (record.level == progressLevel) {
if (showProgress && stopwatch.elapsed.inMilliseconds > 250) {
writingProgress = true;
stdout.write('.');
stopwatch.reset();
}
return;
}

stopwatch.reset();
if (writingProgress) {
// print a new line after progress dots...
print('');
writingProgress = false;
}
var message = record.message;
assert(message == message.trimRight());
assert(message.isNotEmpty);

if (record.level < logging.Level.WARNING) {
if (message.endsWith('...')) {
// Assume there may be more progress to print, so omit the trailing
// newline
writingProgress = true;
stdout.write(message);
} else {
print(message);
}
} else {
stderr.writeln(message);
}
});
}

PackageMeta packageMeta = sdkDocs
? new PackageMeta.fromSdk(sdkDir,
Expand Down Expand Up @@ -246,8 +269,13 @@ main(List<String> arguments) async {

dartdoc.onCheckProgress.listen(logProgress);
await Chain.capture(() async {
DartDocResults results = await dartdoc.generateDocs();
logInfo('Success! Docs generated into ${results.outDir.absolute.path}');
await runZoned(() async {
DartDocResults results = await dartdoc.generateDocs();
logInfo('Success! Docs generated into ${results.outDir.absolute.path}');
},
zoneSpecification: new ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) =>
logPrint(line) ));
}, onError: (e, Chain chain) {
if (e is DartDocFailure) {
stderr.writeln('\nGeneration failed: ${e}.');
Expand Down Expand Up @@ -354,6 +382,10 @@ ArgParser _createArgsParser() {
defaultsTo: false,
hide: true,
);
parser.addFlag('json',
help: 'Prints out progress JSON maps. One entry per line.',
defaultsTo: false,
negatable: true);

return parser;
}
Expand Down
22 changes: 21 additions & 1 deletion lib/src/logging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ final _logger = new Logger('dartdoc');
/// A custom [Level] for tracking file writes and verification.
///
/// Has a value of `501` – one more than [Level.FINE].
final Level progressLevel = new Level('progress', 501);
final Level progressLevel = new Level('PROGRESS', 501);

/// A custom [Level] for errant print statements.
///
/// Has a value of `1201` – one more than [Level.SHOUT].
final Level printLevel = new Level('PRINT', 1201);

void logWarning(Object message) {
_logger.log(Level.WARNING, message);
Expand All @@ -22,3 +27,18 @@ void logInfo(Object message) {
void logProgress(Object message) {
_logger.log(progressLevel, message);
}

void logPrint(Object message) {
_logger.log(printLevel, message);
}

abstract class Jsonable {
/// The `String` to print when in human-readable mode
String get text;

/// The JSON content to print when in JSON-output mode.
Object toJson();

@override
String toString() => text;
}
37 changes: 30 additions & 7 deletions lib/src/warnings.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. 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:analyzer/dart/element/element.dart';
import 'package:tuple/tuple.dart';

Expand Down Expand Up @@ -164,7 +168,7 @@ class PackageWarningCounter {
final _warningCounts = new Map<PackageWarning, int>();
final PackageWarningOptions options;

final _items = <String>[];
final _items = <Jsonable>[];

PackageWarningCounter(this.options);

Expand All @@ -186,14 +190,14 @@ class PackageWarningCounter {
if (options.ignoreWarnings.contains(kind)) {
return;
}
String toWrite;
String type;
if (options.asErrors.contains(kind)) {
toWrite = "error: ${fullMessage}";
type = "error";
} else if (options.asWarnings.contains(kind)) {
toWrite = "warning: ${fullMessage}";
type = "warning";
}
if (toWrite != null) {
var entry = " ${toWrite}";
if (type != null) {
var entry = " $type: $fullMessage";
if (_warningCounts[kind] == 1 &&
config.verboseWarnings &&
packageWarningText[kind].longHelp.isNotEmpty) {
Expand All @@ -206,7 +210,7 @@ class PackageWarningCounter {
entry = '$entry$verboseOut';
}
assert(entry == entry.trimRight());
_items.add(entry);
_items.add(new _JsonWarning(type, kind, fullMessage, entry));
}
maybeFlush();
}
Expand Down Expand Up @@ -256,3 +260,22 @@ class PackageWarningCounter {
return [errors, warnings].join(', ');
}
}

class _JsonWarning extends Jsonable {
final String type;
final PackageWarning kind;
final String message;

@override
final String text;

_JsonWarning(this.type, this.kind, this.message, this.text);

@override
Map<String, dynamic> toJson() => {
'type': type,
'kind': packageWarningText[kind].warningName,
'message': message,
'text': text
};
}
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,4 @@ packages:
source: hosted
version: "2.1.13"
sdks:
dart: ">=1.23.0 <=2.0.0-dev.5.0"
dart: ">=1.23.0 <=2.0.0-dev.6.0"
33 changes: 33 additions & 0 deletions test/compare_output_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,39 @@ void main() {
}
});

test('Validate JSON output', () {
var args = <String>[
dartdocBin,
'--include',
'ex',
'--no-include-source',
'--output',
tempDir.path,
'--json'
];

var result = Process.runSync(Platform.resolvedExecutable, args,
workingDirectory: _testPackagePath);

if (result.exitCode != 0) {
print(result.exitCode);
print(result.stdout);
print(result.stderr);
fail('dartdoc failed');
}

var jsonValues = LineSplitter
.split(result.stdout)
.map((j) => JSON.decode(j) as Map<String, dynamic>)
.toList();

expect(jsonValues, isNotEmpty,
reason: 'All STDOUT lines should be JSON-encoded maps.');

expect(result.stderr as String, isEmpty,
reason: 'STDERR should be empty.');
});

test('--footer-text includes text', () {
String footerTextPath =
path.join(Directory.systemTemp.path, 'footer.txt');
Expand Down