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);
}
}
}