protected override void Append()

in src/log4net/Appender/ColoredConsoleAppender.cs [197:325]


  protected override void Append(LoggingEvent loggingEvent)
  {
    loggingEvent.EnsureNotNull();
    if (_consoleOutputWriter is not null)
    {
      IntPtr consoleHandle = NativeMethods.GetStdHandle(_writeToErrorStream ? NativeMethods.StdErrorHandle : NativeMethods.StdOutputHandle);

      // Default to white on black
      ushort colorInfo = (ushort)Colors.White;

      // see if there is a specified lookup
      if (_levelMapping.Lookup(loggingEvent.Level) is LevelColors levelColors)
      {
        colorInfo = levelColors.CombinedColor;
      }

      // Render the event to a string
      string strLoggingMessage = RenderLoggingEvent(loggingEvent);

      // get the current console color - to restore later
      NativeMethods.GetConsoleScreenBufferInfo(consoleHandle, out NativeMethods.ConsoleScreenBufferInfo bufferInfo);

      // set the console colors
      NativeMethods.SetConsoleTextAttribute(consoleHandle, colorInfo);

      // Using WriteConsoleW seems to be unreliable.
      // If a large buffer is written, say 15,000 chars
      // Followed by a larger buffer, say 20,000 chars
      // then WriteConsoleW will fail, last error 8
      // 'Not enough storage is available to process this command.'
      // 
      // Although the documentation states that the buffer must
      // be less that 64KB (i.e. 32,000 WCHARs) the longest string
      // that I can write out a the first call to WriteConsoleW
      // is only 30,704 chars.
      //
      // Unlike the WriteFile API the WriteConsoleW method does not 
      // seem to be able to partially write out from the input buffer.
      // It does have a lpNumberOfCharsWritten parameter, but this is
      // either the length of the input buffer if any output was written,
      // or 0 when an error occurs.
      //
      // All results above were observed on Windows XP SP1 running
      // .NET runtime 1.1 SP1.
      //
      // Old call to WriteConsoleW:
      //
      // WriteConsoleW(
      //     consoleHandle,
      //     strLoggingMessage,
      //     (UInt32)strLoggingMessage.Length,
      //     out (UInt32)ignoreWrittenCount,
      //     IntPtr.Zero);
      //
      // Instead of calling WriteConsoleW we use WriteFile which 
      // handles large buffers correctly. Because WriteFile does not
      // handle the codepage conversion as WriteConsoleW does we 
      // need to use a System.IO.StreamWriter with the appropriate
      // Encoding. The WriteFile calls are wrapped up in the
      // System.IO.__ConsoleStream internal class obtained through
      // the System.Console.OpenStandardOutput method.
      //
      // See the ActivateOptions method below for the code that
      // retrieves and wraps the stream.


      // The windows console uses ScrollConsoleScreenBuffer internally to
      // scroll the console buffer when the display buffer of the console
      // has been used up. ScrollConsoleScreenBuffer fills the area uncovered
      // by moving the current content with the background color 
      // currently specified on the console. This means that it fills the
      // whole line in front of the cursor position with the current 
      // background color.
      // This causes an issue when writing out text with a non default
      // background color. For example; We write a message with a Blue
      // background color and the scrollable area of the console is full.
      // When we write the newline at the end of the message the console
      // needs to scroll the buffer to make space available for the new line.
      // The ScrollConsoleScreenBuffer internals will fill the newly created
      // space with the current background color: Blue.
      // We then change the console color back to default (White text on a
      // Black background). We write some text to the console, the text is
      // written correctly in White with a Black background, however the
      // remainder of the line still has a Blue background.
      // 
      // This causes a disjointed appearance to the output where the background
      // colors change.
      //
      // This can be remedied by restoring the console colors before causing
      // the buffer to scroll, i.e. before writing the last newline. This does
      // assume that the rendered message will end with a newline.
      //
      // Therefore we identify a trailing newline in the message and don't
      // write this to the output, then we restore the console color and write
      // a newline. Note that we must AutoFlush before we restore the console
      // color otherwise we will have no effect.
      //
      // There will still be a slight artefact for the last line of the message
      // will have the background extended to the end of the line, however this
      // is unlikely to cause any user issues.
      //
      // Note that none of the above is visible while the console buffer is scrollable
      // within the console window viewport, the effects only arise when the actual
      // buffer is full and needs to be scrolled.

      char[] messageCharArray = strLoggingMessage.ToCharArray();
      int arrayLength = messageCharArray.Length;
      bool appendNewline = false;

      // Trim off last newline, if it exists
      if (arrayLength > 1 && messageCharArray[arrayLength - 2] == '\r' && messageCharArray[arrayLength - 1] == '\n')
      {
        arrayLength -= 2;
        appendNewline = true;
      }

      // Write to the output stream
      _consoleOutputWriter.Write(messageCharArray, 0, arrayLength);

      // Restore the console back to its previous color scheme
      NativeMethods.SetConsoleTextAttribute(consoleHandle, bufferInfo.wAttributes);

      if (appendNewline)
      {
        // Write the newline, after changing the color scheme
        _consoleOutputWriter.Write(_windowsNewline, 0, 2);
      }
    }
  }