/// <inheritdoc /> protected override void Output(EventLevel level, int id, string eventName, EventKeywords eventKeywords, string text, bool doNotTranslatePaths = false) { m_writer.WriteLine(level, (m_translator != null && !doNotTranslatePaths) ? m_translator.Translate(text) : text); // At the expense of performance, flush Critical and Error events to the underlying file as they are written // so the log can be viewed immediately. if (level == EventLevel.Critical || level == EventLevel.Error) { SynchronizedFlush(); } }
private void WriteOutputLine(MessageLevel messageLevel, string line, bool overwritable) { TextWriter writer; ConsoleColor color; if (m_pathTranslator != null) { line = m_pathTranslator.Translate(line); } switch (messageLevel) { case MessageLevel.Info: writer = Console.Out; color = m_defaultForegroundColor; break; case MessageLevel.Warning: writer = Console.Out; color = m_warningColor; break; case MessageLevel.Error: writer = Console.Error; color = m_errorColor; break; default: Contract.Assert(messageLevel == MessageLevel.ErrorNoColor); writer = Console.Error; color = m_defaultForegroundColor; break; } lock (m_lock) { if (m_isDisposed) { return; } if (m_taskbar != null && m_highestMessageLevel < messageLevel) { m_highestMessageLevel = messageLevel; switch (m_highestMessageLevel) { case MessageLevel.Info: m_taskbar.SetTaskbarState(TaskbarInterop.TaskbarProgressStates.Normal); break; case MessageLevel.Warning: m_taskbar.SetTaskbarState(TaskbarInterop.TaskbarProgressStates.Paused); break; default: Contract.Assert(m_highestMessageLevel == MessageLevel.Error || m_highestMessageLevel == MessageLevel.ErrorNoColor); m_taskbar.SetTaskbarState(TaskbarInterop.TaskbarProgressStates.Error); break; } } try { if (color != m_defaultForegroundColor) { Console.ForegroundColor = color; } // Details on handling overwriteable lines: // Let's let X represent the cursor's position and - represent blank characters. // // The general principle is that overwriteable lines reset the position of the cursor to where it // was before that line was written. // This is the state after writing a string with a newline in it. Notice the first line has // already wrapped because it was wider than the console // _______________________________ // |X ThisLineIsPrettyLongAndMayWr| // |apAroundToTheNextLine---------| // |MoreText----------------------| // _______________________________ // // Then before a subsequent message is written, we compute a blank string long enough to overwrite all of // the text that needs to be overwritten. That string is written to the console, and the cursor is reset // a second time to be ready for a new message. // // The state of the console is now this, which is perfect for writing the next line. // // |X-----------------------------| // |------------------------------| // |------------------------------| // _______________________________ // // But... // The big complication is the window can be resized before the blanking message is written. // When the window starts resizing, the cursor is moved to the end of the line is currently on. All // text after the cursor is truncated. All text before the cursor is wrapped as the window is resized. // Take the first example from above again. When the window starts to resize, the cursor gets moved // to the 'r' in Wrap. After resizing, the state of the console could now be this // // __________________ // |ThisLineIsPretty| // |LongAndMayWX----| // _________________ // // Notice the cursor is no longer at the beginning of the text we want to overwrite. In order to // account for this, we must track the width of the first line and move the cursor back again if // the window size is smaller than it was when the first line was written. if (m_lastOverwriteableLine != null) { int bufferWidth = GetConsoleWidth(); // An overwriteable line was previously written. 3 operations must be performed: // 1. Check to see if the cursor's position needs to be adjusted in case the window was resized int linesToMoveCursorUp = 0; while (m_firstLineLength > bufferWidth) { linesToMoveCursorUp++; m_firstLineLength -= bufferWidth; } if (linesToMoveCursorUp > 0) { Console.SetCursorPosition(0, Math.Max(0, Console.CursorTop - linesToMoveCursorUp)); } // 2. Write enough blank text to completely overwrite the previous text int backtrackLines = ComputeLinesUsedByString(m_lastOverwriteableLine, bufferWidth); Console.Write(new string(' ', backtrackLines * bufferWidth)); // The console behavior is subtly different in newer Redstone builds. Previously, if the buffer // was 120 characters and 120 characters were written, the cursor would move itself down to the // next line. Now the cursor may actually stay at column position 120 on the original line and // not move down to the next until the 121st character is written. We expect the CursorLeft to be // at the 0 position after writing out the blanking text. So if we detect it isn't, this must be // an OS that has the different behavior. So we manually move the cursor down by writing an extra // character. This ensures the SetCursorPosition call below resets the cursor to the correct place // // This would all be much easier if we could just directly measure how many lines the cursor moved // after writing the blanking line. But if we are at the end of the console buffer, the CursorTop // will just remain constant. if (Console.CursorLeft != 0) { Console.Write(' '); } // 3. Reset the cursor back to its place before #2 so we can write new text in the same place. Console.SetCursorPosition(0, Math.Max(0, Console.CursorTop - backtrackLines)); m_lastOverwriteableLine = null; m_firstLineLength = 0; } if (overwritable) { int original = 0; int current = 0; if (m_debugConsole) { original = Console.CursorTop; } writer.WriteLine(line); if (m_debugConsole) { current = Console.CursorTop; } // After writing an overwriteable line, we must capture some information so the next message can // overwrite it m_lastOverwriteableLine = line; int bufferWidth = GetConsoleWidth(); m_firstLineLength = Math.Min(GetFirstLineLength(line), bufferWidth); // Now reset the cursor position to the beginning of the overwriteable line int computed = ComputeLinesUsedByString(line, bufferWidth); Console.SetCursorPosition(0, Math.Max(0, Console.CursorTop - computed)); if (m_debugConsole) { /* * The code to compute the number of lines written is fickle and can break if Windows changes * how the console behaves. Uncommenting the block to print these internal calculations vs. the * actual is helpful for debugging issues. */ int actual = current - original; Console.Write(computed + ":" + actual + " "); Console.SetCursorPosition(0, Console.CursorTop); } } else { writer.WriteLine(line); } if (color != m_defaultForegroundColor) { Console.ForegroundColor = m_defaultForegroundColor; } } catch (IOException ex) { // We know that the problem is in the console. No need to guess by calling AnalyzeExceptionRootCause throw new BuildXLException(ex.Message, ExceptionRootCause.ConsoleNotConnected); } } }