OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, 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 | |
5 library code_transformers.messages.messages_logger; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:convert' show JSON; | |
9 | |
10 import 'package:barback/barback.dart'; | |
11 import 'package:source_span/source_span.dart'; | |
12 | |
13 import 'messages.dart' show Message, MessageId, BuildLogEntry, LogEntryTable; | |
14 | |
15 /// A [TransformLogger] used to track error and warning messages produced during | |
16 /// a build. | |
17 /// | |
18 /// This logger records all messages that were logged and then forwards | |
19 /// the calls to an underlying [TransformLogger]. The internal records support | |
20 /// serializing the errors and emiting them to an asset (so they can be | |
21 /// presented to the user in a web-based client), clustering similar messages | |
22 /// together, sorting messages in order of importance, etc. | |
23 /// | |
24 /// The logger also supports reporting error messages as warnings. Barback makes | |
25 /// error messages stop the transformation process, which sometimes can surprise | |
26 /// users. Turning errors into warnings is especially useful when used within | |
27 /// `pub serve`, where we would like the transformation to continue as far as it | |
28 /// can. When this flag is turned on, the level is still recorded as an error, | |
29 /// so a web client UI can still highlight their importance. | |
30 // TODO(sigmund): also cluster messages when they are reported on the | |
31 // command-line. | |
32 class BuildLogger implements TransformLogger { | |
33 /// Underling transform that is currently active. This can be either an | |
34 /// [AggregateTransform] or [Transform]. | |
35 final _transform; | |
36 | |
37 /// The primary input id. | |
38 final AssetId _primaryId; | |
39 | |
40 /// Logs created during the current transform. | |
41 final LogEntryTable _logs = new LogEntryTable(); | |
42 | |
43 /// Whether to use `warning` or `error` when forwarding error messages to the | |
44 /// underlying logger in `_transform.logger`. | |
45 final bool convertErrorsToWarnings; | |
46 | |
47 /// Uri prefix to link for additional details. If set, messages logged through | |
48 /// this logger will contain an additional sentence, telling users to find | |
49 /// more details at `$detailsUri#packagename_id`. | |
50 final String detailsUri; | |
51 | |
52 /// If transform is a [Transform] then [primaryId] will default to the | |
53 /// primaryInput.id, if it is an [AggregateTransform] then you must pass in | |
54 /// a [primaryId] to be used, otherwise you will get a runtime error. | |
55 BuildLogger(transform, | |
56 {this.convertErrorsToWarnings: false, AssetId primaryId, this.detailsUri}) | |
57 : _transform = transform, | |
58 _primaryId = primaryId != null ? primaryId : transform.primaryInput.id; | |
59 | |
60 /// Records a message at the fine level. If [msg] is a [Message] it is | |
61 /// recorded directly, otherwise it is first converted to a [String]. | |
62 void fine(msg, {AssetId asset, SourceSpan span}) { | |
63 msg = msg is Message ? msg : new Message.unknown('$msg'); | |
64 _transform.logger.fine(_snippet(msg), asset: asset, span: span); | |
65 _logs.add(new BuildLogEntry(msg, span, LogLevel.FINE.name)); | |
66 } | |
67 | |
68 /// Records a message at the info level. If [msg] is a [Message] it is | |
69 /// recorded directly, otherwise it is first converted to a [String]. | |
70 void info(msg, {AssetId asset, SourceSpan span}) { | |
71 msg = msg is Message ? msg : new Message.unknown('$msg'); | |
72 _transform.logger.info(_snippet(msg), asset: asset, span: span); | |
73 _logs.add(new BuildLogEntry(msg, span, LogLevel.INFO.name)); | |
74 } | |
75 | |
76 /// Records a warning message. If [msg] is a [Message] it is recorded | |
77 /// directly, otherwise it is first converted to a [String]. | |
78 void warning(msg, {AssetId asset, SourceSpan span}) { | |
79 msg = msg is Message ? msg : new Message.unknown('$msg'); | |
80 _transform.logger.warning(_snippet(msg), asset: asset, span: span); | |
81 _logs.add(new BuildLogEntry(msg, span, LogLevel.WARNING.name)); | |
82 } | |
83 | |
84 /// Records an error message. If [msg] is a [Message] it is recorded | |
85 /// directly, otherwise it is first converted to a [String]. | |
86 void error(msg, {AssetId asset, SourceSpan span}) { | |
87 msg = msg is Message ? msg : new Message.unknown('$msg'); | |
88 if (convertErrorsToWarnings) { | |
89 _transform.logger.warning(_snippet(msg), asset: asset, span: span); | |
90 } else { | |
91 _transform.logger.error(_snippet(msg), asset: asset, span: span); | |
92 } | |
93 _logs.add(new BuildLogEntry(msg, span, LogLevel.ERROR.name)); | |
94 } | |
95 | |
96 String _snippet(Message msg) { | |
97 var s = msg.snippet; | |
98 if (detailsUri == null) return s; | |
99 var dot = s.endsWith('.') || s.endsWith('!') || s.endsWith('?') ? '' : '.'; | |
100 var hashTag = '${msg.id.package}_${msg.id.id}'; | |
101 return '$s$dot See $detailsUri#$hashTag for details.'; | |
102 } | |
103 | |
104 /// Outputs the log data to a JSON serialized file. | |
105 Future writeOutput() { | |
106 return _getNextLogAssetId().then((id) { | |
107 _transform.addOutput(new Asset.fromString(id, JSON.encode(_logs))); | |
108 }); | |
109 } | |
110 | |
111 // Each phase outputs a new log file with an incrementing # appended, this | |
112 // figures out the next # to use. | |
113 Future<AssetId> _getNextLogAssetId([int nextNumber = 1]) { | |
114 var nextAssetPath = _primaryId.addExtension('${LOG_EXTENSION}.$nextNumber'); | |
115 return _transform.hasInput(nextAssetPath).then((exists) { | |
116 if (!exists) return nextAssetPath; | |
117 return _getNextLogAssetId(++nextNumber); | |
118 }); | |
119 } | |
120 | |
121 // Reads all log files for an Asset into [logs]. | |
122 static Future _readLogFilesForAsset( | |
123 AssetId id, Transform transform, LogEntryTable entries, | |
124 [nextNumber = 1]) { | |
125 var nextAssetPath = id.addExtension('${LOG_EXTENSION}.$nextNumber'); | |
126 return transform.hasInput(nextAssetPath).then((exists) { | |
127 if (!exists) return null; | |
128 return transform.readInputAsString(nextAssetPath).then((data) { | |
129 entries.addAll(new LogEntryTable.fromJson(JSON.decode(data))); | |
130 return _readLogFilesForAsset(id, transform, entries, ++nextNumber); | |
131 }); | |
132 }); | |
133 } | |
134 | |
135 /// Combines all existing ._buildLogs.* files into a single ._buildLogs file. | |
136 /// [transform] may be a [Transform] or [AggregateTransform]. If an | |
137 /// [AggregateTransform] is passed then [primaryId] must also be passed. | |
138 static Future combineLogFiles(transform, [AssetId primaryId]) { | |
139 if (primaryId == null) primaryId = transform.primaryInput.id; | |
140 var entries = new LogEntryTable(); | |
141 return _readLogFilesForAsset(primaryId, transform, entries).then((_) { | |
142 return transform.addOutput(new Asset.fromString( | |
143 primaryId.addExtension(LOG_EXTENSION), | |
144 JSON.encode(entries.toJson()))); | |
145 }); | |
146 } | |
147 | |
148 // Reads all logs for an asset and adds them to this loggers log output. | |
149 Future addLogFilesFromAsset(AssetId id, [int nextNumber = 1]) { | |
150 return _readLogFilesForAsset(id, _transform, _logs); | |
151 } | |
152 } | |
153 | |
154 /// Extension used for assets that contained serialized logs. | |
155 const String LOG_EXTENSION = '._buildLogs'; | |
OLD | NEW |