OLD | NEW |
(Empty) | |
| 1 library pub.log; |
| 2 import 'dart:async'; |
| 3 import 'dart:convert'; |
| 4 import 'dart:io'; |
| 5 import 'package:path/path.dart' as p; |
| 6 import 'package:source_span/source_span.dart'; |
| 7 import 'package:stack_trace/stack_trace.dart'; |
| 8 import 'exceptions.dart'; |
| 9 import 'io.dart'; |
| 10 import 'progress.dart'; |
| 11 import 'transcript.dart'; |
| 12 import 'utils.dart'; |
| 13 final json = new _JsonLogger(); |
| 14 Verbosity verbosity = Verbosity.NORMAL; |
| 15 bool withPrejudice = false; |
| 16 const _MAX_TRANSCRIPT = 10000; |
| 17 Transcript<Entry> _transcript; |
| 18 final _progresses = new Set<Progress>(); |
| 19 Progress _animatedProgress; |
| 20 final _cyan = getSpecial('\u001b[36m'); |
| 21 final _green = getSpecial('\u001b[32m'); |
| 22 final _magenta = getSpecial('\u001b[35m'); |
| 23 final _red = getSpecial('\u001b[31m'); |
| 24 final _yellow = getSpecial('\u001b[33m'); |
| 25 final _gray = getSpecial('\u001b[1;30m'); |
| 26 final _none = getSpecial('\u001b[0m'); |
| 27 final _noColor = getSpecial('\u001b[39m'); |
| 28 final _bold = getSpecial('\u001b[1m'); |
| 29 class Level { |
| 30 static const ERROR = const Level._("ERR "); |
| 31 static const WARNING = const Level._("WARN"); |
| 32 static const MESSAGE = const Level._("MSG "); |
| 33 static const IO = const Level._("IO "); |
| 34 static const SOLVER = const Level._("SLVR"); |
| 35 static const FINE = const Level._("FINE"); |
| 36 const Level._(this.name); |
| 37 final String name; |
| 38 String toString() => name; |
| 39 } |
| 40 typedef _LogFn(Entry entry); |
| 41 class Verbosity { |
| 42 static const NONE = const Verbosity._("none", const { |
| 43 Level.ERROR: null, |
| 44 Level.WARNING: null, |
| 45 Level.MESSAGE: null, |
| 46 Level.IO: null, |
| 47 Level.SOLVER: null, |
| 48 Level.FINE: null |
| 49 }); |
| 50 static const WARNING = const Verbosity._("warning", const { |
| 51 Level.ERROR: _logToStderr, |
| 52 Level.WARNING: _logToStderr, |
| 53 Level.MESSAGE: null, |
| 54 Level.IO: null, |
| 55 Level.SOLVER: null, |
| 56 Level.FINE: null |
| 57 }); |
| 58 static const NORMAL = const Verbosity._("normal", const { |
| 59 Level.ERROR: _logToStderr, |
| 60 Level.WARNING: _logToStderr, |
| 61 Level.MESSAGE: _logToStdout, |
| 62 Level.IO: null, |
| 63 Level.SOLVER: null, |
| 64 Level.FINE: null |
| 65 }); |
| 66 static const IO = const Verbosity._("io", const { |
| 67 Level.ERROR: _logToStderrWithLabel, |
| 68 Level.WARNING: _logToStderrWithLabel, |
| 69 Level.MESSAGE: _logToStdoutWithLabel, |
| 70 Level.IO: _logToStderrWithLabel, |
| 71 Level.SOLVER: null, |
| 72 Level.FINE: null |
| 73 }); |
| 74 static const SOLVER = const Verbosity._("solver", const { |
| 75 Level.ERROR: _logToStderr, |
| 76 Level.WARNING: _logToStderr, |
| 77 Level.MESSAGE: _logToStdout, |
| 78 Level.IO: null, |
| 79 Level.SOLVER: _logToStdout, |
| 80 Level.FINE: null |
| 81 }); |
| 82 static const ALL = const Verbosity._("all", const { |
| 83 Level.ERROR: _logToStderrWithLabel, |
| 84 Level.WARNING: _logToStderrWithLabel, |
| 85 Level.MESSAGE: _logToStdoutWithLabel, |
| 86 Level.IO: _logToStderrWithLabel, |
| 87 Level.SOLVER: _logToStderrWithLabel, |
| 88 Level.FINE: _logToStderrWithLabel |
| 89 }); |
| 90 const Verbosity._(this.name, this._loggers); |
| 91 final String name; |
| 92 final Map<Level, _LogFn> _loggers; |
| 93 bool isLevelVisible(Level level) => _loggers[level] != null; |
| 94 String toString() => name; |
| 95 } |
| 96 class Entry { |
| 97 final Level level; |
| 98 final List<String> lines; |
| 99 Entry(this.level, this.lines); |
| 100 } |
| 101 void error(message, [error]) { |
| 102 if (error != null) { |
| 103 message = "$message: $error"; |
| 104 var trace; |
| 105 if (error is Error) trace = error.stackTrace; |
| 106 if (trace != null) { |
| 107 message = "$message\nStackTrace: $trace"; |
| 108 } |
| 109 } |
| 110 write(Level.ERROR, message); |
| 111 } |
| 112 void warning(message) => write(Level.WARNING, message); |
| 113 void message(message) => write(Level.MESSAGE, message); |
| 114 void io(message) => write(Level.IO, message); |
| 115 void solver(message) => write(Level.SOLVER, message); |
| 116 void fine(message) => write(Level.FINE, message); |
| 117 void write(Level level, message) { |
| 118 message = message.toString(); |
| 119 var lines = splitLines(message); |
| 120 if (lines.isNotEmpty && lines.last == "") { |
| 121 lines.removeLast(); |
| 122 } |
| 123 var entry = new Entry(level, lines.map(format).toList()); |
| 124 var logFn = verbosity._loggers[level]; |
| 125 if (logFn != null) logFn(entry); |
| 126 if (_transcript != null) _transcript.add(entry); |
| 127 } |
| 128 final _capitalizedAnsiEscape = new RegExp(r'\u001b\[\d+(;\d+)?M'); |
| 129 String format(String string) { |
| 130 if (!withPrejudice) return string; |
| 131 string = string.toUpperCase().replaceAllMapped( |
| 132 _capitalizedAnsiEscape, |
| 133 (match) => match[0].toLowerCase()); |
| 134 return "$_bold$string$_none"; |
| 135 } |
| 136 Future ioAsync(String startMessage, Future operation, [String |
| 137 endMessage(value)]) { |
| 138 if (endMessage == null) { |
| 139 io("Begin $startMessage."); |
| 140 } else { |
| 141 io(startMessage); |
| 142 } |
| 143 return operation.then((result) { |
| 144 if (endMessage == null) { |
| 145 io("End $startMessage."); |
| 146 } else { |
| 147 io(endMessage(result)); |
| 148 } |
| 149 return result; |
| 150 }); |
| 151 } |
| 152 void process(String executable, List<String> arguments, String workingDirectory) |
| 153 { |
| 154 io( |
| 155 "Spawning \"$executable ${arguments.join(' ')}\" in " |
| 156 "${p.absolute(workingDirectory)}"); |
| 157 } |
| 158 void processResult(String executable, PubProcessResult result) { |
| 159 var buffer = new StringBuffer(); |
| 160 buffer.writeln("Finished $executable. Exit code ${result.exitCode}."); |
| 161 dumpOutput(String name, List<String> output) { |
| 162 if (output.length == 0) { |
| 163 buffer.writeln("Nothing output on $name."); |
| 164 } else { |
| 165 buffer.writeln("$name:"); |
| 166 var numLines = 0; |
| 167 for (var line in output) { |
| 168 if (++numLines > 1000) { |
| 169 buffer.writeln( |
| 170 '[${output.length - 1000}] more lines of output ' 'truncated...]')
; |
| 171 break; |
| 172 } |
| 173 buffer.writeln("| $line"); |
| 174 } |
| 175 } |
| 176 } |
| 177 dumpOutput("stdout", result.stdout); |
| 178 dumpOutput("stderr", result.stderr); |
| 179 io(buffer.toString().trim()); |
| 180 } |
| 181 void exception(exception, [StackTrace trace]) { |
| 182 if (exception is SilentException) return; |
| 183 var chain = trace == null ? new Chain.current() : new Chain.forTrace(trace); |
| 184 if (exception is SourceSpanException) { |
| 185 error(exception.toString(color: canUseSpecialChars)); |
| 186 } else { |
| 187 error(getErrorMessage(exception)); |
| 188 } |
| 189 fine("Exception type: ${exception.runtimeType}"); |
| 190 if (json.enabled) { |
| 191 if (exception is UsageException) { |
| 192 json.error(exception.message); |
| 193 } else { |
| 194 json.error(exception); |
| 195 } |
| 196 } |
| 197 if (!isUserFacingException(exception)) { |
| 198 error(chain.terse); |
| 199 } else { |
| 200 fine(chain.terse); |
| 201 } |
| 202 if (exception is WrappedException && exception.innerError != null) { |
| 203 var message = "Wrapped exception: ${exception.innerError}"; |
| 204 if (exception.innerChain != null) { |
| 205 message = "$message\n${exception.innerChain}"; |
| 206 } |
| 207 fine(message); |
| 208 } |
| 209 } |
| 210 void recordTranscript() { |
| 211 _transcript = new Transcript<Entry>(_MAX_TRANSCRIPT); |
| 212 } |
| 213 void dumpTranscript() { |
| 214 if (_transcript == null) return; |
| 215 stderr.writeln('---- Log transcript ----'); |
| 216 _transcript.forEach((entry) { |
| 217 _printToStream(stderr, entry, showLabel: true); |
| 218 }, (discarded) { |
| 219 stderr.writeln('---- ($discarded discarded) ----'); |
| 220 }); |
| 221 stderr.writeln('---- End log transcript ----'); |
| 222 } |
| 223 Future progress(String message, Future callback(), {bool fine: false}) { |
| 224 _stopProgress(); |
| 225 var progress = new Progress(message, fine: fine); |
| 226 _animatedProgress = progress; |
| 227 _progresses.add(progress); |
| 228 return callback().whenComplete(() { |
| 229 progress.stop(); |
| 230 _progresses.remove(progress); |
| 231 }); |
| 232 } |
| 233 void _stopProgress() { |
| 234 if (_animatedProgress != null) _animatedProgress.stopAnimating(); |
| 235 _animatedProgress = null; |
| 236 } |
| 237 String bold(text) => withPrejudice ? text : "$_bold$text$_none"; |
| 238 String gray(text) => |
| 239 withPrejudice ? "$_gray$text$_noColor" : "$_gray$text$_none"; |
| 240 String cyan(text) => "$_cyan$text$_noColor"; |
| 241 String green(text) => "$_green$text$_noColor"; |
| 242 String magenta(text) => "$_magenta$text$_noColor"; |
| 243 String red(text) => "$_red$text$_noColor"; |
| 244 String yellow(text) => "$_yellow$text$_noColor"; |
| 245 void _logToStdout(Entry entry) { |
| 246 _logToStream(stdout, entry, showLabel: false); |
| 247 } |
| 248 void _logToStdoutWithLabel(Entry entry) { |
| 249 _logToStream(stdout, entry, showLabel: true); |
| 250 } |
| 251 void _logToStderr(Entry entry) { |
| 252 _logToStream(stderr, entry, showLabel: false); |
| 253 } |
| 254 void _logToStderrWithLabel(Entry entry) { |
| 255 _logToStream(stderr, entry, showLabel: true); |
| 256 } |
| 257 void _logToStream(IOSink sink, Entry entry, {bool showLabel}) { |
| 258 if (json.enabled) return; |
| 259 _printToStream(sink, entry, showLabel: showLabel); |
| 260 } |
| 261 void _printToStream(IOSink sink, Entry entry, {bool showLabel}) { |
| 262 _stopProgress(); |
| 263 bool firstLine = true; |
| 264 for (var line in entry.lines) { |
| 265 if (showLabel) { |
| 266 if (firstLine) { |
| 267 sink.write('${entry.level.name}: '); |
| 268 } else { |
| 269 sink.write(' | '); |
| 270 } |
| 271 } |
| 272 sink.writeln(line); |
| 273 firstLine = false; |
| 274 } |
| 275 } |
| 276 class _JsonLogger { |
| 277 bool enabled = false; |
| 278 void error(error, [stackTrace]) { |
| 279 var errorJson = { |
| 280 "error": error.toString() |
| 281 }; |
| 282 if (stackTrace == null && error is Error) stackTrace = error.stackTrace; |
| 283 if (stackTrace != null) { |
| 284 errorJson["stackTrace"] = new Chain.forTrace(stackTrace).toString(); |
| 285 } |
| 286 if (error is SourceSpanException && error.span.sourceUrl != null) { |
| 287 errorJson["path"] = p.fromUri(error.span.sourceUrl); |
| 288 } |
| 289 if (error is FileException) { |
| 290 errorJson["path"] = error.path; |
| 291 } |
| 292 this.message(errorJson); |
| 293 } |
| 294 void message(message) { |
| 295 if (!enabled) return; |
| 296 print(JSON.encode(message)); |
| 297 } |
| 298 } |
OLD | NEW |