2 // ConsoleLogger.cs: Outputs to the console
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Runtime.InteropServices;
32 using System.Collections;
33 using System.Collections.Generic;
36 using System.Security;
38 using Microsoft.Build.Framework;
40 namespace Microsoft.Build.BuildEngine {
41 public class ConsoleLogger : ILogger {
45 LoggerVerbosity verbosity;
46 WriteHandler writeHandler;
50 bool performanceSummary;
52 bool skipProjectStartedText;
53 List<string> errors, warnings;
55 ConsoleColor errorColor, warningColor, eventColor, messageColor, highMessageColor;
57 ColorResetter colorReset;
58 bool no_message_color, use_colors;
59 bool noItemAndPropertyList;
61 List<BuildStatusEventArgs> events;
62 Dictionary<string, List<string>> errorsTable;
63 Dictionary<string, List<string>> warningsTable;
64 SortedDictionary<string, PerfInfo> targetPerfTable, tasksPerfTable;
65 string current_events_string;
67 public ConsoleLogger ()
68 : this (LoggerVerbosity.Normal, null, null, null)
72 public ConsoleLogger (LoggerVerbosity verbosity)
73 : this (LoggerVerbosity.Normal, null, null, null)
77 public ConsoleLogger (LoggerVerbosity verbosity,
80 ColorResetter colorReset)
82 this.verbosity = verbosity;
85 this.warningCount = 0;
87 this.writeHandler += new WriteHandler (WriteHandlerFunction);
89 this.writeHandler += write;
90 this.performanceSummary = false;
91 this.showSummary = true;
92 this.skipProjectStartedText = false;
93 errors = new List<string> ();
94 warnings = new List<string> ();
95 this.colorSet = colorSet;
96 this.colorReset = colorReset;
98 events = new List<BuildStatusEventArgs> ();
99 errorsTable = new Dictionary<string, List<string>> ();
100 warningsTable = new Dictionary<string, List<string>> ();
101 targetPerfTable = new SortedDictionary<string, PerfInfo> ();
102 tasksPerfTable = new SortedDictionary<string, PerfInfo> ();
105 errorColor = ConsoleColor.DarkRed;
106 warningColor = ConsoleColor.DarkYellow;
107 eventColor = ConsoleColor.DarkCyan;
108 messageColor = ConsoleColor.DarkGray;
109 highMessageColor = ConsoleColor.White;
111 // if message color is not set via the env var,
112 // then don't use any color for it.
113 no_message_color = true;
116 if (colorSet == null || colorReset == null)
120 string config = Environment.GetEnvironmentVariable ("XBUILD_COLORS");
121 if (config == null) {
126 if (config == "disable")
130 string [] pairs = config.Split (new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
131 foreach (string pair in pairs) {
132 string [] parts = pair.Split (new char[] {'='}, StringSplitOptions.RemoveEmptyEntries);
133 if (parts.Length != 2)
136 if (parts [0] == "errors")
137 TryParseConsoleColor (parts [1], ref errorColor);
138 else if (parts [0] == "warnings")
139 TryParseConsoleColor (parts [1], ref warningColor);
140 else if (parts [0] == "events")
141 TryParseConsoleColor (parts [1], ref eventColor);
142 else if (parts [0] == "messages") {
143 if (TryParseConsoleColor (parts [1], ref messageColor)) {
144 highMessageColor = GetBrightColorFor (messageColor);
145 no_message_color = false;
151 bool TryParseConsoleColor (string color_str, ref ConsoleColor color)
153 switch (color_str.ToLower ()) {
154 case "black": color = ConsoleColor.Black; break;
156 case "blue": color = ConsoleColor.DarkBlue; break;
157 case "green": color = ConsoleColor.DarkGreen; break;
158 case "cyan": color = ConsoleColor.DarkCyan; break;
159 case "red": color = ConsoleColor.DarkRed; break;
160 case "magenta": color = ConsoleColor.DarkMagenta; break;
161 case "yellow": color = ConsoleColor.DarkYellow; break;
162 case "grey": color = ConsoleColor.DarkGray; break;
164 case "brightgrey": color = ConsoleColor.Gray; break;
165 case "brightblue": color = ConsoleColor.Blue; break;
166 case "brightgreen": color = ConsoleColor.Green; break;
167 case "brightcyan": color = ConsoleColor.Cyan; break;
168 case "brightred": color = ConsoleColor.Red; break;
169 case "brightmagenta": color = ConsoleColor.Magenta; break;
170 case "brightyellow": color = ConsoleColor.Yellow; break;
173 case "brightwhite": color = ConsoleColor.White; break;
174 default: return false;
180 ConsoleColor GetBrightColorFor (ConsoleColor color)
183 case ConsoleColor.DarkBlue: return ConsoleColor.Blue;
184 case ConsoleColor.DarkGreen: return ConsoleColor.Green;
185 case ConsoleColor.DarkCyan: return ConsoleColor.Cyan;
186 case ConsoleColor.DarkRed: return ConsoleColor.Red;
187 case ConsoleColor.DarkMagenta: return ConsoleColor.Magenta;
188 case ConsoleColor.DarkYellow: return ConsoleColor.Yellow;
189 case ConsoleColor.DarkGray: return ConsoleColor.Gray;
190 case ConsoleColor.Gray: return ConsoleColor.White;
192 default: return color;
196 public void ApplyParameter (string parameterName,
197 string parameterValue)
199 // FIXME: what we should do here? in msbuild it isn't
200 // changing "parameters" property
203 public virtual void Initialize (IEventSource eventSource)
205 eventSource.BuildStarted += new BuildStartedEventHandler (BuildStartedHandler);
206 eventSource.BuildFinished += new BuildFinishedEventHandler (BuildFinishedHandler);
207 eventSource.ProjectStarted += new ProjectStartedEventHandler (ProjectStartedHandler);
208 eventSource.ProjectFinished += new ProjectFinishedEventHandler (ProjectFinishedHandler);
209 eventSource.TargetStarted += new TargetStartedEventHandler (TargetStartedHandler);
210 eventSource.TargetFinished += new TargetFinishedEventHandler (TargetFinishedHandler);
211 eventSource.TaskStarted += new TaskStartedEventHandler (TaskStartedHandler);
212 eventSource.TaskFinished += new TaskFinishedEventHandler (TaskFinishedHandler);
213 eventSource.MessageRaised += new BuildMessageEventHandler (MessageHandler);
214 eventSource.WarningRaised += new BuildWarningEventHandler (WarningHandler);
215 eventSource.ErrorRaised += new BuildErrorEventHandler (ErrorHandler);
218 public void BuildStartedHandler (object sender, BuildStartedEventArgs args)
220 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
221 WriteLine (String.Empty);
222 WriteLine (String.Format ("Build started {0}.", args.Timestamp));
223 WriteLine ("__________________________________________________");
225 buildStart = args.Timestamp;
230 public void BuildFinishedHandler (object sender, BuildFinishedEventArgs args)
232 if (!IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
237 TimeSpan timeElapsed = args.Timestamp - buildStart;
238 if (performanceSummary || verbosity == LoggerVerbosity.Diagnostic)
239 DumpPerformanceSummary ();
241 if (args.Succeeded == true && !projectFailed) {
242 WriteLine ("Build succeeded.");
244 WriteLine ("Build FAILED.");
246 if (warnings.Count > 0) {
247 WriteLine (Environment.NewLine + "Warnings:");
248 SetColor (warningColor);
250 WriteLine (String.Empty);
251 foreach (KeyValuePair<string, List<string>> pair in warningsTable) {
252 if (!String.IsNullOrEmpty (pair.Key))
253 WriteLine (pair.Key);
255 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
256 foreach (string msg in pair.Value)
257 WriteLine (String.Format ("{0}{1}", indent_str, msg));
259 WriteLine (String.Empty);
265 if (errors.Count > 0) {
266 WriteLine ("Errors:");
267 SetColor (errorColor);
269 WriteLine (String.Empty);
270 foreach (KeyValuePair<string, List<string>> pair in errorsTable) {
271 if (!String.IsNullOrEmpty (pair.Key))
272 WriteLine (pair.Key);
274 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
275 foreach (string msg in pair.Value)
276 WriteLine (String.Format ("{0}{1}", indent_str, msg));
278 WriteLine (String.Empty);
283 if (showSummary == true){
284 WriteLine (String.Format ("\t {0} Warning(s)", warningCount));
285 WriteLine (String.Format ("\t {0} Error(s)", errorCount));
286 WriteLine (String.Empty);
287 WriteLine (String.Format ("Time Elapsed {0}", timeElapsed));
292 public void ProjectStartedHandler (object sender, ProjectStartedEventArgs args)
294 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
295 SetColor (eventColor);
296 WriteLine (String.Format ("Project \"{0}\" ({1} target(s)):", args.ProjectFile,
297 String.IsNullOrEmpty (args.TargetNames) ? "default" : args.TargetNames));
299 WriteLine (String.Empty);
300 DumpProperties (args.Properties);
301 DumpItems (args.Items);
306 public void ProjectFinishedHandler (object sender, ProjectFinishedEventArgs args)
308 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
311 SetColor (eventColor);
312 WriteLine (String.Format ("Done building project \"{0}\".{1}", args.ProjectFile,
313 args.Succeeded ? String.Empty : "-- FAILED"));
315 WriteLine (String.Empty);
318 // no project has failed yet, so update the flag
319 projectFailed = !args.Succeeded;
324 public void TargetStartedHandler (object sender, TargetStartedEventArgs args)
326 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
328 SetColor (eventColor);
329 WriteLine (String.Format ("Target {0}:",args.TargetName));
335 public void TargetFinishedHandler (object sender, TargetFinishedEventArgs args)
337 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) ||
338 (!args.Succeeded && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal))) {
339 SetColor (eventColor);
340 WriteLine (String.Format ("Done building target \"{0}\" in project \"{1}\".{2}",
341 args.TargetName, args.ProjectFile,
342 args.Succeeded ? String.Empty : "-- FAILED"));
344 WriteLine (String.Empty);
351 public void TaskStartedHandler (object sender, TaskStartedEventArgs args)
353 if (this.verbosity == LoggerVerbosity.Detailed) {
354 SetColor (eventColor);
355 WriteLine (String.Format ("Task \"{0}\"",args.TaskName));
362 public void TaskFinishedHandler (object sender, TaskFinishedEventArgs args)
365 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) ||
366 (!args.Succeeded && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal))) {
367 SetColor (eventColor);
369 WriteLine (String.Format ("Done executing task \"{0}\"", args.TaskName));
371 WriteLine (String.Format ("Task \"{0}\" execution -- FAILED", args.TaskName));
377 public void MessageHandler (object sender, BuildMessageEventArgs args)
379 if (IsMessageOk (args)) {
380 if (no_message_color) {
381 WriteLine (args.Message);
383 SetColor (args.Importance == MessageImportance.High ? highMessageColor : messageColor);
384 WriteLine (args.Message);
390 public void WarningHandler (object sender, BuildWarningEventArgs args)
392 string msg = FormatWarningEvent (args);
393 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Quiet)) {
394 SetColor (warningColor);
395 WriteLineWithoutIndent (msg);
400 List<string> list = null;
401 if (!warningsTable.TryGetValue (EventsAsString, out list))
402 warningsTable [EventsAsString] = list = new List<string> ();
408 public void ErrorHandler (object sender, BuildErrorEventArgs args)
410 string msg = FormatErrorEvent (args);
411 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Quiet)) {
412 SetColor (errorColor);
413 WriteLineWithoutIndent (msg);
418 List<string> list = null;
419 if (!errorsTable.TryGetValue (EventsAsString, out list))
420 errorsTable [EventsAsString] = list = new List<string> ();
426 public void CustomEventHandler (object sender, CustomBuildEventArgs args)
430 private void WriteLine (string message)
433 StringBuilder sb = new StringBuilder ();
434 for (int i = 0; i < indent; i++)
438 writeHandler (sb.ToString ());
440 writeHandler (message);
444 void PushEvent (BuildStatusEventArgs args)
447 current_events_string = null;
452 if (performanceSummary || verbosity == LoggerVerbosity.Diagnostic) {
453 var args = events [events.Count - 1];
454 TargetStartedEventArgs tgt_args = args as TargetStartedEventArgs;
455 if (tgt_args != null) {
456 AddPerfInfo (tgt_args.TargetName, args.Timestamp, targetPerfTable);
458 TaskStartedEventArgs tsk_args = args as TaskStartedEventArgs;
459 if (tsk_args != null)
460 AddPerfInfo (tsk_args.TaskName, args.Timestamp, tasksPerfTable);
464 events.RemoveAt (events.Count - 1);
465 current_events_string = null;
468 string EventsToString ()
470 StringBuilder sb = new StringBuilder ();
472 string last_imported_target_file = String.Empty;
473 for (int i = 0; i < events.Count; i ++) {
474 var args = events [i];
475 ProjectStartedEventArgs pargs = args as ProjectStartedEventArgs;
477 sb.AppendFormat ("{0} ({1}) ->\n", pargs.ProjectFile,
478 String.IsNullOrEmpty (pargs.TargetNames) ?
481 last_imported_target_file = String.Empty;
485 TargetStartedEventArgs targs = args as TargetStartedEventArgs;
487 if (targs.TargetFile != targs.ProjectFile && targs.TargetFile != last_imported_target_file)
488 // target from an imported file,
489 // and it hasn't been mentioned as yet
490 sb.AppendFormat ("{0} ", targs.TargetFile);
492 last_imported_target_file = targs.TargetFile;
493 sb.AppendFormat ("({0} target) ->\n", targs.TargetName);
497 return sb.ToString ();
500 void AddPerfInfo (string name, DateTime start, IDictionary<string, PerfInfo> perf_table)
503 if (!perf_table.TryGetValue (name, out pi)) {
504 pi = new PerfInfo ();
505 perf_table [name] = pi;
508 pi.Time += DateTime.Now - start;
512 void DumpPerformanceSummary ()
514 SetColor (eventColor);
515 WriteLine ("Target perfomance summary:");
518 foreach (var pi in targetPerfTable.OrderBy (pair => pair.Value.Time))
519 WriteLine (String.Format ("{0,10:0.000} ms {1,-50} {2,5} calls", pi.Value.Time.TotalMilliseconds, pi.Key, pi.Value.NumberOfCalls));
521 WriteLine (String.Empty);
523 SetColor (eventColor);
524 WriteLine ("Tasks perfomance summary:");
527 foreach (var pi in tasksPerfTable.OrderBy (pair => pair.Value.Time))
528 WriteLine (String.Format ("{0,10:0.000} ms {1,-50} {2,5} calls", pi.Value.Time.TotalMilliseconds, pi.Key, pi.Value.NumberOfCalls));
530 WriteLine (String.Empty);
533 private void WriteLineWithoutIndent (string message)
535 writeHandler (message);
538 private void WriteHandlerFunction (string message)
540 Console.WriteLine (message);
543 void SetColor (ConsoleColor color)
555 private void ParseParameters ()
557 string[] splittedParameters = parameters.Split (';');
558 foreach (string s in splittedParameters ) {
560 case "PerformanceSummary":
561 this.performanceSummary = true;
564 this.showSummary = false;
566 case "NoItemAndPropertyList":
567 this.noItemAndPropertyList = true;
570 throw new ArgumentException ("Invalid parameter : " + s);
575 public virtual void Shutdown ()
579 static bool InEmacs = Environment.GetEnvironmentVariable ("EMACS") == "t";
581 private string FormatErrorEvent (BuildErrorEventArgs args)
583 // For some reason we get an 1-char empty string as Subcategory somtimes.
584 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
585 string subcat = subprefix == "" ? "" : args.Subcategory;
587 if (args.LineNumber != 0){
588 if (args.ColumnNumber != 0 && !InEmacs)
589 return String.Format ("{0}({1},{2}): {3}{4}error {5}: {6}",
590 args.File, args.LineNumber, args.ColumnNumber,
591 subprefix, subcat, args.Code, args.Message);
593 return String.Format ("{0}({1}): {2}{3}error {4}: {5}",
594 args.File, args.LineNumber,
595 subprefix, subcat, args.Code, args.Message);
597 return String.Format ("{0}: {1}{2}error {3}: {4}", args.File, subprefix, subcat, args.Code,
602 private string FormatWarningEvent (BuildWarningEventArgs args)
604 // For some reason we get an 1-char empty string as Subcategory somtimes.
605 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
606 string subcat = subprefix == "" ? "" : args.Subcategory;
608 // FIXME: show more complicated args
609 if (args.LineNumber != 0){
611 if (args.ColumnNumber != 0 && !InEmacs) {
612 return String.Format ("{0}({1},{2}): {3}{4}warning {5}: {6}",
613 args.File, args.LineNumber, args.ColumnNumber,
614 subprefix, subcat, args.Code, args.Message);
616 return String.Format ("{0}({1}): {2}{3}warning {4}: {5}",
617 args.File, args.LineNumber,
618 subprefix, subcat, args.Code, args.Message);
620 return String.Format ("{0}: {1} warning {2}: {3}", args.File, args.Subcategory, args.Code,
625 private bool IsMessageOk (BuildMessageEventArgs bsea)
627 if (bsea.Importance == MessageImportance.High && IsVerbosityGreaterOrEqual (LoggerVerbosity.Minimal)) {
629 } else if (bsea.Importance == MessageImportance.Normal && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
631 } else if (bsea.Importance == MessageImportance.Low && IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed)) {
637 private bool IsVerbosityGreaterOrEqual (LoggerVerbosity v)
639 if (v == LoggerVerbosity.Diagnostic) {
640 return LoggerVerbosity.Diagnostic <= verbosity;
641 } else if (v == LoggerVerbosity.Detailed) {
642 return LoggerVerbosity.Detailed <= verbosity;
643 } else if (v == LoggerVerbosity.Normal) {
644 return LoggerVerbosity.Normal <= verbosity;
645 } else if (v == LoggerVerbosity.Minimal) {
646 return LoggerVerbosity.Minimal <= verbosity;
647 } else if (v == LoggerVerbosity.Quiet) {
653 void DumpProperties (IEnumerable properties)
655 if (noItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic))
658 SetColor (eventColor);
660 WriteLine ("Initial Properties:");
663 if (properties == null)
666 var dict = new SortedDictionary<string, string> ();
667 foreach (DictionaryEntry de in properties)
668 dict [(string)de.Key] = (string)de.Value;
670 foreach (KeyValuePair<string, string> pair in dict)
671 WriteLine (String.Format ("{0} = {1}", pair.Key, pair.Value));
675 void DumpItems (IEnumerable items)
677 if (noItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic) || items == null)
680 SetColor (eventColor);
682 WriteLine ("Initial Items:");
687 var items_table = new SortedDictionary<string, List<ITaskItem>> ();
688 foreach (DictionaryEntry de in items) {
689 string key = (string)de.Key;
690 if (!items_table.ContainsKey (key))
691 items_table [key] = new List<ITaskItem> ();
693 items_table [key].Add ((ITaskItem) de.Value);
696 foreach (string name in items_table.Keys) {
699 foreach (ITaskItem item in items_table [name])
700 WriteLine (item.ItemSpec);
706 public string Parameters {
712 throw new ArgumentNullException ();
714 if (parameters != String.Empty)
719 string EventsAsString {
721 if (current_events_string == null)
722 current_events_string = EventsToString ();
723 return current_events_string;
727 public bool ShowSummary {
728 get { return showSummary; }
729 set { showSummary = value; }
732 public bool SkipProjectStartedText {
733 get { return skipProjectStartedText; }
734 set { skipProjectStartedText = value; }
737 public LoggerVerbosity Verbosity {
738 get { return verbosity; }
739 set { verbosity = value; }
742 protected WriteHandler WriteHandler {
743 get { return writeHandler; }
744 set { writeHandler = value; }
749 public TimeSpan Time;
750 public int NumberOfCalls;