#region Apache License
|
//
|
// Licensed to the Apache Software Foundation (ASF) under one or more
|
// contributor license agreements. See the NOTICE file distributed with
|
// this work for additional information regarding copyright ownership.
|
// The ASF licenses this file to you under the Apache License, Version 2.0
|
// (the "License"); you may not use this file except in compliance with
|
// the License. You may obtain a copy of the License at
|
//
|
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
// Unless required by applicable law or agreed to in writing, software
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// See the License for the specific language governing permissions and
|
// limitations under the License.
|
//
|
#endregion
|
|
// MONO 1.0 Beta mcs does not like #if !A && !B && !C syntax
|
|
// .NET Compact Framework 1.0 has no support for Win32 Console API's
|
#if !NETCF
|
// .Mono 1.0 has no support for Win32 Console API's
|
#if !MONO
|
// SSCLI 1.0 has no support for Win32 Console API's
|
#if !SSCLI
|
// We don't want framework or platform specific code in the CLI version of log4net
|
#if !CLI_1_0
|
|
using System;
|
using System.Globalization;
|
using System.Runtime.InteropServices;
|
|
using log4net.Layout;
|
using log4net.Util;
|
|
namespace log4net.Appender
|
{
|
/// <summary>
|
/// Appends logging events to the console.
|
/// </summary>
|
/// <remarks>
|
/// <para>
|
/// ColoredConsoleAppender appends log events to the standard output stream
|
/// or the error output stream using a layout specified by the
|
/// user. It also allows the color of a specific type of message to be set.
|
/// </para>
|
/// <para>
|
/// By default, all output is written to the console's standard output stream.
|
/// The <see cref="Target"/> property can be set to direct the output to the
|
/// error stream.
|
/// </para>
|
/// <para>
|
/// NOTE: This appender writes directly to the application's attached console
|
/// not to the <c>System.Console.Out</c> or <c>System.Console.Error</c> <c>TextWriter</c>.
|
/// The <c>System.Console.Out</c> and <c>System.Console.Error</c> streams can be
|
/// programmatically redirected (for example NUnit does this to capture program output).
|
/// This appender will ignore these redirections because it needs to use Win32
|
/// API calls to colorize the output. To respect these redirections the <see cref="ConsoleAppender"/>
|
/// must be used.
|
/// </para>
|
/// <para>
|
/// When configuring the colored console appender, mapping should be
|
/// specified to map a logging level to a color. For example:
|
/// </para>
|
/// <code lang="XML" escaped="true">
|
/// <mapping>
|
/// <level value="ERROR" />
|
/// <foreColor value="White" />
|
/// <backColor value="Red, HighIntensity" />
|
/// </mapping>
|
/// <mapping>
|
/// <level value="DEBUG" />
|
/// <backColor value="Green" />
|
/// </mapping>
|
/// </code>
|
/// <para>
|
/// The Level is the standard log4net logging level and ForeColor and BackColor can be any
|
/// combination of the following values:
|
/// <list type="bullet">
|
/// <item><term>Blue</term><description></description></item>
|
/// <item><term>Green</term><description></description></item>
|
/// <item><term>Red</term><description></description></item>
|
/// <item><term>White</term><description></description></item>
|
/// <item><term>Yellow</term><description></description></item>
|
/// <item><term>Purple</term><description></description></item>
|
/// <item><term>Cyan</term><description></description></item>
|
/// <item><term>HighIntensity</term><description></description></item>
|
/// </list>
|
/// </para>
|
/// </remarks>
|
/// <author>Rick Hobbs</author>
|
/// <author>Nicko Cadell</author>
|
public class ColoredConsoleAppender : AppenderSkeleton
|
{
|
#region Colors Enum
|
|
/// <summary>
|
/// The enum of possible color values for use with the color mapping method
|
/// </summary>
|
/// <remarks>
|
/// <para>
|
/// The following flags can be combined together to
|
/// form the colors.
|
/// </para>
|
/// </remarks>
|
/// <seealso cref="ColoredConsoleAppender" />
|
[Flags]
|
public enum Colors : int
|
{
|
/// <summary>
|
/// color is blue
|
/// </summary>
|
Blue = 0x0001,
|
|
/// <summary>
|
/// color is green
|
/// </summary>
|
Green = 0x0002,
|
|
/// <summary>
|
/// color is red
|
/// </summary>
|
Red = 0x0004,
|
|
/// <summary>
|
/// color is white
|
/// </summary>
|
White = Blue | Green | Red,
|
|
/// <summary>
|
/// color is yellow
|
/// </summary>
|
Yellow = Red | Green,
|
|
/// <summary>
|
/// color is purple
|
/// </summary>
|
Purple = Red | Blue,
|
|
/// <summary>
|
/// color is cyan
|
/// </summary>
|
Cyan = Green | Blue,
|
|
/// <summary>
|
/// color is intensified
|
/// </summary>
|
HighIntensity = 0x0008,
|
}
|
|
#endregion // Colors Enum
|
|
#region Public Instance Constructors
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ColoredConsoleAppender" /> class.
|
/// </summary>
|
/// <remarks>
|
/// The instance of the <see cref="ColoredConsoleAppender" /> class is set up to write
|
/// to the standard output stream.
|
/// </remarks>
|
public ColoredConsoleAppender()
|
{
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ColoredConsoleAppender" /> class
|
/// with the specified layout.
|
/// </summary>
|
/// <param name="layout">the layout to use for this appender</param>
|
/// <remarks>
|
/// The instance of the <see cref="ColoredConsoleAppender" /> class is set up to write
|
/// to the standard output stream.
|
/// </remarks>
|
[Obsolete("Instead use the default constructor and set the Layout property")]
|
public ColoredConsoleAppender(ILayout layout) : this(layout, false)
|
{
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ColoredConsoleAppender" /> class
|
/// with the specified layout.
|
/// </summary>
|
/// <param name="layout">the layout to use for this appender</param>
|
/// <param name="writeToErrorStream">flag set to <c>true</c> to write to the console error stream</param>
|
/// <remarks>
|
/// When <paramref name="writeToErrorStream" /> is set to <c>true</c>, output is written to
|
/// the standard error output stream. Otherwise, output is written to the standard
|
/// output stream.
|
/// </remarks>
|
[Obsolete("Instead use the default constructor and set the Layout & Target properties")]
|
public ColoredConsoleAppender(ILayout layout, bool writeToErrorStream)
|
{
|
Layout = layout;
|
m_writeToErrorStream = writeToErrorStream;
|
}
|
|
#endregion // Public Instance Constructors
|
|
#region Public Instance Properties
|
|
/// <summary>
|
/// Target is the value of the console output stream.
|
/// This is either <c>"Console.Out"</c> or <c>"Console.Error"</c>.
|
/// </summary>
|
/// <value>
|
/// Target is the value of the console output stream.
|
/// This is either <c>"Console.Out"</c> or <c>"Console.Error"</c>.
|
/// </value>
|
/// <remarks>
|
/// <para>
|
/// Target is the value of the console output stream.
|
/// This is either <c>"Console.Out"</c> or <c>"Console.Error"</c>.
|
/// </para>
|
/// </remarks>
|
virtual public string Target
|
{
|
get { return m_writeToErrorStream ? ConsoleError : ConsoleOut; }
|
set
|
{
|
string v = value.Trim();
|
|
if (string.Compare(ConsoleError, v, true, CultureInfo.InvariantCulture) == 0)
|
{
|
m_writeToErrorStream = true;
|
}
|
else
|
{
|
m_writeToErrorStream = false;
|
}
|
}
|
}
|
|
/// <summary>
|
/// Add a mapping of level to color - done by the config file
|
/// </summary>
|
/// <param name="mapping">The mapping to add</param>
|
/// <remarks>
|
/// <para>
|
/// Add a <see cref="LevelColors"/> mapping to this appender.
|
/// Each mapping defines the foreground and background colors
|
/// for a level.
|
/// </para>
|
/// </remarks>
|
public void AddMapping(LevelColors mapping)
|
{
|
m_levelMapping.Add(mapping);
|
}
|
|
#endregion // Public Instance Properties
|
|
#region Override implementation of AppenderSkeleton
|
|
/// <summary>
|
/// This method is called by the <see cref="AppenderSkeleton.DoAppend(log4net.Core.LoggingEvent)"/> method.
|
/// </summary>
|
/// <param name="loggingEvent">The event to log.</param>
|
/// <remarks>
|
/// <para>
|
/// Writes the event to the console.
|
/// </para>
|
/// <para>
|
/// The format of the output will depend on the appender's layout.
|
/// </para>
|
/// </remarks>
|
#if NET_4_0
|
[System.Security.SecuritySafeCritical]
|
#endif
|
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)]
|
override protected void Append(log4net.Core.LoggingEvent loggingEvent)
|
{
|
if (m_consoleOutputWriter != null)
|
{
|
IntPtr consoleHandle = IntPtr.Zero;
|
if (m_writeToErrorStream)
|
{
|
// Write to the error stream
|
consoleHandle = GetStdHandle(STD_ERROR_HANDLE);
|
}
|
else
|
{
|
// Write to the output stream
|
consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
}
|
|
// Default to white on black
|
ushort colorInfo = (ushort)Colors.White;
|
|
// see if there is a specified lookup
|
LevelColors levelColors = m_levelMapping.Lookup(loggingEvent.Level) as LevelColors;
|
if (levelColors != null)
|
{
|
colorInfo = levelColors.CombinedColor;
|
}
|
|
// Render the event to a string
|
string strLoggingMessage = RenderLoggingEvent(loggingEvent);
|
|
// get the current console color - to restore later
|
CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
|
GetConsoleScreenBufferInfo(consoleHandle, out bufferInfo);
|
|
// set the console colors
|
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
|
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>
|
#if NET_4_0
|
[System.Security.SecuritySafeCritical]
|
#endif
|
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode=true)]
|
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
|