Skip to content

Commit f2fddbb

Browse files
authored
Add JSON logging (#1531)
1 parent 89fb9bc commit f2fddbb

File tree

6 files changed

+157
-46
lines changed

6 files changed

+157
-46
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## unreleased
2+
* Add a `--json` flag to providing logging in a machine-readable format.
3+
14
## 0.14.1
25
* Add better support for GenericFunctionTypeElementImpl (#1506, #1509)
36
* Fix up dartdoc so it can be used with the head analyzer again (#1509)

bin/dartdoc.dart

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
library dartdoc.bin;
66

7+
import 'dart:async';
8+
import 'dart:convert';
79
import 'dart:io';
810
import 'dart:isolate' show Isolate;
911

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

127+
final logJson = args['json'] as bool;
128+
125129
// By default, get all log output at `progressLevel` or greater.
126130
// This allows us to capture progress events and print `...`.
127131
logging.Logger.root.level = progressLevel;
128-
final stopwatch = new Stopwatch()..start();
129-
130-
// Used to track if we're printing `...` to show progress.
131-
// Allows unified new-line tracking
132-
var writingProgress = false;
133-
134-
logging.Logger.root.onRecord.listen((record) {
135-
if (record.level == progressLevel) {
136-
if (showProgress && stopwatch.elapsed.inMilliseconds > 250) {
137-
writingProgress = true;
138-
stdout.write('.');
139-
stopwatch.reset();
132+
133+
if (logJson) {
134+
logging.Logger.root.onRecord.listen((record) {
135+
if (record.level == progressLevel) {
136+
return;
140137
}
141-
return;
142-
}
143138

144-
stopwatch.reset();
145-
if (writingProgress) {
146-
// print a new line after progress dots...
147-
print('');
148-
writingProgress = false;
149-
}
150-
var message = record.message;
151-
assert(message == message.trimRight());
152-
assert(message.isNotEmpty);
153-
154-
if (record.level < logging.Level.WARNING) {
155-
if (message.endsWith('...')) {
156-
// Assume there may be more progress to print, so omit the trailing
157-
// newline
158-
writingProgress = true;
159-
stdout.write(message);
139+
var output = <String, dynamic>{'level': record.level.name};
140+
141+
if (record.object is Jsonable) {
142+
output['data'] = record.object;
160143
} else {
161-
print(message);
144+
output['message'] = record.message;
162145
}
163-
} else {
164-
stderr.writeln(message);
165-
}
166-
});
146+
147+
print(JSON.encode(output));
148+
});
149+
} else {
150+
final stopwatch = new Stopwatch()..start();
151+
152+
// Used to track if we're printing `...` to show progress.
153+
// Allows unified new-line tracking
154+
var writingProgress = false;
155+
156+
logging.Logger.root.onRecord.listen((record) {
157+
if (record.level == progressLevel) {
158+
if (showProgress && stopwatch.elapsed.inMilliseconds > 250) {
159+
writingProgress = true;
160+
stdout.write('.');
161+
stopwatch.reset();
162+
}
163+
return;
164+
}
165+
166+
stopwatch.reset();
167+
if (writingProgress) {
168+
// print a new line after progress dots...
169+
print('');
170+
writingProgress = false;
171+
}
172+
var message = record.message;
173+
assert(message == message.trimRight());
174+
assert(message.isNotEmpty);
175+
176+
if (record.level < logging.Level.WARNING) {
177+
if (message.endsWith('...')) {
178+
// Assume there may be more progress to print, so omit the trailing
179+
// newline
180+
writingProgress = true;
181+
stdout.write(message);
182+
} else {
183+
print(message);
184+
}
185+
} else {
186+
stderr.writeln(message);
187+
}
188+
});
189+
}
167190

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

247270
dartdoc.onCheckProgress.listen(logProgress);
248271
await Chain.capture(() async {
249-
DartDocResults results = await dartdoc.generateDocs();
250-
logInfo('Success! Docs generated into ${results.outDir.absolute.path}');
272+
await runZoned(() async {
273+
DartDocResults results = await dartdoc.generateDocs();
274+
logInfo('Success! Docs generated into ${results.outDir.absolute.path}');
275+
},
276+
zoneSpecification: new ZoneSpecification(
277+
print: (Zone self, ZoneDelegate parent, Zone zone, String line) =>
278+
logPrint(line) ));
251279
}, onError: (e, Chain chain) {
252280
if (e is DartDocFailure) {
253281
stderr.writeln('\nGeneration failed: ${e}.');
@@ -354,6 +382,10 @@ ArgParser _createArgsParser() {
354382
defaultsTo: false,
355383
hide: true,
356384
);
385+
parser.addFlag('json',
386+
help: 'Prints out progress JSON maps. One entry per line.',
387+
defaultsTo: false,
388+
negatable: true);
357389

358390
return parser;
359391
}

lib/src/logging.dart

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ final _logger = new Logger('dartdoc');
99
/// A custom [Level] for tracking file writes and verification.
1010
///
1111
/// Has a value of `501` – one more than [Level.FINE].
12-
final Level progressLevel = new Level('progress', 501);
12+
final Level progressLevel = new Level('PROGRESS', 501);
13+
14+
/// A custom [Level] for errant print statements.
15+
///
16+
/// Has a value of `1201` – one more than [Level.SHOUT].
17+
final Level printLevel = new Level('PRINT', 1201);
1318

1419
void logWarning(Object message) {
1520
_logger.log(Level.WARNING, message);
@@ -22,3 +27,18 @@ void logInfo(Object message) {
2227
void logProgress(Object message) {
2328
_logger.log(progressLevel, message);
2429
}
30+
31+
void logPrint(Object message) {
32+
_logger.log(printLevel, message);
33+
}
34+
35+
abstract class Jsonable {
36+
/// The `String` to print when in human-readable mode
37+
String get text;
38+
39+
/// The JSON content to print when in JSON-output mode.
40+
Object toJson();
41+
42+
@override
43+
String toString() => text;
44+
}

lib/src/warnings.dart

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
15
import 'package:analyzer/dart/element/element.dart';
26
import 'package:tuple/tuple.dart';
37

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

167-
final _items = <String>[];
171+
final _items = <Jsonable>[];
168172

169173
PackageWarningCounter(this.options);
170174

@@ -186,14 +190,14 @@ class PackageWarningCounter {
186190
if (options.ignoreWarnings.contains(kind)) {
187191
return;
188192
}
189-
String toWrite;
193+
String type;
190194
if (options.asErrors.contains(kind)) {
191-
toWrite = "error: ${fullMessage}";
195+
type = "error";
192196
} else if (options.asWarnings.contains(kind)) {
193-
toWrite = "warning: ${fullMessage}";
197+
type = "warning";
194198
}
195-
if (toWrite != null) {
196-
var entry = " ${toWrite}";
199+
if (type != null) {
200+
var entry = " $type: $fullMessage";
197201
if (_warningCounts[kind] == 1 &&
198202
config.verboseWarnings &&
199203
packageWarningText[kind].longHelp.isNotEmpty) {
@@ -206,7 +210,7 @@ class PackageWarningCounter {
206210
entry = '$entry$verboseOut';
207211
}
208212
assert(entry == entry.trimRight());
209-
_items.add(entry);
213+
_items.add(new _JsonWarning(type, kind, fullMessage, entry));
210214
}
211215
maybeFlush();
212216
}
@@ -256,3 +260,22 @@ class PackageWarningCounter {
256260
return [errors, warnings].join(', ');
257261
}
258262
}
263+
264+
class _JsonWarning extends Jsonable {
265+
final String type;
266+
final PackageWarning kind;
267+
final String message;
268+
269+
@override
270+
final String text;
271+
272+
_JsonWarning(this.type, this.kind, this.message, this.text);
273+
274+
@override
275+
Map<String, dynamic> toJson() => {
276+
'type': type,
277+
'kind': packageWarningText[kind].warningName,
278+
'message': message,
279+
'text': text
280+
};
281+
}

pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,4 +350,4 @@ packages:
350350
source: hosted
351351
version: "2.1.13"
352352
sdks:
353-
dart: ">=1.23.0 <=2.0.0-dev.5.0"
353+
dart: ">=1.23.0 <=2.0.0-dev.6.0"

test/compare_output_test.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,39 @@ void main() {
151151
}
152152
});
153153

154+
test('Validate JSON output', () {
155+
var args = <String>[
156+
dartdocBin,
157+
'--include',
158+
'ex',
159+
'--no-include-source',
160+
'--output',
161+
tempDir.path,
162+
'--json'
163+
];
164+
165+
var result = Process.runSync(Platform.resolvedExecutable, args,
166+
workingDirectory: _testPackagePath);
167+
168+
if (result.exitCode != 0) {
169+
print(result.exitCode);
170+
print(result.stdout);
171+
print(result.stderr);
172+
fail('dartdoc failed');
173+
}
174+
175+
var jsonValues = LineSplitter
176+
.split(result.stdout)
177+
.map((j) => JSON.decode(j) as Map<String, dynamic>)
178+
.toList();
179+
180+
expect(jsonValues, isNotEmpty,
181+
reason: 'All STDOUT lines should be JSON-encoded maps.');
182+
183+
expect(result.stderr as String, isEmpty,
184+
reason: 'STDERR should be empty.');
185+
});
186+
154187
test('--footer-text includes text', () {
155188
String footerTextPath =
156189
path.join(Directory.systemTemp.path, 'footer.txt');

0 commit comments

Comments
 (0)