📄 coloredconsoleappender.cs
字号:
// 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
m_consoleOutputWriter.Write(messageCharArray, 0, arrayLength);
// Restore the console back to its previous color scheme
SetConsoleTextAttribute(consoleHandle, bufferInfo.wAttributes);
if (appendNewline)
{
// Write the newline, after changing the color scheme
m_consoleOutputWriter.Write(s_windowsNewline, 0, 2);
}
}
}
private static readonly char[] s_windowsNewline = {'\r', '\n'};
/// <summary>
/// This appender requires a <see cref="Layout"/> to be set.
/// </summary>
/// <value><c>true</c></value>
/// <remarks>
/// <para>
/// This appender requires a <see cref="Layout"/> to be set.
/// </para>
/// </remarks>
override protected bool RequiresLayout
{
get { return true; }
}
/// <summary>
/// Initialize the options for this appender
/// </summary>
/// <remarks>
/// <para>
/// Initialize the level to color mappings set on this appender.
/// </para>
/// </remarks>
public override void ActivateOptions()
{
base.ActivateOptions();
m_levelMapping.ActivateOptions();
System.IO.Stream consoleOutputStream = null;
// Use the Console methods to open a Stream over the console std handle
if (m_writeToErrorStream)
{
// Write to the error stream
consoleOutputStream = Console.OpenStandardError();
}
else
{
// Write to the output stream
consoleOutputStream = Console.OpenStandardOutput();
}
// Lookup the codepage encoding for the console
System.Text.Encoding consoleEncoding = System.Text.Encoding.GetEncoding(GetConsoleOutputCP());
// Create a writer around the console stream
m_consoleOutputWriter = new System.IO.StreamWriter(consoleOutputStream, consoleEncoding, 0x100);
m_consoleOutputWriter.AutoFlush = true;
// SuppressFinalize on m_consoleOutputWriter because all it will do is flush
// and close the file handle. Because we have set AutoFlush the additional flush
// is not required. The console file handle should not be closed, so we don't call
// Dispose, Close or the finalizer.
GC.SuppressFinalize(m_consoleOutputWriter);
}
#endregion // Override implementation of AppenderSkeleton
#region Public Static Fields
/// <summary>
/// The <see cref="ColoredConsoleAppender.Target"/> to use when writing to the Console
/// standard output stream.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="ColoredConsoleAppender.Target"/> to use when writing to the Console
/// standard output stream.
/// </para>
/// </remarks>
public const string ConsoleOut = "Console.Out";
/// <summary>
/// The <see cref="ColoredConsoleAppender.Target"/> to use when writing to the Console
/// standard error output stream.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="ColoredConsoleAppender.Target"/> to use when writing to the Console
/// standard error output stream.
/// </para>
/// </remarks>
public const string ConsoleError = "Console.Error";
#endregion // Public Static Fields
#region Private Instances Fields
/// <summary>
/// Flag to write output to the error stream rather than the standard output stream
/// </summary>
private bool m_writeToErrorStream = false;
/// <summary>
/// Mapping from level object to color value
/// </summary>
private LevelMapping m_levelMapping = new LevelMapping();
/// <summary>
/// The console output stream writer to write to
/// </summary>
/// <remarks>
/// <para>
/// This writer is not thread safe.
/// </para>
/// </remarks>
private System.IO.StreamWriter m_consoleOutputWriter = null;
#endregion // Private Instances Fields
#region Win32 Methods
[DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern int GetConsoleOutputCP();
[DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern bool SetConsoleTextAttribute(
IntPtr consoleHandle,
ushort attributes);
[DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern bool GetConsoleScreenBufferInfo(
IntPtr consoleHandle,
out CONSOLE_SCREEN_BUFFER_INFO bufferInfo);
// [DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
// private static extern bool WriteConsoleW(
// IntPtr hConsoleHandle,
// [MarshalAs(UnmanagedType.LPWStr)] string strBuffer,
// UInt32 bufferLen,
// out UInt32 written,
// IntPtr reserved);
//private const UInt32 STD_INPUT_HANDLE = unchecked((UInt32)(-10));
private const UInt32 STD_OUTPUT_HANDLE = unchecked((UInt32)(-11));
private const UInt32 STD_ERROR_HANDLE = unchecked((UInt32)(-12));
[DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern IntPtr GetStdHandle(
UInt32 type);
[StructLayout(LayoutKind.Sequential)]
private struct COORD
{
public UInt16 x;
public UInt16 y;
}
[StructLayout(LayoutKind.Sequential)]
private struct SMALL_RECT
{
public UInt16 Left;
public UInt16 Top;
public UInt16 Right;
public UInt16 Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct CONSOLE_SCREEN_BUFFER_INFO
{
public COORD dwSize;
public COORD dwCursorPosition;
public ushort wAttributes;
public SMALL_RECT srWindow;
public COORD dwMaximumWindowSize;
}
#endregion // Win32 Methods
#region LevelColors LevelMapping Entry
/// <summary>
/// A class to act as a mapping between the level that a logging call is made at and
/// the color it should be displayed as.
/// </summary>
/// <remarks>
/// <para>
/// Defines the mapping between a level and the color it should be displayed in.
/// </para>
/// </remarks>
public class LevelColors : LevelMappingEntry
{
private Colors m_foreColor;
private Colors m_backColor;
private ushort m_combinedColor = 0;
/// <summary>
/// The mapped foreground color for the specified level
/// </summary>
/// <remarks>
/// <para>
/// Required property.
/// The mapped foreground color for the specified level.
/// </para>
/// </remarks>
public Colors ForeColor
{
get { return m_foreColor; }
set { m_foreColor = value; }
}
/// <summary>
/// The mapped background color for the specified level
/// </summary>
/// <remarks>
/// <para>
/// Required property.
/// The mapped background color for the specified level.
/// </para>
/// </remarks>
public Colors BackColor
{
get { return m_backColor; }
set { m_backColor = value; }
}
/// <summary>
/// Initialize the options for the object
/// </summary>
/// <remarks>
/// <para>
/// Combine the <see cref="ForeColor"/> and <see cref="BackColor"/> together.
/// </para>
/// </remarks>
public override void ActivateOptions()
{
base.ActivateOptions();
m_combinedColor = (ushort)( (int)m_foreColor + (((int)m_backColor) << 4) );
}
/// <summary>
/// The combined <see cref="ForeColor"/> and <see cref="BackColor"/> suitable for
/// setting the console color.
/// </summary>
internal ushort CombinedColor
{
get { return m_combinedColor; }
}
}
#endregion // LevelColors LevelMapping Entry
}
}
#endif // !CLI_1_0
#endif // !SSCLI
#endif // !MONO
#endif // !NETCF
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -